From 06a8674eec6ca72d4e11aab5aa6bc5088c4da24e Mon Sep 17 00:00:00 2001 From: Matthew Olsson Date: Sat, 10 Feb 2024 20:52:58 -0700 Subject: [PATCH] LibWeb: Simplify Animation::update_finished_state a bit This removes the two boolean hack in favor of using the existing mechanism to remove queued tasks. It also exposes the element invalidation behavior for call sites that don't necessarily want to update the finished state, but still need to invalidate the associated target. --- .../Libraries/LibWeb/Animations/Animation.cpp | 60 ++++++++++--------- .../Libraries/LibWeb/Animations/Animation.h | 7 +-- Userland/Libraries/LibWeb/HTML/EventNames.h | 1 + 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/Userland/Libraries/LibWeb/Animations/Animation.cpp b/Userland/Libraries/LibWeb/Animations/Animation.cpp index 338d607af6..223802197b 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.cpp +++ b/Userland/Libraries/LibWeb/Animations/Animation.cpp @@ -630,6 +630,8 @@ WebIDL::ExceptionOr Animation::silently_set_current_time(Optional // https://www.w3.org/TR/web-animations-1/#update-an-animations-finished-state void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify synchronously_notify) { + auto& realm = this->realm(); + // 1. Let the unconstrained current time be the result of calculating the current time substituting an unresolved // time value for the hold time if did seek is false. If did seek is true, the unconstrained current time is // equal to the current time. @@ -706,30 +708,23 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync if (current_finished_state && !m_is_finished) { // 1. Let finish notification steps refer to the following procedure: JS::SafeFunction finish_notification_steps = [&]() { - if (m_should_abort_finish_notification_microtask) { - m_should_abort_finish_notification_microtask = false; - m_has_finish_notification_microtask_scheduled = false; - return; - } - // 1. If animation’s play state is not equal to finished, abort these steps. if (play_state() != Bindings::AnimationPlayState::Finished) return; // 2. Resolve animation’s current finished promise object with animation. { - HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm()) }; - WebIDL::resolve_promise(realm(), current_finished_promise(), this); + HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm) }; + WebIDL::resolve_promise(realm, current_finished_promise(), this); } m_is_finished = true; // 3. Create an AnimationPlaybackEvent, finishEvent. // 4. Set finishEvent’s type attribute to finish. // 5. Set finishEvent’s currentTime attribute to the current time of animation. - auto& realm = this->realm(); AnimationPlaybackEventInit init; init.current_time = current_time(); - auto finish_event = AnimationPlaybackEvent::create(realm, "finish"_fly_string, init); + auto finish_event = AnimationPlaybackEvent::create(realm, HTML::EventNames::finish, init); // 6. Set finishEvent’s timelineTime attribute to the current time of the timeline with which animation is // associated. If animation is not associated with a timeline, or the timeline is inactive, let @@ -750,44 +745,47 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync // Otherwise, queue a task to dispatch finishEvent 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(), [this, finish_event]() { + // Manually create a task so its ID can be saved + auto& document = verify_cast(realm.global_object()).associated_document(); + auto task = HTML::Task::create(HTML::Task::Source::DOMManipulation, &document, [this, finish_event]() { dispatch_event(finish_event); }); + m_pending_finish_microtask_id = task->id(); + HTML::main_thread_event_loop().task_queue().add(move(task)); } - - m_has_finish_notification_microtask_scheduled = false; }; // 2. If synchronously notify is true, cancel any queued microtask to run the finish notification steps for this // animation, and run the finish notification steps immediately. if (synchronously_notify == SynchronouslyNotify::Yes) { - m_should_abort_finish_notification_microtask = false; + if (m_pending_finish_microtask_id.has_value()) { + HTML::main_thread_event_loop().task_queue().remove_tasks_matching([id = move(m_pending_finish_microtask_id)](auto const& task) { + return task.id() == id; + }); + } finish_notification_steps(); - m_should_abort_finish_notification_microtask = true; } // Otherwise, if synchronously notify is false, queue a microtask to run finish notification steps for // animation unless there is already a microtask queued to run those steps for animation. - else { - if (!m_has_finish_notification_microtask_scheduled) - HTML::queue_a_microtask({}, move(finish_notification_steps)); - - m_has_finish_notification_microtask_scheduled = true; - m_should_abort_finish_notification_microtask = false; + else if (!m_pending_finish_microtask_id.has_value()) { + auto& document = verify_cast(realm.global_object()).associated_document(); + auto task = HTML::Task::create(HTML::Task::Source::DOMManipulation, &document, move(finish_notification_steps)); + m_pending_finish_microtask_id = task->id(); + HTML::main_thread_event_loop().task_queue().add(move(task)); } } // 6. If current finished state is false and animation’s current finished promise is already resolved, set // animation’s current finished promise to a new promise in the relevant Realm of animation. if (!current_finished_state && m_is_finished) { - m_current_finished_promise = WebIDL::create_promise(realm()); + { + HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm) }; + m_current_finished_promise = WebIDL::create_promise(realm); + } m_is_finished = false; } - // Invalidate the style of our target element, if applicable - if (m_effect) { - if (auto target = m_effect->target()) - target->invalidate_style(); - } + invalidate_effect(); } // Step 12 of https://www.w3.org/TR/web-animations-1/#playing-an-animation-section @@ -881,6 +879,14 @@ JS::NonnullGCPtr Animation::current_finished_promise() const return *m_current_finished_promise; } +void Animation::invalidate_effect() +{ + if (m_effect) { + if (auto target = m_effect->target()) + target->invalidate_style(); + } +} + Animation::Animation(JS::Realm& realm) : DOM::EventTarget(realm) { diff --git a/Userland/Libraries/LibWeb/Animations/Animation.h b/Userland/Libraries/LibWeb/Animations/Animation.h index 6e11f823cc..5c5b999656 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.h +++ b/Userland/Libraries/LibWeb/Animations/Animation.h @@ -120,6 +120,8 @@ private: JS::NonnullGCPtr current_ready_promise() const; JS::NonnullGCPtr current_finished_promise() const; + void invalidate_effect(); + // https://www.w3.org/TR/web-animations-1/#dom-animation-id FlyString m_id; @@ -164,10 +166,7 @@ private: // https://www.w3.org/TR/web-animations-1/#pending-pause-task TaskState m_pending_pause_task { TaskState::None }; - // Flags used to manage the finish notification microtask and ultimately prevent more than one finish notification - // microtask from being queued at any given time - bool m_should_abort_finish_notification_microtask { false }; - bool m_has_finish_notification_microtask_scheduled { false }; + Optional m_pending_finish_microtask_id; }; } diff --git a/Userland/Libraries/LibWeb/HTML/EventNames.h b/Userland/Libraries/LibWeb/HTML/EventNames.h index 91329aa490..e45b95a5e2 100644 --- a/Userland/Libraries/LibWeb/HTML/EventNames.h +++ b/Userland/Libraries/LibWeb/HTML/EventNames.h @@ -49,6 +49,7 @@ namespace Web::HTML::EventNames { __ENUMERATE_HTML_EVENT(emptied) \ __ENUMERATE_HTML_EVENT(ended) \ __ENUMERATE_HTML_EVENT(error) \ + __ENUMERATE_HTML_EVENT(finish) \ __ENUMERATE_HTML_EVENT(focus) \ __ENUMERATE_HTML_EVENT(formdata) \ __ENUMERATE_HTML_EVENT(hashchange) \