diff --git a/Userland/Libraries/LibWeb/Animations/Animation.cpp b/Userland/Libraries/LibWeb/Animations/Animation.cpp index 33cfd8d7b04..4bcd675560f 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.cpp +++ b/Userland/Libraries/LibWeb/Animations/Animation.cpp @@ -359,6 +359,66 @@ void Animation::set_replace_state(Bindings::AnimationReplaceState value) } } +// https://www.w3.org/TR/web-animations-1/#dom-animation-cancel +void Animation::cancel() +{ + auto& realm = this->realm(); + + // 1. If animation’s play state is not idle, perform the following steps: + if (play_state() != Bindings::AnimationPlayState::Idle) { + HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm) }; + + // 1. Run the procedure to reset an animation’s pending tasks on animation. + reset_an_animations_pending_tasks(); + + // 2. Reject the current finished promise with a DOMException named "AbortError". + auto dom_exception = WebIDL::AbortError::create(realm, "Animation was cancelled"_fly_string); + WebIDL::reject_promise(realm, current_finished_promise(), dom_exception); + + // 3. Set the [[PromiseIsHandled]] internal slot of the current finished promise to true. + WebIDL::mark_promise_as_handled(current_finished_promise()); + + // 4. Let current finished promise be a new promise in the relevant Realm of animation. + m_current_finished_promise = WebIDL::create_promise(realm); + + // 5. Create an AnimationPlaybackEvent, cancelEvent. + // 6. Set cancelEvent’s type attribute to cancel. + // 7. Set cancelEvent’s currentTime to null. + // 8. Let timeline time be the current time of the timeline with which animation is associated. If animation is + // not associated with an active timeline, let timeline time be an unresolved time value. + // 9. Set cancelEvent’s timelineTime to timeline time. If timeline time is unresolved, set it to null. + AnimationPlaybackEventInit init; + init.timeline_time = m_timeline && !m_timeline->is_inactive() ? m_timeline->current_time() : Optional {}; + auto cancel_event = AnimationPlaybackEvent::create(realm, HTML::EventNames::cancel, init); + + // 10. If animation has a document for timing, then append cancelEvent to its document for timing's pending + // animation event queue along with its target, animation. If animation is associated with an active + // timeline that defines a procedure to convert timeline times to origin-relative time, let the scheduled + // event time be the result of applying that procedure to timeline time. Otherwise, the scheduled event time + // is an unresolved time value. + // Otherwise, queue a task to dispatch cancelEvent at animation. The task source for this task is the DOM + // manipulation task source. + if (auto document = document_for_timing()) { + Optional scheduled_event_time; + if (m_timeline && !m_timeline->is_inactive() && m_timeline->can_convert_a_timeline_time_to_an_origin_relative_time()) + scheduled_event_time = m_timeline->convert_a_timeline_time_to_an_origin_relative_time(m_timeline->current_time()); + document->append_pending_animation_event({ cancel_event, *this, scheduled_event_time }); + } else { + HTML::queue_global_task(HTML::Task::Source::DOMManipulation, realm.global_object(), [this, cancel_event]() { + dispatch_event(cancel_event); + }); + } + } + + // 2. Make animation’s hold time unresolved. + m_hold_time = {}; + + // 3. Make animation’s start time unresolved. + m_start_time = {}; + + invalidate_effect(); +} + // https://www.w3.org/TR/web-animations-1/#dom-animation-finish WebIDL::ExceptionOr Animation::finish() { @@ -961,6 +1021,36 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync invalidate_effect(); } +// https://www.w3.org/TR/web-animations-1/#animation-reset-an-animations-pending-tasks +void Animation::reset_an_animations_pending_tasks() +{ + auto& realm = this->realm(); + + // 1. If animation does not have a pending play task or a pending pause task, abort this procedure. + if (!pending()) + return; + + // 2. If animation has a pending play task, cancel that task. + m_pending_play_task = TaskState::None; + + // 3. If animation has a pending pause task, cancel that task. + m_pending_pause_task = TaskState::None; + + // 4. Apply any pending playback rate on animation. + apply_any_pending_playback_rate(); + + // 5. Reject animation’s current ready promise with a DOMException named "AbortError". + auto dom_exception = WebIDL::AbortError::create(realm, "Animation was cancelled"_fly_string); + WebIDL::reject_promise(realm, current_ready_promise(), dom_exception); + + // 6. Set the [[PromiseIsHandled]] internal slot of animation’s current ready promise to true. + WebIDL::mark_promise_as_handled(current_ready_promise()); + + // 7. Let animation’s current ready promise be the result of creating a new resolved Promise object with value + // animation in the relevant Realm of animation. + m_current_ready_promise = WebIDL::create_resolved_promise(realm, this); +} + // Step 12 of https://www.w3.org/TR/web-animations-1/#playing-an-animation-section void Animation::run_pending_play_task() { diff --git a/Userland/Libraries/LibWeb/Animations/Animation.h b/Userland/Libraries/LibWeb/Animations/Animation.h index c8950e0f179..3d483ff94f5 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.h +++ b/Userland/Libraries/LibWeb/Animations/Animation.h @@ -70,6 +70,7 @@ public: Yes, No, }; + void cancel(); WebIDL::ExceptionOr finish(); WebIDL::ExceptionOr play(); WebIDL::ExceptionOr play_an_animation(AutoRewind); @@ -118,6 +119,7 @@ private: void apply_any_pending_playback_rate(); WebIDL::ExceptionOr silently_set_current_time(Optional); void update_finished_state(DidSeek, SynchronouslyNotify); + void reset_an_animations_pending_tasks(); void run_pending_play_task(); void run_pending_pause_task(); diff --git a/Userland/Libraries/LibWeb/Animations/Animation.idl b/Userland/Libraries/LibWeb/Animations/Animation.idl index a73ffe557d7..662e2e2ac17 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.idl +++ b/Userland/Libraries/LibWeb/Animations/Animation.idl @@ -24,7 +24,7 @@ interface Animation : EventTarget { // FIXME: attribute EventHandler oncancel; // FIXME: attribute EventHandler onremove; - // FIXME: undefined cancel(); + undefined cancel(); undefined finish(); undefined play(); undefined pause();