From 10fddb99fc822bafbc41ae35dcf5e20b362febac Mon Sep 17 00:00:00 2001 From: Matthew Olsson Date: Sat, 3 Feb 2024 18:21:29 -0700 Subject: [PATCH] LibWeb: Implement Document::remove_replaced_animations() --- .../Libraries/LibWeb/Animations/Animation.cpp | 14 +++ .../Libraries/LibWeb/Animations/Animation.h | 1 + .../LibWeb/Animations/AnimationEffect.h | 1 + .../LibWeb/Animations/KeyframeEffect.h | 2 + Userland/Libraries/LibWeb/DOM/Document.cpp | 87 ++++++++++++++++++- Userland/Libraries/LibWeb/HTML/EventNames.h | 1 + 6 files changed, 105 insertions(+), 1 deletion(-) diff --git a/Userland/Libraries/LibWeb/Animations/Animation.cpp b/Userland/Libraries/LibWeb/Animations/Animation.cpp index 3bac7d96a1e..523c4632096 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.cpp +++ b/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 Animation::play() { diff --git a/Userland/Libraries/LibWeb/Animations/Animation.h b/Userland/Libraries/LibWeb/Animations/Animation.h index 350a95953b9..f8f46f01edb 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.h +++ b/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; } diff --git a/Userland/Libraries/LibWeb/Animations/AnimationEffect.h b/Userland/Libraries/LibWeb/Animations/AnimationEffect.h index a0d9138327f..2dec4af9d00 100644 --- a/Userland/Libraries/LibWeb/Animations/AnimationEffect.h +++ b/Userland/Libraries/LibWeb/Animations/AnimationEffect.h @@ -142,6 +142,7 @@ public: Optional transformed_progress() const; virtual DOM::Element* target() const { return {}; } + virtual bool is_keyframe_effect() const { return false; } protected: AnimationEffect(JS::Realm&); diff --git a/Userland/Libraries/LibWeb/Animations/KeyframeEffect.h b/Userland/Libraries/LibWeb/Animations/KeyframeEffect.h index 1e10ed9efa0..968b50be37c 100644 --- a/Userland/Libraries/LibWeb/Animations/KeyframeEffect.h +++ b/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 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; diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 0eebf513aca..bcacebf986a 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -3820,7 +3820,92 @@ void Document::update_animations_and_send_events(Optional 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> 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& a, JS::NonnullGCPtr& b) { + VERIFY(a->effect()->is_keyframe_effect()); + VERIFY(b->effect()->is_keyframe_effect()); + auto& a_effect = *static_cast(a->effect().ptr()); + auto& b_effect = *static_cast(b->effect().ptr()); + return Animations::KeyframeEffect::composite_order(a_effect, b_effect) < 0; + }); + + // Lower value = higher priority + HashMap 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 diff --git a/Userland/Libraries/LibWeb/HTML/EventNames.h b/Userland/Libraries/LibWeb/HTML/EventNames.h index e45b95a5e22..cbf05baeb29 100644 --- a/Userland/Libraries/LibWeb/HTML/EventNames.h +++ b/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) \