瀏覽代碼

LibWeb: Handle pre-existing animations when considering animation-name

If a DOM::Element has an animation-name property, then in addition to
remembering where it came from, it will also remember the
Animations::Animation object that was created for it. This allows
StyleComputer to cancel that animation if the animation-name property
changes as well as to apply any changes required (for example, if
animation-play-state changes from "running" to "paused", it needs to
call .pause() on the animation).
Matthew Olsson 1 年之前
父節點
當前提交
0f54d797d2
共有 2 個文件被更改,包括 104 次插入76 次删除
  1. 4 0
      Userland/Libraries/LibWeb/Animations/Animatable.h
  2. 100 76
      Userland/Libraries/LibWeb/CSS/StyleComputer.cpp

+ 4 - 0
Userland/Libraries/LibWeb/Animations/Animatable.h

@@ -36,10 +36,14 @@ public:
     JS::GCPtr<CSS::CSSStyleDeclaration const> cached_animation_name_source() const { return m_cached_animation_name_source; }
     void set_cached_animation_name_source(JS::GCPtr<CSS::CSSStyleDeclaration const> value) { m_cached_animation_name_source = value; }
 
+    JS::GCPtr<Animations::Animation> cached_animation_name_animation() const { return m_cached_animation_name_animation; }
+    void set_cached_animation_name_animation(JS::GCPtr<Animations::Animation> value) { m_cached_animation_name_animation = value; }
+
 private:
     Vector<JS::NonnullGCPtr<AnimationEffect>> m_associated_effects;
     bool m_is_sorted_by_composite_order { true };
     JS::GCPtr<CSS::CSSStyleDeclaration const> m_cached_animation_name_source;
+    JS::GCPtr<Animations::Animation> m_cached_animation_name_animation;
 };
 
 }

+ 100 - 76
Userland/Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -959,6 +959,72 @@ ErrorOr<void> StyleComputer::collect_animation_into(JS::NonnullGCPtr<Animations:
     return {};
 }
 
