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) \