From 727a9a647254ed7916be4849c0179c2aa4411142 Mon Sep 17 00:00:00 2001 From: Matthew Olsson Date: Fri, 2 Feb 2024 15:01:30 -0700 Subject: [PATCH] LibWeb: Run pending play tasks when the timeline time changes Note that the timeline time changes every animation frame when the Document sends out animation events --- .../Libraries/LibWeb/Animations/Animation.cpp | 88 +++++++++++++++++++ .../Libraries/LibWeb/Animations/Animation.h | 4 + .../LibWeb/Animations/AnimationTimeline.cpp | 10 +-- .../LibWeb/Animations/AnimationTimeline.h | 2 +- .../LibWeb/Animations/DocumentTimeline.cpp | 8 +- .../LibWeb/Animations/DocumentTimeline.h | 2 +- 6 files changed, 101 insertions(+), 13 deletions(-) diff --git a/Userland/Libraries/LibWeb/Animations/Animation.cpp b/Userland/Libraries/LibWeb/Animations/Animation.cpp index 7310ec1b95..64d6252594 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.cpp +++ b/Userland/Libraries/LibWeb/Animations/Animation.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -489,6 +490,23 @@ JS::GCPtr Animation::document_for_timing() const return m_timeline->associated_document(); } +void Animation::notify_timeline_time_did_change() +{ + update_finished_state(DidSeek::No, SynchronouslyNotify::Yes); + + // Act on the pending play or pause task + if (m_pending_pause_task == TaskState::Scheduled) { + m_pending_pause_task = TaskState::None; + // FIXME: + // run_pending_pause_task(); + } + + if (m_pending_play_task == TaskState::Scheduled) { + m_pending_play_task = TaskState::None; + run_pending_play_task(); + } +} + // https://www.w3.org/TR/web-animations-1/#associated-effect-end double Animation::associated_effect_end() const { @@ -725,6 +743,76 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync } } +// Step 12 of https://www.w3.org/TR/web-animations-1/#playing-an-animation-section +void Animation::run_pending_play_task() +{ + // 1. Assert that at least one of animation’s start time or hold time is resolved. + VERIFY(m_start_time.has_value() || m_hold_time.has_value()); + + // 2. Let ready time be the time value of the timeline associated with animation at the moment when animation became + // ready. + // FIXME: Ideally we would save the time before the update_finished_state call in play_an_animation() and use that + // as the ready time, as step 2 indicates, however at that point the timeline will not have had it's current + // time updated by the Document, so the time we would save would be incorrect if there are no other + // animations running. + auto ready_time = m_timeline->current_time(); + + // Note: The timeline being active is a precondition for this method to be called + VERIFY(ready_time.has_value()); + + // 3. Perform the steps corresponding to the first matching condition below, if any: + + // -> If animation’s hold time is resolved, + if (m_hold_time.has_value()) { + // 1. Apply any pending playback rate on animation. + apply_any_pending_playback_rate(); + + // 2. Let new start time be the result of evaluating ready time - hold time / playback rate for animation. If + // the playback rate is zero, let new start time be simply ready time. + auto new_start_time = m_playback_rate != 0.0 ? ready_time.value() - (m_hold_time.value() / m_playback_rate) : ready_time; + + // 3. Set the start time of animation to new start time. + m_start_time = new_start_time; + + // 4. If animation’s playback rate is not 0, make animation’s hold time unresolved. + if (m_playback_rate != 0.0) + m_hold_time = {}; + } + // -> If animation’s start time is resolved and animation has a pending playback rate, + else if (m_start_time.has_value() && m_pending_playback_rate.has_value()) { + // 1. Let current time to match be the result of evaluating (ready time - start time) × playback rate for + // animation. + auto current_time_to_match = (ready_time.value() - m_start_time.value()) * m_playback_rate; + + // 2. Apply any pending playback rate on animation. + apply_any_pending_playback_rate(); + + // 3. If animation’s playback rate is zero, let animation’s hold time be current time to match. + if (m_playback_rate == 0.0) + m_hold_time = current_time_to_match; + + // 4. Let new start time be the result of evaluating ready time - current time to match / playback rate for + // animation. If the playback rate is zero, let new start time be simply ready time. + auto new_start_time = m_playback_rate != 0.0 ? ready_time.value() - (current_time_to_match / m_playback_rate) : ready_time; + + // 5. Set the start time of animation to new start time. + m_start_time = new_start_time; + } + + // 4. Resolve animation’s current ready promise with animation. + HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm()) }; + WebIDL::resolve_promise(realm(), current_ready_promise(), this); + + // 5. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false, + // and the synchronously notify flag set to false. + update_finished_state(DidSeek::No, SynchronouslyNotify::No); +} + +void Animation::run_pending_pause_task() +{ + // FIXME: Implement +} + JS::NonnullGCPtr Animation::current_ready_promise() const { if (!m_current_ready_promise) { diff --git a/Userland/Libraries/LibWeb/Animations/Animation.h b/Userland/Libraries/LibWeb/Animations/Animation.h index b1611478e5..1b07584ec9 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.h +++ b/Userland/Libraries/LibWeb/Animations/Animation.h @@ -63,6 +63,7 @@ public: Optional convert_a_timeline_time_to_an_origin_relative_time(Optional) const; JS::GCPtr document_for_timing() const; + void notify_timeline_time_did_change(); protected: Animation(JS::Realm&); @@ -93,6 +94,9 @@ private: WebIDL::ExceptionOr silently_set_current_time(Optional); void update_finished_state(DidSeek, SynchronouslyNotify); + void run_pending_play_task(); + void run_pending_pause_task(); + JS::NonnullGCPtr current_ready_promise() const; JS::NonnullGCPtr current_finished_promise() const; diff --git a/Userland/Libraries/LibWeb/Animations/AnimationTimeline.cpp b/Userland/Libraries/LibWeb/Animations/AnimationTimeline.cpp index 4b6166b516..54ee26022b 100644 --- a/Userland/Libraries/LibWeb/Animations/AnimationTimeline.cpp +++ b/Userland/Libraries/LibWeb/Animations/AnimationTimeline.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Matthew Olsson . + * Copyright (c) 2023-2024, Matthew Olsson . * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,10 +12,10 @@ namespace Web::Animations { JS_DEFINE_ALLOCATOR(AnimationTimeline); -WebIDL::ExceptionOr AnimationTimeline::set_current_time(Optional value) +void AnimationTimeline::set_current_time(Optional value) { if (value == m_current_time) - return {}; + return; if (m_is_monotonically_increasing && m_current_time.has_value()) { if (!value.has_value() || value.value() < m_current_time.value()) @@ -25,9 +25,7 @@ WebIDL::ExceptionOr AnimationTimeline::set_current_time(Optional v m_current_time = value; for (auto& animation : m_associated_animations) - TRY(animation->set_current_time(value)); - - return {}; + animation->notify_timeline_time_did_change(); } void AnimationTimeline::set_associated_document(JS::GCPtr document) diff --git a/Userland/Libraries/LibWeb/Animations/AnimationTimeline.h b/Userland/Libraries/LibWeb/Animations/AnimationTimeline.h index 7e3a4cfac0..0926045f18 100644 --- a/Userland/Libraries/LibWeb/Animations/AnimationTimeline.h +++ b/Userland/Libraries/LibWeb/Animations/AnimationTimeline.h @@ -17,7 +17,7 @@ class AnimationTimeline : public Bindings::PlatformObject { public: Optional current_time() const { return m_current_time; } - virtual WebIDL::ExceptionOr set_current_time(Optional); + virtual void set_current_time(Optional); JS::GCPtr associated_document() const { return m_associated_document; } void set_associated_document(JS::GCPtr); diff --git a/Userland/Libraries/LibWeb/Animations/DocumentTimeline.cpp b/Userland/Libraries/LibWeb/Animations/DocumentTimeline.cpp index ff8d72d5c2..a9f43730bf 100644 --- a/Userland/Libraries/LibWeb/Animations/DocumentTimeline.cpp +++ b/Userland/Libraries/LibWeb/Animations/DocumentTimeline.cpp @@ -41,21 +41,19 @@ Optional DocumentTimeline::convert_a_timeline_time_to_an_origin_relative } // https://www.w3.org/TR/web-animations-1/#origin-time -WebIDL::ExceptionOr DocumentTimeline::set_current_time(Optional current_time) +void DocumentTimeline::set_current_time(Optional current_time) { // A document timeline is a type of timeline that is associated with a document and whose current time is calculated // as a fixed offset from the now timestamp provided each time the update animations and send events procedure is // run. This fixed offset is referred to as the document timeline’s origin time. if (!current_time.has_value()) - TRY(Base::set_current_time({})); + Base::set_current_time({}); else - TRY(Base::set_current_time(current_time.value() - m_origin_time)); + Base::set_current_time(current_time.value() - m_origin_time); // After a document timeline becomes active, it is monotonically increasing. if (!is_inactive()) VERIFY(is_monotonically_increasing()); - - return {}; } // https://www.w3.org/TR/web-animations-1/#document-timelines diff --git a/Userland/Libraries/LibWeb/Animations/DocumentTimeline.h b/Userland/Libraries/LibWeb/Animations/DocumentTimeline.h index 590d3821da..d63571db8d 100644 --- a/Userland/Libraries/LibWeb/Animations/DocumentTimeline.h +++ b/Userland/Libraries/LibWeb/Animations/DocumentTimeline.h @@ -26,7 +26,7 @@ public: static JS::NonnullGCPtr create(JS::Realm&, DOM::Document&, HighResolutionTime::DOMHighResTimeStamp origin_time); static WebIDL::ExceptionOr> construct_impl(JS::Realm&, DocumentTimelineOptions options = {}); - virtual WebIDL::ExceptionOr set_current_time(Optional current_time) override; + virtual void set_current_time(Optional current_time) override; virtual bool is_inactive() const override; virtual Optional convert_a_timeline_time_to_an_origin_relative_time(Optional) override;