+static void apply_animation_properties(DOM::Document& document, StyleProperties& style, Animations::Animation& animation)
+{
+    auto& effect = verify_cast<Animations::KeyframeEffect>(*animation.effect());
+
+    Optional<CSS::Time> duration;
+    if (auto duration_value = style.maybe_null_property(PropertyID::AnimationDuration); duration_value) {
+        if (duration_value->is_time()) {
+            duration = duration_value->as_time().time();
+        } else if (duration_value->is_identifier() && duration_value->as_identifier().id() == ValueID::Auto) {
+            // We use empty optional to represent "auto".
+            duration = {};
+        }
+    }
+
+    CSS::Time delay { 0, CSS::Time::Type::S };
+    if (auto delay_value = style.maybe_null_property(PropertyID::AnimationDelay); delay_value && delay_value->is_time())
+        delay = delay_value->as_time().time();
+
+    double iteration_count = 1.0;
+    if (auto iteration_count_value = style.maybe_null_property(PropertyID::AnimationIterationCount); iteration_count_value) {
+        if (iteration_count_value->is_identifier() && iteration_count_value->to_identifier() == ValueID::Infinite)
+            iteration_count = HUGE_VAL;
+        else if (iteration_count_value->is_number())
+            iteration_count = iteration_count_value->as_number().number();
+    }
+
+    CSS::AnimationFillMode fill_mode { CSS::AnimationFillMode::None };
+    if (auto fill_mode_property = style.maybe_null_property(PropertyID::AnimationFillMode); fill_mode_property && fill_mode_property->is_identifier()) {
+        if (auto fill_mode_value = value_id_to_animation_fill_mode(fill_mode_property->to_identifier()); fill_mode_value.has_value())
+            fill_mode = *fill_mode_value;
+    }
+
+    CSS::AnimationDirection direction { CSS::AnimationDirection::Normal };
+    if (auto direction_property = style.maybe_null_property(PropertyID::AnimationDirection); direction_property && direction_property->is_identifier()) {
+        if (auto direction_value = value_id_to_animation_direction(direction_property->to_identifier()); direction_value.has_value())
+            direction = *direction_value;
+    }
+
+    CSS::AnimationPlayState play_state { CSS::AnimationPlayState::Running };
+    if (auto play_state_property = style.maybe_null_property(PropertyID::AnimationPlayState); play_state_property && play_state_property->is_identifier()) {
+        if (auto play_state_value = value_id_to_animation_play_state(play_state_property->to_identifier()); play_state_value.has_value())
+            play_state = *play_state_value;
+    }
+
+    Animations::TimingFunction timing_function = Animations::ease_timing_function;
+    if (auto timing_property = style.maybe_null_property(PropertyID::AnimationTimingFunction); timing_property && timing_property->is_easing())
+        timing_function = Animations::TimingFunction::from_easing_style_value(timing_property->as_easing());
+
+    auto iteration_duration = duration.has_value()
+        ? Variant<double, String> { 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));
+
+    HTML::TemporaryExecutionContext context(document.relevant_settings_object());
+    if (play_state == CSS::AnimationPlayState::Running && animation.play_state() != Bindings::AnimationPlayState::Running) {
+        animation.play().release_value_but_fixme_should_propagate_errors();
+    } else if (play_state == CSS::AnimationPlayState::Paused && animation.play_state() != Bindings::AnimationPlayState::Paused) {
+        animation.pause().release_value_but_fixme_should_propagate_errors();
+    }
+}
+
 // https://www.w3.org/TR/css-cascade/#cascading
 ErrorOr<void> StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode) const
 {
@@ -1021,84 +1087,42 @@ ErrorOr<void> StyleComputer::compute_cascaded_values(StyleProperties& style, DOM
     }();
 
     if (animation_name.has_value()) {
-        if (auto source_declaration = style.property_source_declaration(PropertyID::AnimationName); source_declaration && source_declaration != element.cached_animation_name_source()) {
-            // This animation name is new, so we need to create a new animation for it.
-            element.set_cached_animation_name_source(source_declaration);
-
-            Optional<CSS::Time> duration;
-            if (auto duration_value = style.maybe_null_property(PropertyID::AnimationDuration); duration_value) {
-                if (duration_value->is_time()) {
-                    duration = duration_value->as_time().time();
-                } else if (duration_value->is_identifier() && duration_value->as_identifier().id() == ValueID::Auto) {
-                    // We use empty optional to represent "auto".
-                    duration = {};
-                }
-            }
-
-            CSS::Time delay { 0, CSS::Time::Type::S };
-            if (auto delay_value = style.maybe_null_property(PropertyID::AnimationDelay); delay_value && delay_value->is_time())
-                delay = delay_value->as_time().time();
-
-            double iteration_count = 1.0;
-            if (auto iteration_count_value = style.maybe_null_property(PropertyID::AnimationIterationCount); iteration_count_value) {
-                if (iteration_count_value->is_identifier() && iteration_count_value->to_identifier() == ValueID::Infinite)
-                    iteration_count = HUGE_VAL;
-                else if (iteration_count_value->is_number())
-                    iteration_count = iteration_count_value->as_number().number();
-            }
-
-            CSS::AnimationFillMode fill_mode { CSS::AnimationFillMode::None };
-            if (auto fill_mode_property = style.maybe_null_property(PropertyID::AnimationFillMode); fill_mode_property && fill_mode_property->is_identifier()) {
-                if (auto fill_mode_value = value_id_to_animation_fill_mode(fill_mode_property->to_identifier()); fill_mode_value.has_value())
-                    fill_mode = *fill_mode_value;
-            }
-
-            CSS::AnimationDirection direction { CSS::AnimationDirection::Normal };
-            if (auto direction_property = style.maybe_null_property(PropertyID::AnimationDirection); direction_property && direction_property->is_identifier()) {
-                if (auto direction_value = value_id_to_animation_direction(direction_property->to_identifier()); direction_value.has_value())
-                    direction = *direction_value;
-            }
+        if (auto source_declaration = style.property_source_declaration(PropertyID::AnimationName); source_declaration) {
+            auto& realm = element.realm();
 
-            CSS::AnimationPlayState play_state { CSS::AnimationPlayState::Running };
-            if (auto play_state_property = style.maybe_null_property(PropertyID::AnimationPlayState); play_state_property && play_state_property->is_identifier()) {
-                if (auto play_state_value = value_id_to_animation_play_state(play_state_property->to_identifier()); play_state_value.has_value())
-                    play_state = *play_state_value;
+            if (source_declaration != element.cached_animation_name_source()) {
+                // This animation name is new, so we need to create a new animation for it.
+                if (auto existing_animation = element.cached_animation_name_animation())
+                    existing_animation->cancel();
+                element.set_cached_animation_name_source(source_declaration);
+
+                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, style, animation);
+                if (pseudo_element.has_value())
+                    effect->set_pseudo_element(Selector::PseudoElement { pseudo_element.value() });
+
+                auto const& 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());
+
+                element.associate_with_effect(effect);
+                element.set_cached_animation_name_animation(animation);
+            } else {
+                // The animation hasn't changed, but some properties of the animation may have
+                apply_animation_properties(m_document, style, *element.cached_animation_name_animation());
             }
-
-            Animations::TimingFunction timing_function = Animations::ease_timing_function;
-            if (auto timing_property = style.maybe_null_property(PropertyID::AnimationTimingFunction); timing_property && timing_property->is_easing())
-                timing_function = Animations::TimingFunction::from_easing_style_value(timing_property->as_easing());
-
-            auto& realm = element.realm();
-            auto effect = Animations::KeyframeEffect::create(realm);
-            auto iteration_duration = duration.has_value()
-                ? Variant<double, String> { 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));
-            if (pseudo_element.has_value())
-                effect->set_pseudo_element(Selector::PseudoElement { pseudo_element.value() });
-
-            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);
-
-            auto const& 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());
-
-            element.associate_with_effect(effect);
-
-            HTML::TemporaryExecutionContext context(m_document->relevant_settings_object());
-            animation->play().release_value_but_fixme_should_propagate_errors();
-            if (play_state == CSS::AnimationPlayState::Paused)
-                animation->pause().release_value_but_fixme_should_propagate_errors();
+        }
+    } else {
+        // If the element had an existing animation, cancel it
+        if (auto existing_animation = element.cached_animation_name_animation()) {
+            existing_animation->cancel();
+            element.set_cached_animation_name_animation({});
+            element.set_cached_animation_name_source({});
         }
     }