Kaynağa Gözat

LibWeb: Implement Document::remove_replaced_animations()

Matthew Olsson 1 yıl önce
ebeveyn
işleme
10fddb99fc

+ 14 - 0
Userland/Libraries/LibWeb/Animations/Animation.cpp

@@ -345,6 +345,20 @@ bool Animation::is_replaceable() const
     return true;
 }
 
+void Animation::set_replace_state(Bindings::AnimationReplaceState value)
+{
+    m_replace_state = value;
+
+    if (value == Bindings::AnimationReplaceState::Removed) {
+        // Remove the associated effect from its target, if applicable
+        if (m_effect && m_effect->target())
+            m_effect->target()->disassociate_with_effect(*m_effect);
+
+        // Remove this animation from its timeline
+        m_timeline->disassociate_with_animation(*this);
+    }
+}
+
 // https://www.w3.org/TR/web-animations-1/#dom-animation-play
 WebIDL::ExceptionOr<void> Animation::play()
 {

+ 1 - 0
Userland/Libraries/LibWeb/Animations/Animation.h

@@ -54,6 +54,7 @@ public:
 
     bool is_replaceable() const;
     Bindings::AnimationReplaceState replace_state() const { return m_replace_state; }
+    void set_replace_state(Bindings::AnimationReplaceState value);
 
     // https://www.w3.org/TR/web-animations-1/#dom-animation-pending
     bool pending() const { return m_pending_play_task == TaskState::Scheduled || m_pending_pause_task == TaskState::Scheduled; }

+ 1 - 0
Userland/Libraries/LibWeb/Animations/AnimationEffect.h

@@ -142,6 +142,7 @@ public:
     Optional<double> transformed_progress() const;
 
     virtual DOM::Element* target() const { return {}; }
+    virtual bool is_keyframe_effect() const { return false; }
 
 protected:
     AnimationEffect(JS::Realm&);

+ 2 - 0
Userland/Libraries/LibWeb/Animations/KeyframeEffect.h

@@ -96,6 +96,8 @@ public:
     KeyFrameSet const* key_frame_set() { return m_key_frame_set; }
     void set_key_frame_set(RefPtr<KeyFrameSet const> key_frame_set) { m_key_frame_set = key_frame_set; }
 
+    virtual bool is_keyframe_effect() const override { return true; }
+
 private:
     KeyframeEffect(JS::Realm&);
     virtual ~KeyframeEffect() override;

+ 86 - 1
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -3820,7 +3820,92 @@ void Document::update_animations_and_send_events(Optional<double> const& timesta
 // https://www.w3.org/TR/web-animations-1/#remove-replaced-animations
 void Document::remove_replaced_animations()
 {
-    // FIXME: Implement this
+    // When asked to remove replaced animations for a Document, doc, then for every animation, animation, that:
+    // - has an associated animation effect whose effect target is a descendant of doc, and
+    // - is replaceable, and
+    // - has a replace state of active, and
+    // - for which there exists for each target property of every animation effect associated with animation, an
+    //   animation effect associated with a replaceable animation with a higher composite order than animation that
+    //   includes the same target property
+
+    Vector<JS::NonnullGCPtr<Animations::Animation>> replaceable_animations;
+    for (auto const& timeline : m_associated_animation_timelines) {
+        for (auto const& animation : timeline->associated_animations()) {
+            if (!animation->effect() || !animation->effect()->target() || &animation->effect()->target()->document() != this)
+                continue;
+
+            if (!animation->is_replaceable())
+                continue;
+
+            if (animation->replace_state() != Bindings::AnimationReplaceState::Active)
+                continue;
+
+            // Composite order is only defined for KeyframeEffects
+            if (!animation->effect()->is_keyframe_effect())
+                continue;
+
+            replaceable_animations.append(animation);
+        }
+    }
+
+    quick_sort(replaceable_animations, [](JS::NonnullGCPtr<Animations::Animation>& a, JS::NonnullGCPtr<Animations::Animation>& b) {
+        VERIFY(a->effect()->is_keyframe_effect());
+        VERIFY(b->effect()->is_keyframe_effect());
+        auto& a_effect = *static_cast<Animations::KeyframeEffect*>(a->effect().ptr());
+        auto& b_effect = *static_cast<Animations::KeyframeEffect*>(b->effect().ptr());
+        return Animations::KeyframeEffect::composite_order(a_effect, b_effect) < 0;
+    });
+
+    // Lower value = higher priority
+    HashMap<CSS::PropertyID, size_t> highest_property_composite_orders;
+    for (int i = replaceable_animations.size() - 1; i >= 0; i--) {
+        auto animation = replaceable_animations[i];
+        bool has_any_highest_priority_property = false;
+
+        for (auto const& property : animation->effect()->target_properties()) {
+            if (!highest_property_composite_orders.contains(property)) {
+                has_any_highest_priority_property = true;
+                highest_property_composite_orders.set(property, i);
+            }
+        }
+
+        if (!has_any_highest_priority_property) {
+            // perform the following steps:
+
+            // - Set animation’s replace state to removed.
+            animation->set_replace_state(Bindings::AnimationReplaceState::Removed);
+
+            // - Create an AnimationPlaybackEvent, removeEvent.
+            // - Set removeEvent’s type attribute to remove.
+            // - Set removeEvent’s currentTime attribute to the current time of animation.
+            // - Set removeEvent’s timelineTime attribute to the current time of the timeline with which animation is
+            //   associated.
+            Animations::AnimationPlaybackEventInit init;
+            init.current_time = animation->current_time();
+            init.timeline_time = animation->timeline()->current_time();
+            auto remove_event = Animations::AnimationPlaybackEvent::create(realm(), HTML::EventNames::remove, init);
+
+            // - If animation has a document for timing, then append removeEvent to its document for timing's pending
+            //   animation event queue along with its target, animation. For the scheduled event time, use the result of
+            //   applying the procedure to convert timeline time to origin-relative time to the current time of the
+            //   timeline with which animation is associated.
+            if (auto document = animation->document_for_timing()) {
+                PendingAnimationEvent pending_animation_event {
+                    remove_event,
+                    animation,
+                    animation->timeline()->convert_a_timeline_time_to_an_origin_relative_time(init.timeline_time),
+                };
+                document->append_pending_animation_event(pending_animation_event);
+            }
+            //   Otherwise, queue a task to dispatch removeEvent at animation. The task source for this task is the DOM
+            //   manipulation task source.
+            else {
+                HTML::queue_global_task(HTML::Task::Source::DOMManipulation, realm().global_object(), [animation, remove_event]() {
+                    animation->dispatch_event(remove_event);
+                });
+            }
+        }
+    }
 }
 
 // https://html.spec.whatwg.org/multipage/dom.html#dom-document-nameditem-filter

+ 1 - 0
Userland/Libraries/LibWeb/HTML/EventNames.h

@@ -81,6 +81,7 @@ namespace Web::HTML::EventNames {
     __ENUMERATE_HTML_EVENT(ratechange)               \
     __ENUMERATE_HTML_EVENT(readystatechange)         \
     __ENUMERATE_HTML_EVENT(rejectionhandled)         \
+    __ENUMERATE_HTML_EVENT(remove)                   \
     __ENUMERATE_HTML_EVENT(removetrack)              \
     __ENUMERATE_HTML_EVENT(reset)                    \
     __ENUMERATE_HTML_EVENT(resize)                   \