diff --git a/Libraries/LibWeb/Animations/Animatable.cpp b/Libraries/LibWeb/Animations/Animatable.cpp index c3370d4a5c..e8d877d7fd 100644 --- a/Libraries/LibWeb/Animations/Animatable.cpp +++ b/Libraries/LibWeb/Animations/Animatable.cpp @@ -246,10 +246,11 @@ void Animatable::visit_edges(JS::Cell::Visitor& visitor) { auto& impl = ensure_impl(); visitor.visit(impl.associated_animations); - for (auto const& cached_animation_source : impl.cached_animation_name_source) - visitor.visit(cached_animation_source); - for (auto const& cached_animation_name : impl.cached_animation_name_animation) - visitor.visit(cached_animation_name); + for (auto const& css_animation : impl.css_defined_animations) { + if (css_animation) + visitor.visit(*css_animation); + } + for (auto const& transition : impl.transitions) { if (transition) { visitor.visit(transition->cached_transition_property_source); @@ -258,61 +259,21 @@ void Animatable::visit_edges(JS::Cell::Visitor& visitor) } } -GC::Ptr Animatable::cached_animation_name_source(Optional pseudo_element) const -{ - if (!m_impl) - return {}; - auto& impl = *m_impl; - if (pseudo_element.has_value()) { - if (!CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(pseudo_element.value())) { - return {}; - } - return impl.cached_animation_name_source[to_underlying(pseudo_element.value()) + 1]; - } - return impl.cached_animation_name_source[0]; -} - -void Animatable::set_cached_animation_name_source(GC::Ptr value, Optional pseudo_element) +HashMap>* Animatable::css_defined_animations(Optional pseudo_element) { auto& impl = ensure_impl(); - if (pseudo_element.has_value()) { - if (!CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(pseudo_element.value())) { - return; - } - impl.cached_animation_name_source[to_underlying(pseudo_element.value()) + 1] = value; - } else { - impl.cached_animation_name_source[0] = value; - } -} -GC::Ptr Animatable::cached_animation_name_animation(Optional pseudo_element) const -{ - if (!m_impl) - return {}; - auto& impl = *m_impl; + if (pseudo_element.has_value() && !CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(pseudo_element.value())) + return nullptr; - if (pseudo_element.has_value()) { - if (!CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(pseudo_element.value())) { - return {}; - } + auto index = pseudo_element + .map([](CSS::PseudoElement pseudo_element_value) { return to_underlying(pseudo_element_value) + 1; }) + .value_or(0); - return impl.cached_animation_name_animation[to_underlying(pseudo_element.value()) + 1]; - } - return impl.cached_animation_name_animation[0]; -} + if (!impl.css_defined_animations[index]) + impl.css_defined_animations[index] = make>>(); -void Animatable::set_cached_animation_name_animation(GC::Ptr value, Optional pseudo_element) -{ - auto& impl = ensure_impl(); - if (pseudo_element.has_value()) { - if (!CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(pseudo_element.value())) { - return; - } - - impl.cached_animation_name_animation[to_underlying(pseudo_element.value()) + 1] = value; - } else { - impl.cached_animation_name_animation[0] = value; - } + return impl.css_defined_animations[index]; } GC::Ptr Animatable::cached_transition_property_source(Optional pseudo_element) const diff --git a/Libraries/LibWeb/Animations/Animatable.h b/Libraries/LibWeb/Animations/Animatable.h index 09e86f6706..fc56f59809 100644 --- a/Libraries/LibWeb/Animations/Animatable.h +++ b/Libraries/LibWeb/Animations/Animatable.h @@ -52,11 +52,9 @@ public: void associate_with_animation(GC::Ref); void disassociate_with_animation(GC::Ref); - GC::Ptr cached_animation_name_source(Optional) const; - void set_cached_animation_name_source(GC::Ptr value, Optional); - - GC::Ptr cached_animation_name_animation(Optional) const; - void set_cached_animation_name_animation(GC::Ptr value, Optional); + HashMap>* css_defined_animations(Optional); + void add_css_animation(FlyString name, Optional, GC::Ref); + void remove_css_animation(FlyString name, Optional); GC::Ptr cached_transition_property_source(Optional) const; void set_cached_transition_property_source(Optional, GC::Ptr value); @@ -80,9 +78,7 @@ private: Vector> associated_animations; bool is_sorted_by_composite_order { true }; - Array, to_underlying(CSS::PseudoElement::KnownPseudoElementCount) + 1> cached_animation_name_source; - Array, to_underlying(CSS::PseudoElement::KnownPseudoElementCount) + 1> cached_animation_name_animation; - + mutable Array>>, to_underlying(CSS::PseudoElement::KnownPseudoElementCount) + 1> css_defined_animations; mutable Array, to_underlying(CSS::PseudoElement::KnownPseudoElementCount) + 1> transitions; ~Impl(); diff --git a/Libraries/LibWeb/CSS/ComputedProperties.cpp b/Libraries/LibWeb/CSS/ComputedProperties.cpp index d57576aeec..d414caf71a 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.cpp +++ b/Libraries/LibWeb/CSS/ComputedProperties.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -53,7 +54,6 @@ ComputedProperties::~ComputedProperties() = default; void ComputedProperties::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); - visitor.visit(m_animation_name_source); visitor.visit(m_transition_property_source); } @@ -1934,6 +1934,108 @@ Optional ComputedProperties::view_transition_name() const return {}; } +Vector ComputedProperties::animations() const +{ + // CSS Animations are defined by binding keyframes to an element using the animation-* properties. These list-valued + // properties, which are all longhands of the animation shorthand, form a coordinating list property group with + // animation-name as the coordinating list base property and each item in the coordinated value list defining the + // properties of a single animation effect. + auto const& coordinated_properties = assemble_coordinated_value_list( + PropertyID::AnimationName, + { PropertyID::AnimationDuration, + PropertyID::AnimationTimingFunction, + PropertyID::AnimationIterationCount, + PropertyID::AnimationDirection, + PropertyID::AnimationPlayState, + PropertyID::AnimationDelay, + PropertyID::AnimationFillMode, + PropertyID::AnimationComposition, + PropertyID::AnimationName }); + + Vector animations; + + for (size_t i = 0; i < coordinated_properties.get(PropertyID::AnimationName)->size(); i++) { + // https://drafts.csswg.org/css-animations-1/#propdef-animation-name + // none: No keyframes are specified at all, so there will be no animation. Any other animations properties + // specified for this animation have no effect. + if (coordinated_properties.get(PropertyID::AnimationName).value()[i]->to_keyword() == Keyword::None) + continue; + + auto animation_name_style_value = coordinated_properties.get(PropertyID::AnimationName).value()[i]; + auto animation_duration_style_value = coordinated_properties.get(PropertyID::AnimationDuration).value()[i]; + auto animation_timing_function_style_value = coordinated_properties.get(PropertyID::AnimationTimingFunction).value()[i]; + auto animation_iteration_count_style_value = coordinated_properties.get(PropertyID::AnimationIterationCount).value()[i]; + auto animation_direction_style_value = coordinated_properties.get(PropertyID::AnimationDirection).value()[i]; + auto animation_play_state_style_value = coordinated_properties.get(PropertyID::AnimationPlayState).value()[i]; + auto animation_delay_style_value = coordinated_properties.get(PropertyID::AnimationDelay).value()[i]; + auto animation_fill_mode_style_value = coordinated_properties.get(PropertyID::AnimationFillMode).value()[i]; + auto animation_composition_style_value = coordinated_properties.get(PropertyID::AnimationComposition).value()[i]; + + auto duration = [&] { + if (animation_duration_style_value->is_time()) + return animation_duration_style_value->as_time().time().to_milliseconds(); + + if (animation_duration_style_value->is_calculated()) + return animation_duration_style_value->as_calculated().resolve_time({}).value().to_milliseconds(); + + VERIFY_NOT_REACHED(); + }(); + + auto timing_function = EasingFunction::from_style_value(animation_timing_function_style_value); + + auto iteration_count = [&] { + if (animation_iteration_count_style_value->to_keyword() == Keyword::Infinite) + return AK::Infinity; + + if (animation_iteration_count_style_value->is_number()) + return animation_iteration_count_style_value->as_number().number(); + + if (animation_iteration_count_style_value->is_calculated()) + return animation_iteration_count_style_value->as_calculated().resolve_number({}).value(); + + VERIFY_NOT_REACHED(); + }(); + + auto direction = keyword_to_animation_direction(animation_direction_style_value->to_keyword()).value(); + auto play_state = keyword_to_animation_play_state(animation_play_state_style_value->to_keyword()).value(); + auto delay = [&] { + if (animation_delay_style_value->is_time()) + return animation_delay_style_value->as_time().time().to_milliseconds(); + + if (animation_delay_style_value->is_calculated()) + return animation_delay_style_value->as_calculated().resolve_time({}).value().to_milliseconds(); + + VERIFY_NOT_REACHED(); + }(); + auto fill_mode = keyword_to_animation_fill_mode(animation_fill_mode_style_value->to_keyword()).value(); + auto composition = keyword_to_animation_composition(animation_composition_style_value->to_keyword()).value(); + + auto name = [&] { + if (animation_name_style_value->is_custom_ident()) + return animation_name_style_value->as_custom_ident().custom_ident(); + + if (animation_name_style_value->is_string()) + return animation_name_style_value->as_string().string_value(); + + VERIFY_NOT_REACHED(); + }(); + + animations.append(AnimationProperties { + .duration = duration, + .timing_function = timing_function, + .iteration_count = iteration_count, + .direction = direction, + .play_state = play_state, + .delay = delay, + .fill_mode = fill_mode, + .composition = composition, + .name = name, + }); + } + + return animations; +} + MaskType ComputedProperties::mask_type() const { auto const& value = property(PropertyID::MaskType); diff --git a/Libraries/LibWeb/CSS/ComputedProperties.h b/Libraries/LibWeb/CSS/ComputedProperties.h index edaffcce70..0afa1b5ea9 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.h +++ b/Libraries/LibWeb/CSS/ComputedProperties.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -68,9 +69,6 @@ public: StyleValue const& property(PropertyID, WithAnimationsApplied = WithAnimationsApplied::Yes) const; void revert_property(PropertyID, ComputedProperties const& style_for_revert); - GC::Ptr animation_name_source() const { return m_animation_name_source; } - void set_animation_name_source(GC::Ptr declaration) { m_animation_name_source = declaration; } - GC::Ptr transition_property_source() const { return m_transition_property_source; } void set_transition_property_source(GC::Ptr declaration) { m_transition_property_source = declaration; } @@ -187,6 +185,18 @@ public: ContainerType container_type() const; MixBlendMode mix_blend_mode() const; Optional view_transition_name() const; + struct AnimationProperties { + Variant duration; + EasingFunction timing_function; + double iteration_count; + AnimationDirection direction; + AnimationPlayState play_state; + double delay; + AnimationFillMode fill_mode; + AnimationComposition composition; + FlyString name; + }; + Vector animations() const; Display display_before_box_type_transformation() const; void set_display_before_box_type_transformation(Display value); @@ -274,7 +284,6 @@ private: Overflow overflow(PropertyID) const; Vector shadow(PropertyID, Layout::Node const&) const; - GC::Ptr m_animation_name_source; GC::Ptr m_transition_property_source; Array, number_of_longhand_properties> m_property_values; diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index 1e0563d3a5..cce9a5c035 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -1177,103 +1177,92 @@ void StyleComputer::collect_animation_into(DOM::AbstractElement abstract_element } } -static void apply_animation_properties(DOM::Document& document, CascadedProperties& cascaded_properties, Animations::Animation& animation) +static void apply_animation_properties(DOM::Document const& document, ComputedProperties::AnimationProperties const& animation_properties, Animations::Animation& animation) { - if (!animation.effect()) - return; + VERIFY(animation.effect()); auto& effect = as(*animation.effect()); - Optional duration; - if (auto duration_value = cascaded_properties.property(PropertyID::AnimationDuration); duration_value) { - if (duration_value->is_time()) { - duration = duration_value->as_time().time(); - } else if (duration_value->is_keyword() && duration_value->as_keyword().keyword() == Keyword::Auto) { - // We use empty optional to represent "auto". - duration = {}; - } else if (duration_value->is_calculated() && duration_value->as_calculated().resolves_to_time()) { - auto resolved_time = duration_value->as_calculated().resolve_time({}); - if (resolved_time.has_value()) { - duration = resolved_time.value(); - } - } - } + effect.set_iteration_duration(animation_properties.duration); + effect.set_start_delay(animation_properties.delay); + effect.set_iteration_count(animation_properties.iteration_count); + effect.set_timing_function(animation_properties.timing_function); + effect.set_fill_mode(Animations::css_fill_mode_to_bindings_fill_mode(animation_properties.fill_mode)); + effect.set_playback_direction(Animations::css_animation_direction_to_bindings_playback_direction(animation_properties.direction)); + effect.set_composite(Animations::css_animation_composition_to_bindings_composite_operation(animation_properties.composition)); - auto delay = Time::make_seconds(0); - if (auto delay_value = cascaded_properties.property(PropertyID::AnimationDelay); delay_value) { - if (delay_value->is_time()) { - delay = delay_value->as_time().time(); - } else if (delay_value->is_calculated() && delay_value->as_calculated().resolves_to_time()) { - auto resolved_time = delay_value->as_calculated().resolve_time({}); - if (resolved_time.has_value()) { - delay = resolved_time.value(); - } - } - } - - double iteration_count = 1.0; - if (auto iteration_count_value = cascaded_properties.property(PropertyID::AnimationIterationCount); iteration_count_value) { - if (iteration_count_value->is_keyword() && iteration_count_value->to_keyword() == Keyword::Infinite) - iteration_count = HUGE_VAL; - else if (iteration_count_value->is_number()) - iteration_count = iteration_count_value->as_number().number(); - else if (iteration_count_value->is_calculated() && iteration_count_value->as_calculated().resolves_to_number()) { - auto resolved_number = iteration_count_value->as_calculated().resolve_number({}); - if (resolved_number.has_value()) { - iteration_count = resolved_number.value(); - } - } - } - - CSS::AnimationFillMode fill_mode { CSS::AnimationFillMode::None }; - if (auto fill_mode_property = cascaded_properties.property(PropertyID::AnimationFillMode); fill_mode_property && fill_mode_property->is_keyword()) { - if (auto fill_mode_value = keyword_to_animation_fill_mode(fill_mode_property->to_keyword()); fill_mode_value.has_value()) - fill_mode = *fill_mode_value; - } - - CSS::AnimationDirection direction { CSS::AnimationDirection::Normal }; - if (auto direction_property = cascaded_properties.property(PropertyID::AnimationDirection); direction_property && direction_property->is_keyword()) { - if (auto direction_value = keyword_to_animation_direction(direction_property->to_keyword()); direction_value.has_value()) - direction = *direction_value; - } - - CSS::AnimationPlayState play_state { CSS::AnimationPlayState::Running }; - if (auto play_state_property = cascaded_properties.property(PropertyID::AnimationPlayState); play_state_property && play_state_property->is_keyword()) { - if (auto play_state_value = keyword_to_animation_play_state(play_state_property->to_keyword()); play_state_value.has_value()) - play_state = *play_state_value; - } - - EasingFunction timing_function = EasingFunction::ease(); - if (auto timing_property = cascaded_properties.property(PropertyID::AnimationTimingFunction); timing_property && (timing_property->is_easing() || (timing_property->is_keyword() && !timing_property->is_css_wide_keyword()))) - timing_function = EasingFunction::from_style_value(timing_property.release_nonnull()); - - AnimationComposition animation_composition { AnimationComposition::Replace }; - if (auto composite_property = cascaded_properties.property(PropertyID::AnimationComposition); composite_property) { - if (auto animation_composition_value = keyword_to_animation_composition(composite_property->to_keyword()); animation_composition_value.has_value()) - animation_composition = *animation_composition_value; - } - - auto iteration_duration = duration.has_value() - ? Variant { duration.release_value().to_milliseconds() } - : "auto"_string; - effect.set_iteration_duration(iteration_duration); - effect.set_start_delay(delay.to_milliseconds()); - effect.set_iteration_count(iteration_count); - effect.set_timing_function(move(timing_function)); - effect.set_fill_mode(Animations::css_fill_mode_to_bindings_fill_mode(fill_mode)); - effect.set_playback_direction(Animations::css_animation_direction_to_bindings_playback_direction(direction)); - effect.set_composite(Animations::css_animation_composition_to_bindings_composite_operation(animation_composition)); - - if (play_state != effect.last_css_animation_play_state()) { - if (play_state == CSS::AnimationPlayState::Running && animation.play_state() != Bindings::AnimationPlayState::Running) { + if (animation_properties.play_state != effect.last_css_animation_play_state()) { + if (animation_properties.play_state == CSS::AnimationPlayState::Running && animation.play_state() != Bindings::AnimationPlayState::Running) { HTML::TemporaryExecutionContext context(document.realm()); animation.play().release_value_but_fixme_should_propagate_errors(); - } else if (play_state == CSS::AnimationPlayState::Paused && animation.play_state() != Bindings::AnimationPlayState::Paused) { + } else if (animation_properties.play_state == CSS::AnimationPlayState::Paused && animation.play_state() != Bindings::AnimationPlayState::Paused) { HTML::TemporaryExecutionContext context(document.realm()); animation.pause().release_value_but_fixme_should_propagate_errors(); } - effect.set_last_css_animation_play_state(play_state); + effect.set_last_css_animation_play_state(animation_properties.play_state); + } +} + +// https://drafts.csswg.org/css-animations-1/#animations +void StyleComputer::process_animation_definitions(ComputedProperties const& computed_properties, DOM::AbstractElement& abstract_element) const +{ + auto const& animation_definitions = computed_properties.animations(); + + // FIXME: Add some animation helpers to AbstractElement once pseudo-elements are animatable. + auto& element = abstract_element.element(); + auto const& pseudo_element = abstract_element.pseudo_element(); + auto& document = abstract_element.document(); + + auto* element_animations = element.css_defined_animations(pseudo_element); + + // If we have a nullptr for element_animations it means that the pseudo element was invalid and thus we shouldn't apply animations + if (!element_animations) + return; + + HashTable defined_animation_names; + + for (auto const& animation_properties : animation_definitions) { + defined_animation_names.set(animation_properties.name); + + // Changes to the values of animation properties while the animation is running apply as if the animation had + // those values from when it began + if (auto const& existing_animation = element_animations->get(animation_properties.name); existing_animation.has_value()) { + apply_animation_properties(document, animation_properties, existing_animation.value()); + return; + } + + // An animation applies to an element if its name appears as one of the identifiers in the computed value of the + // animation-name property and the animation uses a valid @keyframes rule + auto animation = CSSAnimation::create(document.realm()); + animation->set_id(animation_properties.name); + animation->set_timeline(document.timeline()); + animation->set_owning_element(element); + + auto effect = Animations::KeyframeEffect::create(document.realm()); + animation->set_effect(effect); + + apply_animation_properties(document, animation_properties, animation); + + if (pseudo_element.has_value()) + effect->set_pseudo_element(Selector::PseudoElementSelector { pseudo_element.value() }); + + if (auto const* rule_cache = rule_cache_for_cascade_origin(CascadeOrigin::Author, {}, {})) { + if (auto keyframe_set = rule_cache->rules_by_animation_keyframes.get(animation_properties.name); keyframe_set.has_value()) + effect->set_key_frame_set(keyframe_set.value()); + } + + effect->set_target(&element); + element_animations->set(animation_properties.name, animation); + } + + // Once an animation has started it continues until it ends or the animation-name is removed + for (auto const& existing_animation_name : element_animations->keys()) { + if (defined_animation_names.contains(existing_animation_name)) + continue; + + element_animations->get(existing_animation_name).value()->cancel(Animations::Animation::ShouldInvalidate::No); + element_animations->remove(existing_animation_name); } } @@ -2569,9 +2558,6 @@ GC::Ref StyleComputer::compute_properties(DOM::AbstractEleme if (animated_value.has_value()) computed_style->set_animated_property(property_id, animated_value.value(), inherited); - if (property_id == PropertyID::AnimationName) { - computed_style->set_animation_name_source(cascaded_properties.property_source(property_id)); - } if (property_id == PropertyID::TransitionProperty) { computed_style->set_transition_property_source(cascaded_properties.property_source(property_id)); } @@ -2589,6 +2575,9 @@ GC::Ref StyleComputer::compute_properties(DOM::AbstractEleme // 4. Convert properties into their computed forms compute_property_values(computed_style, abstract_element); + // 5. Add or modify CSS-defined animations + process_animation_definitions(computed_style, abstract_element); + // FIXME: Support multiple entries for `animation` properties // Animation declarations [css-animations-2] auto animation_name = [&]() -> Optional { @@ -2600,76 +2589,30 @@ GC::Ref StyleComputer::compute_properties(DOM::AbstractEleme return animation_name.to_string(SerializationMode::Normal); }(); - // FIXME: Add some animation helpers to AbstractElement once pseudo-elements are animatable. - auto& element = abstract_element.element(); - auto pseudo_element = abstract_element.pseudo_element(); - - if (animation_name.has_value()) { - if (auto source_declaration = computed_style->animation_name_source()) { - auto& realm = element.realm(); - - if (source_declaration != element.cached_animation_name_source(pseudo_element)) { - // This animation name is new, so we need to create a new animation for it. - if (auto existing_animation = element.cached_animation_name_animation(pseudo_element)) - existing_animation->cancel(Animations::Animation::ShouldInvalidate::No); - element.set_cached_animation_name_source(source_declaration, pseudo_element); - - auto effect = Animations::KeyframeEffect::create(realm); - auto animation = CSSAnimation::create(realm); - animation->set_id(animation_name.release_value()); - animation->set_timeline(m_document->timeline()); - animation->set_owning_element(element); - animation->set_effect(effect); - apply_animation_properties(m_document, cascaded_properties, animation); - if (pseudo_element.has_value()) - effect->set_pseudo_element(Selector::PseudoElementSelector { pseudo_element.value() }); - - if (auto* rule_cache = rule_cache_for_cascade_origin(CascadeOrigin::Author, {}, {})) { - if (auto keyframe_set = rule_cache->rules_by_animation_keyframes.get(animation->id()); keyframe_set.has_value()) - effect->set_key_frame_set(keyframe_set.value()); - } - - effect->set_target(&element); - element.set_cached_animation_name_animation(animation, pseudo_element); - } else { - // The animation hasn't changed, but some properties of the animation may have - if (auto animation = element.cached_animation_name_animation(pseudo_element); animation) - apply_animation_properties(m_document, cascaded_properties, *animation); - } - } - } else { - // If the element had an existing animation, cancel it - if (auto existing_animation = element.cached_animation_name_animation(pseudo_element)) { - existing_animation->cancel(Animations::Animation::ShouldInvalidate::No); - element.set_cached_animation_name_animation({}, pseudo_element); - element.set_cached_animation_name_source({}, pseudo_element); - } - } - - auto animations = element.get_animations_internal(Animations::GetAnimationsOptions { .subtree = false }); + auto animations = abstract_element.element().get_animations_internal(Animations::GetAnimationsOptions { .subtree = false }); if (animations.is_exception()) { - dbgln("Error getting animations for element {}", element.debug_description()); + dbgln("Error getting animations for element {}", abstract_element.debug_description()); } else { for (auto& animation : animations.value()) { if (auto effect = animation->effect(); effect && effect->is_keyframe_effect()) { auto& keyframe_effect = *static_cast(effect.ptr()); - if (keyframe_effect.pseudo_element_type() == pseudo_element) + if (keyframe_effect.pseudo_element_type() == abstract_element.pseudo_element()) collect_animation_into(abstract_element, keyframe_effect, computed_style); } } } - // 5. Run automatic box type transformations + // 6. Run automatic box type transformations transform_box_type_if_needed(computed_style, abstract_element); - // 6. Apply any property-specific computed value logic + // 7. Apply any property-specific computed value logic resolve_effective_overflow_values(computed_style); compute_text_align(computed_style, abstract_element); - // 7. Let the element adjust computed style - element.adjust_computed_style(computed_style); + // 8. Let the element adjust computed style + abstract_element.element().adjust_computed_style(computed_style); - // 8. Transition declarations [css-transitions-1] + // 9. Transition declarations [css-transitions-1] // Theoretically this should be part of the cascade, but it works with computed values, which we don't have until now. compute_transitioned_properties(computed_style, abstract_element); if (auto previous_style = abstract_element.computed_properties()) { diff --git a/Libraries/LibWeb/CSS/StyleComputer.h b/Libraries/LibWeb/CSS/StyleComputer.h index 0ff321f619..848cfda00d 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Libraries/LibWeb/CSS/StyleComputer.h @@ -193,6 +193,7 @@ public: void compute_property_values(ComputedProperties&, Optional) const; void compute_font(ComputedProperties&, Optional) const; + void process_animation_definitions(ComputedProperties const& computed_properties, DOM::AbstractElement& abstract_element) const; [[nodiscard]] inline bool should_reject_with_ancestor_filter(Selector const&) const; diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 8bec910b30..ca4eaf8eff 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -4089,34 +4089,35 @@ void Element::play_or_cancel_animations_after_display_property_change() auto has_display_none_inclusive_ancestor = this->has_inclusive_ancestor_with_display_none(); - auto play_or_cancel_depending_on_display = [&](Animations::Animation& animation, Optional pseudo_element) { - if (has_display_none_inclusive_ancestor) { - animation.cancel(); - } else { - auto play_state { CSS::AnimationPlayState::Running }; - if (auto play_state_property = cascaded_properties(pseudo_element)->property(CSS::PropertyID::AnimationPlayState); - play_state_property && play_state_property->is_keyword()) { - if (auto play_state_value = keyword_to_animation_play_state( - play_state_property->to_keyword()); - play_state_value.has_value()) - play_state = *play_state_value; - } - if (play_state == CSS::AnimationPlayState::Running) { - HTML::TemporaryExecutionContext context(realm()); - animation.play().release_value_but_fixme_should_propagate_errors(); - } else if (play_state == CSS::AnimationPlayState::Paused) { - HTML::TemporaryExecutionContext context(realm()); - animation.pause().release_value_but_fixme_should_propagate_errors(); + auto play_or_cancel_depending_on_display = [&](HashMap>& animations, Optional pseudo_element) { + for (auto& [_, animation] : animations) { + if (has_display_none_inclusive_ancestor) { + animation->cancel(); + } else { + auto play_state { CSS::AnimationPlayState::Running }; + if (auto play_state_property = cascaded_properties(pseudo_element)->property(CSS::PropertyID::AnimationPlayState); + play_state_property && play_state_property->is_keyword()) { + if (auto play_state_value = keyword_to_animation_play_state( + play_state_property->to_keyword()); + play_state_value.has_value()) + play_state = *play_state_value; + } + if (play_state == CSS::AnimationPlayState::Running) { + HTML::TemporaryExecutionContext context(realm()); + animation->play().release_value_but_fixme_should_propagate_errors(); + } else if (play_state == CSS::AnimationPlayState::Paused) { + HTML::TemporaryExecutionContext context(realm()); + animation->pause().release_value_but_fixme_should_propagate_errors(); + } } } }; - if (auto animation = cached_animation_name_animation({})) - play_or_cancel_depending_on_display(*animation, {}); + play_or_cancel_depending_on_display(*css_defined_animations({}), {}); + for (auto i = 0; i < to_underlying(CSS::PseudoElement::KnownPseudoElementCount); i++) { auto pseudo_element = static_cast(i); - if (auto animation = cached_animation_name_animation(pseudo_element)) - play_or_cancel_depending_on_display(*animation, pseudo_element); + play_or_cancel_depending_on_display(*css_defined_animations(pseudo_element), pseudo_element); } } diff --git a/Tests/LibWeb/Text/expected/css/animations-use-computed-style.txt b/Tests/LibWeb/Text/expected/css/animations-use-computed-style.txt new file mode 100644 index 0000000000..8ac5d199e5 --- /dev/null +++ b/Tests/LibWeb/Text/expected/css/animations-use-computed-style.txt @@ -0,0 +1,2 @@ +linear +2 diff --git a/Tests/LibWeb/Text/expected/css/multiple-animations.txt b/Tests/LibWeb/Text/expected/css/multiple-animations.txt new file mode 100644 index 0000000000..07bd763a60 --- /dev/null +++ b/Tests/LibWeb/Text/expected/css/multiple-animations.txt @@ -0,0 +1,4 @@ +foo +1000 +bar +2000 diff --git a/Tests/LibWeb/Text/input/css/animations-use-computed-style.html b/Tests/LibWeb/Text/input/css/animations-use-computed-style.html new file mode 100644 index 0000000000..f2ce69f186 --- /dev/null +++ b/Tests/LibWeb/Text/input/css/animations-use-computed-style.html @@ -0,0 +1,32 @@ + + + + +
+ + + + diff --git a/Tests/LibWeb/Text/input/css/multiple-animations.html b/Tests/LibWeb/Text/input/css/multiple-animations.html new file mode 100644 index 0000000000..bab1e4a56f --- /dev/null +++ b/Tests/LibWeb/Text/input/css/multiple-animations.html @@ -0,0 +1,40 @@ + + + + +
+ + + +