/src/serenity/Userland/Libraries/LibWeb/Animations/Animation.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2023-2024, Matthew Olsson <mattco@serenityos.org>. |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/TemporaryChange.h> |
8 | | #include <LibWeb/Animations/Animation.h> |
9 | | #include <LibWeb/Animations/AnimationEffect.h> |
10 | | #include <LibWeb/Animations/AnimationPlaybackEvent.h> |
11 | | #include <LibWeb/Animations/DocumentTimeline.h> |
12 | | #include <LibWeb/Bindings/AnimationPrototype.h> |
13 | | #include <LibWeb/Bindings/Intrinsics.h> |
14 | | #include <LibWeb/CSS/CSSAnimation.h> |
15 | | #include <LibWeb/DOM/Document.h> |
16 | | #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h> |
17 | | #include <LibWeb/HTML/Window.h> |
18 | | #include <LibWeb/HighResolutionTime/Performance.h> |
19 | | #include <LibWeb/Painting/Paintable.h> |
20 | | #include <LibWeb/WebIDL/ExceptionOr.h> |
21 | | #include <LibWeb/WebIDL/Promise.h> |
22 | | |
23 | | namespace Web::Animations { |
24 | | |
25 | | JS_DEFINE_ALLOCATOR(Animation); |
26 | | |
27 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-animation |
28 | | JS::NonnullGCPtr<Animation> Animation::create(JS::Realm& realm, JS::GCPtr<AnimationEffect> effect, Optional<JS::GCPtr<AnimationTimeline>> timeline) |
29 | 0 | { |
30 | | // 1. Let animation be a new Animation object. |
31 | 0 | auto animation = realm.heap().allocate<Animation>(realm, realm); |
32 | | |
33 | | // 2. Run the procedure to set the timeline of an animation on animation passing timeline as the new timeline or, if |
34 | | // a timeline argument is missing, passing the default document timeline of the Document associated with the |
35 | | // Window that is the current global object. |
36 | 0 | if (!timeline.has_value()) { |
37 | 0 | auto& window = verify_cast<HTML::Window>(HTML::current_global_object()); |
38 | 0 | timeline = window.associated_document().timeline(); |
39 | 0 | } |
40 | 0 | animation->set_timeline(timeline.release_value()); |
41 | | |
42 | | // 3. Run the procedure to set the associated effect of an animation on animation passing source as the new effect. |
43 | 0 | animation->set_effect(effect); |
44 | |
|
45 | 0 | return animation; |
46 | 0 | } |
47 | | |
48 | | WebIDL::ExceptionOr<JS::NonnullGCPtr<Animation>> Animation::construct_impl(JS::Realm& realm, JS::GCPtr<AnimationEffect> effect, Optional<JS::GCPtr<AnimationTimeline>> timeline) |
49 | 0 | { |
50 | 0 | return create(realm, effect, timeline); |
51 | 0 | } |
52 | | |
53 | | // https://www.w3.org/TR/web-animations-1/#animation-set-the-associated-effect-of-an-animation |
54 | | void Animation::set_effect(JS::GCPtr<AnimationEffect> new_effect) |
55 | 0 | { |
56 | | // Setting this attribute updates the object’s associated effect using the procedure to set the associated effect of |
57 | | // an animation. |
58 | | |
59 | | // 1. Let old effect be the current associated effect of animation, if any. |
60 | 0 | auto old_effect = m_effect; |
61 | | |
62 | | // 2. If new effect is the same object as old effect, abort this procedure. |
63 | 0 | if (new_effect == old_effect) |
64 | 0 | return; |
65 | | |
66 | | // 3. If animation has a pending pause task, reschedule that task to run as soon as animation is ready. |
67 | | // 4. If animation has a pending play task, reschedule that task to run as soon as animation is ready to play ne |
68 | | // effect. |
69 | | // Note: There is no real difference between "pending" and "as soon as possible", so this step is a no-op. |
70 | | |
71 | | // 5. If new effect is not null and if new effect is the associated effect of another animation, previous animation, |
72 | | // run the procedure to set the associated effect of an animation (this procedure) on previous animation passing |
73 | | // null as new effect. |
74 | 0 | if (new_effect && new_effect->associated_animation() != this) { |
75 | 0 | if (auto animation = new_effect->associated_animation()) |
76 | 0 | animation->set_effect({}); |
77 | 0 | } |
78 | | |
79 | | // 6. Let the associated effect of animation be new effect. |
80 | 0 | auto old_target = m_effect ? m_effect->target() : nullptr; |
81 | 0 | auto new_target = new_effect ? new_effect->target() : nullptr; |
82 | 0 | if (old_target != new_target) { |
83 | 0 | if (old_target) |
84 | 0 | old_target->disassociate_with_animation(*this); |
85 | 0 | if (new_target) |
86 | 0 | new_target->associate_with_animation(*this); |
87 | 0 | } |
88 | 0 | if (new_effect) |
89 | 0 | new_effect->set_associated_animation(this); |
90 | 0 | if (m_effect) |
91 | 0 | m_effect->set_associated_animation({}); |
92 | 0 | m_effect = new_effect; |
93 | | |
94 | | // 7. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false, |
95 | | // and the synchronously notify flag set to false. |
96 | 0 | update_finished_state(DidSeek::No, SynchronouslyNotify::No); |
97 | 0 | } |
98 | | |
99 | | // https://www.w3.org/TR/web-animations-1/#animation-set-the-timeline-of-an-animation |
100 | | void Animation::set_timeline(JS::GCPtr<AnimationTimeline> new_timeline) |
101 | 0 | { |
102 | | // Setting this attribute updates the object’s timeline using the procedure to set the timeline of an animation. |
103 | | |
104 | | // 1. Let old timeline be the current timeline of animation, if any. |
105 | 0 | auto old_timeline = m_timeline; |
106 | | |
107 | | // 2. If new timeline is the same object as old timeline, abort this procedure. |
108 | 0 | if (new_timeline == old_timeline) |
109 | 0 | return; |
110 | | |
111 | | // 3. Let the timeline of animation be new timeline. |
112 | 0 | if (m_timeline) |
113 | 0 | m_timeline->disassociate_with_animation(*this); |
114 | 0 | m_timeline = new_timeline; |
115 | 0 | m_timeline->associate_with_animation(*this); |
116 | | |
117 | | // 4. If the start time of animation is resolved, make animation’s hold time unresolved. |
118 | 0 | if (m_start_time.has_value()) |
119 | 0 | m_hold_time = {}; |
120 | | |
121 | | // 5. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false, |
122 | | // and the synchronously notify flag set to false. |
123 | 0 | update_finished_state(DidSeek::No, SynchronouslyNotify::No); |
124 | 0 | } |
125 | | |
126 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-starttime |
127 | | // https://www.w3.org/TR/web-animations-1/#set-the-start-time |
128 | | void Animation::set_start_time(Optional<double> const& new_start_time) |
129 | 0 | { |
130 | | // Setting this attribute updates the start time using the procedure to set the start time of this object to the new |
131 | | // value. |
132 | | |
133 | | // 1. Let timeline time be the current time value of the timeline that animation is associated with. If there is no |
134 | | // timeline associated with animation or the associated timeline is inactive, let the timeline time be |
135 | | // unresolved. |
136 | 0 | auto timeline_time = m_timeline && !m_timeline->is_inactive() ? m_timeline->current_time() : Optional<double> {}; |
137 | | |
138 | | // 2. If timeline time is unresolved and new start time is resolved, make animation’s hold time unresolved. |
139 | 0 | if (!timeline_time.has_value() && new_start_time.has_value()) |
140 | 0 | m_hold_time = {}; |
141 | | |
142 | | // 3. Let previous current time be animation’s current time. |
143 | 0 | auto previous_current_time = current_time(); |
144 | | |
145 | | // 4. Apply any pending playback rate on animation. |
146 | 0 | apply_any_pending_playback_rate(); |
147 | | |
148 | | // 5. Set animation’s start time to new start time. |
149 | 0 | m_start_time = new_start_time; |
150 | | |
151 | | // 6. Update animation’s hold time based on the first matching condition from the following, |
152 | | |
153 | | // -> If new start time is resolved, |
154 | 0 | if (new_start_time.has_value()) { |
155 | | // If animation’s playback rate is not zero, make animation’s hold time unresolved. |
156 | 0 | if (m_playback_rate != 0.0) |
157 | 0 | m_hold_time = {}; |
158 | 0 | } |
159 | | // -> Otherwise (new start time is unresolved), |
160 | 0 | else { |
161 | | // Set animation’s hold time to previous current time even if previous current time is unresolved. |
162 | 0 | m_hold_time = previous_current_time; |
163 | 0 | } |
164 | | |
165 | | // 7. If animation has a pending play task or a pending pause task, cancel that task and resolve animation’s current |
166 | | // ready promise with animation. |
167 | 0 | if (pending()) { |
168 | 0 | m_pending_play_task = TaskState::None; |
169 | 0 | m_pending_pause_task = TaskState::None; |
170 | 0 | WebIDL::resolve_promise(realm(), current_ready_promise(), this); |
171 | 0 | } |
172 | | |
173 | | // 8. Run the procedure to update an animation’s finished state for animation with the did seek flag set to true, |
174 | | // and the synchronously notify flag set to false. |
175 | 0 | update_finished_state(DidSeek::Yes, SynchronouslyNotify::No); |
176 | 0 | } |
177 | | |
178 | | // https://www.w3.org/TR/web-animations-1/#animation-current-time |
179 | | Optional<double> Animation::current_time() const |
180 | 0 | { |
181 | | // The current time is calculated from the first matching condition from below: |
182 | | |
183 | | // -> If the animation’s hold time is resolved, |
184 | 0 | if (m_hold_time.has_value()) { |
185 | | // The current time is the animation’s hold time. |
186 | 0 | return m_hold_time.value(); |
187 | 0 | } |
188 | | |
189 | | // -> If any of the following are true: |
190 | | // - the animation has no associated timeline, or |
191 | | // - the associated timeline is inactive, or |
192 | | // - the animation’s start time is unresolved. |
193 | 0 | if (!m_timeline || m_timeline->is_inactive() || !m_start_time.has_value()) { |
194 | | // The current time is an unresolved time value. |
195 | 0 | return {}; |
196 | 0 | } |
197 | | |
198 | | // -> Otherwise, |
199 | | // current time = (timeline time - start time) × playback rate |
200 | | // Where timeline time is the current time value of the associated timeline. The playback rate value is defined |
201 | | // in §4.4.15 Speed control. |
202 | 0 | return (m_timeline->current_time().value() - m_start_time.value()) * playback_rate(); |
203 | 0 | } |
204 | | |
205 | | // https://www.w3.org/TR/web-animations-1/#animation-set-the-current-time |
206 | | WebIDL::ExceptionOr<void> Animation::set_current_time(Optional<double> const& seek_time) |
207 | 0 | { |
208 | | // 1. Run the steps to silently set the current time of animation to seek time. |
209 | 0 | TRY(silently_set_current_time(seek_time)); |
210 | | |
211 | | // 2. If animation has a pending pause task, synchronously complete the pause operation by performing the following |
212 | | // steps: |
213 | 0 | if (m_pending_pause_task == TaskState::Scheduled) { |
214 | | // 1. Set animation’s hold time to seek time. |
215 | 0 | m_hold_time = seek_time; |
216 | | |
217 | | // 2. Apply any pending playback rate to animation. |
218 | 0 | apply_any_pending_playback_rate(); |
219 | | |
220 | | // 3. Make animation’s start time unresolved. |
221 | 0 | m_start_time = {}; |
222 | | |
223 | | // 4. Cancel the pending pause task. |
224 | 0 | m_pending_pause_task = TaskState::None; |
225 | | |
226 | | // 5 Resolve animation’s current ready promise with animation. |
227 | 0 | WebIDL::resolve_promise(realm(), current_ready_promise(), this); |
228 | 0 | } |
229 | | |
230 | | // 3. Run the procedure to update an animation’s finished state for animation with the did seek flag set to true, |
231 | | // and the synchronously notify flag set to false. |
232 | 0 | update_finished_state(DidSeek::Yes, SynchronouslyNotify::No); |
233 | |
|
234 | 0 | return {}; |
235 | 0 | } |
236 | | |
237 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-playbackrate |
238 | | // https://www.w3.org/TR/web-animations-1/#set-the-playback-rate |
239 | | WebIDL::ExceptionOr<void> Animation::set_playback_rate(double new_playback_rate) |
240 | 0 | { |
241 | | // Setting this attribute follows the procedure to set the playback rate of this object to the new value. |
242 | | |
243 | | // 1. Clear any pending playback rate on animation. |
244 | 0 | m_pending_playback_rate = {}; |
245 | | |
246 | | // 2. Let previous time be the value of the current time of animation before changing the playback rate. |
247 | 0 | auto previous_time = current_time(); |
248 | | |
249 | | // 3. Let previous playback rate be the current effective playback rate of animation. |
250 | 0 | auto previous_playback_rate = playback_rate(); |
251 | | |
252 | | // 4. Set the playback rate to new playback rate. |
253 | 0 | m_playback_rate = new_playback_rate; |
254 | | |
255 | | // 5. Perform the steps corresponding to the first matching condition from the following, if any: |
256 | | |
257 | | // -> If animation is associated with a monotonically increasing timeline and the previous time is resolved, |
258 | 0 | if (m_timeline && m_timeline->is_monotonically_increasing() && previous_time.has_value()) { |
259 | | // set the current time of animation to previous time. |
260 | 0 | TRY(set_current_time(previous_time)); |
261 | 0 | } |
262 | | // -> If animation is associated with a non-null timeline that is not monotonically increasing, the start time of |
263 | | // animation is resolved, associated effect end is not infinity, and either: |
264 | | // - the previous playback rate < 0 and the new playback rate ≥ 0, or |
265 | | // - the previous playback rate ≥ 0 and the new playback rate < 0, |
266 | 0 | else if (m_timeline && !m_timeline->is_monotonically_increasing() && m_start_time.has_value() && !isinf(associated_effect_end()) && ((previous_playback_rate < 0.0 && new_playback_rate >= 0.0) || (previous_playback_rate >= 0 && new_playback_rate < 0))) { |
267 | | // Set animation’s start time to the result of evaluating associated effect end - start time for animation. |
268 | 0 | m_start_time = associated_effect_end() - m_start_time.value(); |
269 | 0 | } |
270 | |
|
271 | 0 | return {}; |
272 | 0 | } |
273 | | |
274 | | // https://www.w3.org/TR/web-animations-1/#animation-play-state |
275 | | Bindings::AnimationPlayState Animation::play_state() const |
276 | 0 | { |
277 | | // The play state of animation, animation, at a given moment is the state corresponding to the first matching |
278 | | // condition from the following: |
279 | | |
280 | | // -> All of the following conditions are true: |
281 | | // - The current time of animation is unresolved, and |
282 | | // - the start time of animation is unresolved, and |
283 | | // - animation does not have either a pending play task or a pending pause task, |
284 | 0 | auto current_time = this->current_time(); |
285 | 0 | if (!current_time.has_value() && !m_start_time.has_value() && !pending()) { |
286 | | // → idle |
287 | 0 | return Bindings::AnimationPlayState::Idle; |
288 | 0 | } |
289 | | |
290 | | // -> Either of the following conditions are true: |
291 | | // - animation has a pending pause task, or |
292 | | // - both the start time of animation is unresolved and it does not have a pending play task, |
293 | 0 | if (m_pending_pause_task == TaskState::Scheduled || (!m_start_time.has_value() && m_pending_play_task == TaskState::None)) { |
294 | | // → paused |
295 | 0 | return Bindings::AnimationPlayState::Paused; |
296 | 0 | } |
297 | | |
298 | | // -> For animation, current time is resolved and either of the following conditions are true: |
299 | | // - animation’s effective playback rate > 0 and current time ≥ associated effect end; or |
300 | | // - animation’s effective playback rate < 0 and current time ≤ 0, |
301 | 0 | auto effective_playback_rate = this->effective_playback_rate(); |
302 | 0 | if (current_time.has_value() && ((effective_playback_rate > 0.0 && current_time.value() >= associated_effect_end()) || (effective_playback_rate < 0.0 && current_time.value() <= 0.0))) { |
303 | | // → finished |
304 | 0 | return Bindings::AnimationPlayState::Finished; |
305 | 0 | } |
306 | | |
307 | | // -> Otherwise, |
308 | | // → running |
309 | 0 | return Bindings::AnimationPlayState::Running; |
310 | 0 | } |
311 | | |
312 | | // https://www.w3.org/TR/web-animations-1/#animation-relevant |
313 | | bool Animation::is_relevant() const |
314 | 0 | { |
315 | | // An animation is relevant if: |
316 | | // - Its associated effect is current or in effect, and |
317 | | // - Its replace state is not removed. |
318 | 0 | return (m_effect->is_current() || m_effect->is_in_effect()) && replace_state() != Bindings::AnimationReplaceState::Removed; |
319 | 0 | } |
320 | | |
321 | | // https://www.w3.org/TR/web-animations-1/#replaceable-animation |
322 | | bool Animation::is_replaceable() const |
323 | 0 | { |
324 | | // An animation is replaceable if all of the following conditions are true: |
325 | | |
326 | | // - The existence of the animation is not prescribed by markup. That is, it is not a CSS animation with an owning |
327 | | // element, nor a CSS transition with an owning element. |
328 | | // FIXME: Check for transitions |
329 | 0 | if (is_css_animation() && static_cast<CSS::CSSAnimation const*>(this)->owning_element()) |
330 | 0 | return false; |
331 | | |
332 | | // - The animation's play state is finished. |
333 | 0 | if (play_state() != Bindings::AnimationPlayState::Finished) |
334 | 0 | return false; |
335 | | |
336 | | // - The animation's replace state is not removed. |
337 | 0 | if (replace_state() == Bindings::AnimationReplaceState::Removed) |
338 | 0 | return false; |
339 | | |
340 | | // - The animation is associated with a monotonically increasing timeline. |
341 | 0 | if (!m_timeline || !m_timeline->is_monotonically_increasing()) |
342 | 0 | return false; |
343 | | |
344 | | // - The animation has an associated effect. |
345 | 0 | if (!m_effect) |
346 | 0 | return false; |
347 | | |
348 | | // - The animation's associated effect is in effect. |
349 | 0 | if (!m_effect->is_in_effect()) |
350 | 0 | return false; |
351 | | |
352 | | // - The animation's associated effect has an effect target. |
353 | 0 | if (!m_effect->target()) |
354 | 0 | return false; |
355 | | |
356 | 0 | return true; |
357 | 0 | } |
358 | | |
359 | | void Animation::set_replace_state(Bindings::AnimationReplaceState value) |
360 | 0 | { |
361 | 0 | if (value == Bindings::AnimationReplaceState::Removed) { |
362 | | // Remove the associated effect from its target, if applicable |
363 | 0 | if (m_effect && m_effect->target()) |
364 | 0 | m_effect->target()->disassociate_with_animation(*this); |
365 | | |
366 | | // Remove this animation from its timeline |
367 | 0 | m_timeline->disassociate_with_animation(*this); |
368 | 0 | } else if (value == Bindings::AnimationReplaceState::Persisted && m_replace_state == Bindings::AnimationReplaceState::Removed) { |
369 | | // This animation was removed, but is now being "unremoved"; undo the effects from the if-statement above |
370 | 0 | if (m_effect && m_effect->target()) |
371 | 0 | m_effect->target()->associate_with_animation(*this); |
372 | 0 | m_timeline->associate_with_animation(*this); |
373 | 0 | } |
374 | |
|
375 | 0 | m_replace_state = value; |
376 | 0 | } |
377 | | |
378 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-onfinish |
379 | | JS::GCPtr<WebIDL::CallbackType> Animation::onfinish() |
380 | 0 | { |
381 | 0 | return event_handler_attribute(HTML::EventNames::finish); |
382 | 0 | } |
383 | | |
384 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-onfinish |
385 | | void Animation::set_onfinish(JS::GCPtr<WebIDL::CallbackType> event_handler) |
386 | 0 | { |
387 | 0 | set_event_handler_attribute(HTML::EventNames::finish, event_handler); |
388 | 0 | } |
389 | | |
390 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-oncancel |
391 | | JS::GCPtr<WebIDL::CallbackType> Animation::oncancel() |
392 | 0 | { |
393 | 0 | return event_handler_attribute(HTML::EventNames::cancel); |
394 | 0 | } |
395 | | |
396 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-oncancel |
397 | | void Animation::set_oncancel(JS::GCPtr<WebIDL::CallbackType> event_handler) |
398 | 0 | { |
399 | 0 | set_event_handler_attribute(HTML::EventNames::cancel, event_handler); |
400 | 0 | } |
401 | | |
402 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-onremove |
403 | | JS::GCPtr<WebIDL::CallbackType> Animation::onremove() |
404 | 0 | { |
405 | 0 | return event_handler_attribute(HTML::EventNames::remove); |
406 | 0 | } |
407 | | |
408 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-onremove |
409 | | void Animation::set_onremove(JS::GCPtr<WebIDL::CallbackType> event_handler) |
410 | 0 | { |
411 | 0 | set_event_handler_attribute(HTML::EventNames::remove, event_handler); |
412 | 0 | } |
413 | | |
414 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-cancel |
415 | | void Animation::cancel(ShouldInvalidate should_invalidate) |
416 | 0 | { |
417 | | // Note: When called from JS, we always want to invalidate the animation target's style. However, this method is |
418 | | // also called from the StyleComputer when the animation-name CSS property changes. That happens in the |
419 | | // middle of a cascade, and importantly, _before_ computing the animation effect stack, so there is no |
420 | | // need for another invalidation. And in fact, if we did invalidate, it would lead to a crash, as the element |
421 | | // would not have it's "m_needs_style_update" flag cleared. |
422 | |
|
423 | 0 | auto& realm = this->realm(); |
424 | | |
425 | | // 1. If animation’s play state is not idle, perform the following steps: |
426 | 0 | if (play_state() != Bindings::AnimationPlayState::Idle) { |
427 | 0 | HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm) }; |
428 | | |
429 | | // 1. Run the procedure to reset an animation’s pending tasks on animation. |
430 | 0 | reset_an_animations_pending_tasks(); |
431 | | |
432 | | // 2. Reject the current finished promise with a DOMException named "AbortError". |
433 | 0 | auto dom_exception = WebIDL::AbortError::create(realm, "Animation was cancelled"_string); |
434 | 0 | WebIDL::reject_promise(realm, current_finished_promise(), dom_exception); |
435 | | |
436 | | // 3. Set the [[PromiseIsHandled]] internal slot of the current finished promise to true. |
437 | 0 | WebIDL::mark_promise_as_handled(current_finished_promise()); |
438 | | |
439 | | // 4. Let current finished promise be a new promise in the relevant Realm of animation. |
440 | 0 | m_current_finished_promise = WebIDL::create_promise(realm); |
441 | 0 | m_is_finished = false; |
442 | | |
443 | | // 5. Create an AnimationPlaybackEvent, cancelEvent. |
444 | | // 6. Set cancelEvent’s type attribute to cancel. |
445 | | // 7. Set cancelEvent’s currentTime to null. |
446 | | // 8. Let timeline time be the current time of the timeline with which animation is associated. If animation is |
447 | | // not associated with an active timeline, let timeline time be an unresolved time value. |
448 | | // 9. Set cancelEvent’s timelineTime to timeline time. If timeline time is unresolved, set it to null. |
449 | 0 | AnimationPlaybackEventInit init; |
450 | 0 | init.timeline_time = m_timeline && !m_timeline->is_inactive() ? m_timeline->current_time() : Optional<double> {}; |
451 | 0 | auto cancel_event = AnimationPlaybackEvent::create(realm, HTML::EventNames::cancel, init); |
452 | | |
453 | | // 10. If animation has a document for timing, then append cancelEvent to its document for timing's pending |
454 | | // animation event queue along with its target, animation. If animation is associated with an active |
455 | | // timeline that defines a procedure to convert timeline times to origin-relative time, let the scheduled |
456 | | // event time be the result of applying that procedure to timeline time. Otherwise, the scheduled event time |
457 | | // is an unresolved time value. |
458 | | // Otherwise, queue a task to dispatch cancelEvent at animation. The task source for this task is the DOM |
459 | | // manipulation task source. |
460 | 0 | if (auto document = document_for_timing()) { |
461 | 0 | Optional<double> scheduled_event_time; |
462 | 0 | if (m_timeline && !m_timeline->is_inactive() && m_timeline->can_convert_a_timeline_time_to_an_origin_relative_time()) |
463 | 0 | scheduled_event_time = m_timeline->convert_a_timeline_time_to_an_origin_relative_time(m_timeline->current_time()); |
464 | 0 | document->append_pending_animation_event({ cancel_event, *this, *this, scheduled_event_time }); |
465 | 0 | } else { |
466 | 0 | HTML::queue_global_task(HTML::Task::Source::DOMManipulation, realm.global_object(), JS::create_heap_function(heap(), [this, cancel_event]() { |
467 | 0 | dispatch_event(cancel_event); |
468 | 0 | })); |
469 | 0 | } |
470 | 0 | } |
471 | | |
472 | | // 2. Make animation’s hold time unresolved. |
473 | 0 | m_hold_time = {}; |
474 | | |
475 | | // 3. Make animation’s start time unresolved. |
476 | 0 | m_start_time = {}; |
477 | | |
478 | | // This time is needed for dispatching the animationcancel DOM event |
479 | 0 | if (auto effect = m_effect) |
480 | 0 | m_saved_cancel_time = effect->active_time_using_fill(Bindings::FillMode::Both); |
481 | |
|
482 | 0 | if (should_invalidate == ShouldInvalidate::Yes) |
483 | 0 | invalidate_effect(); |
484 | 0 | } |
485 | | |
486 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-finish |
487 | | WebIDL::ExceptionOr<void> Animation::finish() |
488 | 0 | { |
489 | | // 1. If animation’s effective playback rate is zero, or if animation’s effective playback rate > 0 and associated |
490 | | // effect end is infinity, throw an "InvalidStateError" DOMException and abort these steps. |
491 | 0 | auto effective_playback_rate = this->effective_playback_rate(); |
492 | 0 | if (effective_playback_rate == 0.0) |
493 | 0 | return WebIDL::InvalidStateError::create(realm(), "Animation with a playback rate of 0 cannot be finished"_string); |
494 | 0 | if (effective_playback_rate > 0.0 && isinf(associated_effect_end())) |
495 | 0 | return WebIDL::InvalidStateError::create(realm(), "Animation with no end cannot be finished"_string); |
496 | | |
497 | | // 2. Apply any pending playback rate to animation. |
498 | 0 | apply_any_pending_playback_rate(); |
499 | | |
500 | | // 3. Set limit as follows: |
501 | | // -> If playback rate > 0, |
502 | | // Let limit be associated effect end. |
503 | | // -> Otherwise, |
504 | | // Let limit be zero. |
505 | 0 | auto playback_rate = this->playback_rate(); |
506 | 0 | auto limit = playback_rate > 0.0 ? associated_effect_end() : 0.0; |
507 | | |
508 | | // 4. Silently set the current time to limit. |
509 | 0 | TRY(silently_set_current_time(limit)); |
510 | | |
511 | | // 5. If animation’s start time is unresolved and animation has an associated active timeline, let the start time be |
512 | | // the result of evaluating timeline time - (limit / playback rate) where timeline time is the current time value |
513 | | // of the associated timeline. |
514 | 0 | if (!m_start_time.has_value() && m_timeline && !m_timeline->is_inactive()) |
515 | 0 | m_start_time = m_timeline->current_time().value() - (limit / playback_rate); |
516 | | |
517 | | // 6. If there is a pending pause task and start time is resolved, |
518 | 0 | auto should_resolve_ready_promise = false; |
519 | 0 | if (m_pending_pause_task == TaskState::Scheduled && m_start_time.has_value()) { |
520 | | // 1. Let the hold time be unresolved. |
521 | | // Note: Typically the hold time will already be unresolved except in the case when the animation was previously |
522 | | // idle. |
523 | 0 | m_hold_time = {}; |
524 | | |
525 | | // 2. Cancel the pending pause task. |
526 | 0 | m_pending_pause_task = TaskState::None; |
527 | | |
528 | | // 3. Resolve the current ready promise of animation with animation. |
529 | 0 | should_resolve_ready_promise = true; |
530 | 0 | } |
531 | | |
532 | | // 7. If there is a pending play task and start time is resolved, cancel that task and resolve the current ready |
533 | | // promise of animation with animation. |
534 | 0 | if (m_pending_play_task == TaskState::Scheduled && m_start_time.has_value()) { |
535 | 0 | m_pending_play_task = TaskState::None; |
536 | 0 | should_resolve_ready_promise = true; |
537 | 0 | } |
538 | |
|
539 | 0 | if (should_resolve_ready_promise) { |
540 | 0 | HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm()) }; |
541 | 0 | WebIDL::resolve_promise(realm(), current_ready_promise(), this); |
542 | 0 | } |
543 | | |
544 | | // 8. Run the procedure to update an animation’s finished state for animation with the did seek flag set to true, |
545 | | // and the synchronously notify flag set to true. |
546 | 0 | update_finished_state(DidSeek::Yes, SynchronouslyNotify::Yes); |
547 | |
|
548 | 0 | return {}; |
549 | 0 | } |
550 | | |
551 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-play |
552 | | WebIDL::ExceptionOr<void> Animation::play() |
553 | 0 | { |
554 | | // Begins or resumes playback of the animation by running the procedure to play an animation passing true as the |
555 | | // value of the auto-rewind flag. |
556 | 0 | return play_an_animation(AutoRewind::Yes); |
557 | 0 | } |
558 | | |
559 | | // https://www.w3.org/TR/web-animations-1/#play-an-animation |
560 | | WebIDL::ExceptionOr<void> Animation::play_an_animation(AutoRewind auto_rewind) |
561 | 0 | { |
562 | 0 | if (auto document = document_for_timing()) |
563 | 0 | document->ensure_animation_timer(); |
564 | | |
565 | | // 1. Let aborted pause be a boolean flag that is true if animation has a pending pause task, and false otherwise. |
566 | 0 | auto aborted_pause = m_pending_pause_task == TaskState::Scheduled; |
567 | | |
568 | | // 2. Let has pending ready promise be a boolean flag that is initially false. |
569 | 0 | auto has_pending_ready_promise = false; |
570 | | |
571 | | // 3. Let seek time be a time value that is initially unresolved. |
572 | 0 | Optional<double> seek_time; |
573 | | |
574 | | // 4. If the auto-rewind flag is true, perform the steps corresponding to the first matching condition from the |
575 | | // following, if any: |
576 | 0 | if (auto_rewind == AutoRewind::Yes) { |
577 | 0 | auto playback_rate = this->playback_rate(); |
578 | 0 | auto current_time = this->current_time(); |
579 | 0 | auto associated_effect_end = this->associated_effect_end(); |
580 | | |
581 | | // -> If animation’s effective playback rate ≥ 0, and animation’s current time is either: |
582 | | // - unresolved, or |
583 | | // - less than zero, or |
584 | | // - greater than or equal to associated effect end, |
585 | 0 | if (playback_rate >= 0.0 && (!current_time.has_value() || current_time.value() < 0.0 || current_time.value() >= associated_effect_end)) { |
586 | | // Set seek time to zero. |
587 | 0 | seek_time = 0.0; |
588 | 0 | } |
589 | | // -> If animation’s effective playback rate < 0, and animation’s current time is either: |
590 | | // - unresolved, or |
591 | | // - less than or equal to zero, or |
592 | | // - greater than associated effect end, |
593 | 0 | else if (playback_rate < 0.0 && (!current_time.has_value() || current_time.value() <= 0.0 || current_time.value() > associated_effect_end)) { |
594 | | // -> If associated effect end is positive infinity, |
595 | 0 | if (isinf(associated_effect_end) && associated_effect_end > 0.0) { |
596 | | // throw an "InvalidStateError" DOMException and abort these steps. |
597 | 0 | return WebIDL::InvalidStateError::create(realm(), "Cannot rewind an animation with an infinite effect end"_string); |
598 | 0 | } |
599 | | // -> Otherwise, |
600 | | // Set seek time to animation’s associated effect end. |
601 | 0 | seek_time = associated_effect_end; |
602 | 0 | } |
603 | 0 | } |
604 | | |
605 | | // 5. If the following three conditions are all satisfied: |
606 | | // - seek time is unresolved, and |
607 | | // - animation’s start time is unresolved, and |
608 | | // - animation’s current time is unresolved, |
609 | 0 | if (!seek_time.has_value() && !m_start_time.has_value() && !current_time().has_value()) { |
610 | | // set seek time to zero. |
611 | 0 | seek_time = 0.0; |
612 | 0 | } |
613 | | |
614 | | // 6. Let has finite timeline be true if animation has an associated timeline that is not monotonically increasing. |
615 | 0 | auto has_finite_timeline = m_timeline && !m_timeline->is_monotonically_increasing(); |
616 | | |
617 | | // 7. If seek time is resolved, |
618 | 0 | if (seek_time.has_value()) { |
619 | | // -> If has finite timeline is true, |
620 | 0 | if (has_finite_timeline) { |
621 | | // 1. Set animation’s start time to seek time. |
622 | 0 | m_start_time = seek_time; |
623 | | |
624 | | // 2. Let animation’s hold time be unresolved. |
625 | 0 | m_hold_time = {}; |
626 | | |
627 | | // 3. Apply any pending playback rate on animation. |
628 | 0 | apply_any_pending_playback_rate(); |
629 | 0 | } |
630 | | // Otherwise, |
631 | 0 | else { |
632 | | // Set animation’s hold time to seek time. |
633 | 0 | m_hold_time = seek_time; |
634 | 0 | } |
635 | 0 | } |
636 | | |
637 | | // 8. If animation’s hold time is resolved, let its start time be unresolved. |
638 | 0 | if (m_hold_time.has_value()) |
639 | 0 | m_start_time = {}; |
640 | | |
641 | | // 9. If animation has a pending play task or a pending pause task, |
642 | 0 | if (pending()) { |
643 | | // 1. Cancel that task. |
644 | 0 | m_pending_play_task = TaskState::None; |
645 | 0 | m_pending_pause_task = TaskState::None; |
646 | | |
647 | | // 2. Set has pending ready promise to true. |
648 | 0 | has_pending_ready_promise = true; |
649 | 0 | } |
650 | | |
651 | | // 10. If the following four conditions are all satisfied: |
652 | | // - animation’s hold time is unresolved, and |
653 | | // - seek time is unresolved, and |
654 | | // - aborted pause is false, and |
655 | | // - animation does not have a pending playback rate, |
656 | 0 | if (!m_hold_time.has_value() && !seek_time.has_value() && !aborted_pause && !m_pending_playback_rate.has_value()) { |
657 | | // abort this procedure. |
658 | 0 | return {}; |
659 | 0 | } |
660 | | |
661 | | // 11. If has pending ready promise is false, let animation’s current ready promise be a new promise in the relevant |
662 | | // Realm of animation. |
663 | 0 | if (!has_pending_ready_promise) |
664 | 0 | m_current_ready_promise = WebIDL::create_promise(realm()); |
665 | | |
666 | | // 12. Schedule a task to run as soon as animation is ready. The task shall perform the following steps: |
667 | | // |
668 | | // Note: Steps omitted, set run_pending_play_task() |
669 | | // |
670 | | // So long as the above task is scheduled but has yet to run, animation is described as having a pending play |
671 | | // task. While the task is running, however, animation does not have a pending play task. |
672 | | // |
673 | | // If a user agent determines that animation is immediately ready, it may schedule the above task as a microtask |
674 | | // such that it runs at the next microtask checkpoint, but it must not perform the task synchronously. |
675 | 0 | m_pending_play_task = TaskState::Scheduled; |
676 | 0 | if (m_timeline) |
677 | 0 | m_saved_play_time = m_timeline->current_time().value(); |
678 | | |
679 | | // 13. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false, |
680 | | // and the synchronously notify flag set to false. |
681 | 0 | update_finished_state(DidSeek::No, SynchronouslyNotify::No); |
682 | |
|
683 | 0 | return {}; |
684 | 0 | } |
685 | | |
686 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-pause |
687 | | WebIDL::ExceptionOr<void> Animation::pause() |
688 | 0 | { |
689 | | // 1. If animation has a pending pause task, abort these steps. |
690 | 0 | if (m_pending_pause_task == TaskState::Scheduled) |
691 | 0 | return {}; |
692 | | |
693 | | // 2. If the play state of animation is paused, abort these steps. |
694 | 0 | if (play_state() == Bindings::AnimationPlayState::Paused) |
695 | 0 | return {}; |
696 | | |
697 | | // 3. Let seek time be a time value that is initially unresolved. |
698 | 0 | Optional<double> seek_time; |
699 | | |
700 | | // 4. Let has finite timeline be true if animation has an associated timeline that is not monotonically increasing. |
701 | 0 | auto has_finite_timeline = m_timeline && !m_timeline->is_monotonically_increasing(); |
702 | | |
703 | | // 5. If the animation’s current time is unresolved, perform the steps according to the first matching condition |
704 | | // from below: |
705 | 0 | if (!current_time().has_value()) { |
706 | | // -> If animation’s playback rate is ≥ 0, |
707 | 0 | if (playback_rate() >= 0.0) { |
708 | | // Set seek time to zero. |
709 | 0 | seek_time = 0.0; |
710 | 0 | } |
711 | | // -> Otherwise |
712 | 0 | else { |
713 | | // If associated effect end for animation is positive infinity, |
714 | 0 | auto associated_effect_end = this->associated_effect_end(); |
715 | 0 | if (isinf(associated_effect_end) && associated_effect_end > 0.0) { |
716 | | // throw an "InvalidStateError" DOMException and abort these steps. |
717 | 0 | return WebIDL::InvalidStateError::create(realm(), "Cannot pause an animation with an infinite effect end"_string); |
718 | 0 | } |
719 | | |
720 | | // Otherwise, |
721 | | // Set seek time to animation’s associated effect end. |
722 | 0 | seek_time = associated_effect_end; |
723 | 0 | } |
724 | 0 | } |
725 | | |
726 | | // 6. If seek time is resolved, |
727 | 0 | if (seek_time.has_value()) { |
728 | | // If has finite timeline is true, |
729 | 0 | if (has_finite_timeline) { |
730 | | // Set animation’s start time to seek time. |
731 | 0 | m_start_time = seek_time; |
732 | 0 | } |
733 | | // Otherwise, |
734 | 0 | else { |
735 | | // Set animation’s hold time to seek time. |
736 | 0 | m_hold_time = seek_time; |
737 | 0 | } |
738 | 0 | } |
739 | | |
740 | | // 7. Let has pending ready promise be a boolean flag that is initially false. |
741 | 0 | auto has_pending_ready_promise = false; |
742 | | |
743 | | // 8. If animation has a pending play task, cancel that task and let has pending ready promise be true. |
744 | 0 | if (m_pending_play_task == TaskState::Scheduled) { |
745 | 0 | m_pending_pause_task = TaskState::None; |
746 | 0 | has_pending_ready_promise = true; |
747 | 0 | } |
748 | | |
749 | | // 9. If has pending ready promise is false, set animation’s current ready promise to a new promise in the relevant |
750 | | // Realm of animation. |
751 | 0 | if (!has_pending_ready_promise) |
752 | 0 | m_current_ready_promise = WebIDL::create_promise(realm()); |
753 | | |
754 | | // 10. Schedule a task to be executed at the first possible moment where both of the following conditions are true: |
755 | | // - the user agent has performed any processing necessary to suspend the playback of animation’s associated |
756 | | // effect, if any. |
757 | | // - the animation is associated with a timeline that is not inactive. |
758 | | // |
759 | | // Note: This is run_pending_pause_task() |
760 | 0 | m_pending_pause_task = TaskState::Scheduled; |
761 | 0 | m_saved_pause_time = m_timeline->current_time().value(); |
762 | | |
763 | | // 11. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false, |
764 | | // and the synchronously notify flag set to false. |
765 | 0 | update_finished_state(DidSeek::No, SynchronouslyNotify::No); |
766 | |
|
767 | 0 | return {}; |
768 | 0 | } |
769 | | |
770 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-updateplaybackrate |
771 | | WebIDL::ExceptionOr<void> Animation::update_playback_rate(double new_playback_rate) |
772 | 0 | { |
773 | | // 1. Let previous play state be animation’s play state. |
774 | | // Note: It is necessary to record the play state before updating animation’s effective playback rate since, in the |
775 | | // following logic, we want to immediately apply the pending playback rate of animation if it is currently |
776 | | // finished regardless of whether or not it will still be finished after we apply the pending playback rate. |
777 | 0 | auto previous_play_state = play_state(); |
778 | | |
779 | | // 2. Let animation’s pending playback rate be new playback rate. |
780 | 0 | m_pending_playback_rate = new_playback_rate; |
781 | | |
782 | | // 3. Perform the steps corresponding to the first matching condition from below: |
783 | | |
784 | | // -> If animation has a pending play task or a pending pause task, |
785 | 0 | if (pending()) { |
786 | | // Abort these steps. |
787 | | // Note: The different types of pending tasks will apply the pending playback rate when they run so there is no |
788 | | // further action required in this case. |
789 | 0 | return {}; |
790 | 0 | } |
791 | | |
792 | | // -> If previous play state is idle or paused, or animation’s current time is unresolved, |
793 | 0 | if (previous_play_state == Bindings::AnimationPlayState::Idle || previous_play_state == Bindings::AnimationPlayState::Paused || !current_time().has_value()) { |
794 | | // Apply any pending playback rate on animation. |
795 | | // Note: the second condition above is required so that if we have a running animation with an unresolved |
796 | | // current time and no pending play task, we do not attempt to play it below. |
797 | 0 | apply_any_pending_playback_rate(); |
798 | 0 | } |
799 | | // -> If previous play state is finished, |
800 | 0 | else if (previous_play_state == Bindings::AnimationPlayState::Finished) { |
801 | | // 1. Let the unconstrained current time be the result of calculating the current time of animation |
802 | | // substituting an unresolved time value for the hold time. |
803 | 0 | Optional<double> unconstrained_current_time; |
804 | 0 | { |
805 | 0 | TemporaryChange change(m_hold_time, {}); |
806 | 0 | unconstrained_current_time = current_time(); |
807 | 0 | } |
808 | | |
809 | | // 2. Let animation’s start time be the result of evaluating the following expression: |
810 | | // timeline time - (unconstrained current time / pending playback rate) |
811 | | // Where timeline time is the current time value of the timeline associated with animation. |
812 | | // If pending playback rate is zero, let animation’s start time be timeline time. |
813 | 0 | if (m_pending_playback_rate.value() == 0.0) { |
814 | 0 | m_start_time = m_timeline->current_time().value(); |
815 | 0 | } else { |
816 | 0 | m_start_time = m_timeline->current_time().value() - (unconstrained_current_time.value() / m_pending_playback_rate.value()); |
817 | 0 | } |
818 | | |
819 | | // 3. Apply any pending playback rate on animation. |
820 | 0 | apply_any_pending_playback_rate(); |
821 | | |
822 | | // 4. Run the procedure to update an animation’s finished state for animation with the did seek flag set to |
823 | | // false, and the synchronously notify flag set to false. |
824 | 0 | update_finished_state(DidSeek::No, SynchronouslyNotify::No); |
825 | 0 | } |
826 | | // -> Otherwise, |
827 | 0 | else { |
828 | | // Run the procedure to play an animation for animation with the auto-rewind flag set to false. |
829 | 0 | TRY(play_an_animation(AutoRewind::No)); |
830 | 0 | } |
831 | |
|
832 | 0 | return {}; |
833 | 0 | } |
834 | | |
835 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-reverse |
836 | | WebIDL::ExceptionOr<void> Animation::reverse() |
837 | 0 | { |
838 | 0 | auto& realm = this->realm(); |
839 | | |
840 | | // 1. If there is no timeline associated with animation, or the associated timeline is inactive throw an |
841 | | // "InvalidStateError" DOMException and abort these steps. |
842 | 0 | if (!m_timeline || m_timeline->is_inactive()) |
843 | 0 | return WebIDL::InvalidStateError::create(realm, "Cannot reverse an animation with an inactive timeline"_string); |
844 | | |
845 | | // 2. Let original pending playback rate be animation’s pending playback rate. |
846 | 0 | auto original_pending_playback_rate = m_pending_playback_rate; |
847 | | |
848 | | // 3. Let animation’s pending playback rate be the additive inverse of its effective playback rate (i.e. |
849 | | // -effective playback rate). |
850 | 0 | m_pending_playback_rate = -effective_playback_rate(); |
851 | | |
852 | | // 4. Run the steps to play an animation for animation with the auto-rewind flag set to true. |
853 | | // If the steps to play an animation throw an exception, set animation’s pending playback rate to original |
854 | | // pending playback rate and propagate the exception. |
855 | 0 | auto result = play_an_animation(AutoRewind::Yes); |
856 | 0 | if (result.is_error()) { |
857 | 0 | m_pending_playback_rate = original_pending_playback_rate; |
858 | 0 | return result; |
859 | 0 | } |
860 | | |
861 | 0 | return {}; |
862 | 0 | } |
863 | | |
864 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-persist |
865 | | void Animation::persist() |
866 | 0 | { |
867 | | // Sets this animation’s replace state to persisted. |
868 | 0 | set_replace_state(Bindings::AnimationReplaceState::Persisted); |
869 | 0 | } |
870 | | |
871 | | // https://www.w3.org/TR/web-animations-1/#animation-time-to-timeline-time |
872 | | Optional<double> Animation::convert_an_animation_time_to_timeline_time(Optional<double> time) const |
873 | 0 | { |
874 | | // 1. If time is unresolved, return time. |
875 | 0 | if (!time.has_value()) |
876 | 0 | return time; |
877 | | |
878 | | // 2. If time is infinity, return an unresolved time value. |
879 | 0 | if (isinf(time.value())) |
880 | 0 | return {}; |
881 | | |
882 | | // 3. If animation’s playback rate is zero, return an unresolved time value. |
883 | 0 | if (m_playback_rate == 0.0) |
884 | 0 | return {}; |
885 | | |
886 | | // 4. If animation’s start time is unresolved, return an unresolved time value. |
887 | 0 | if (!m_start_time.has_value()) |
888 | 0 | return {}; |
889 | | |
890 | | // 5. Return the result of calculating: time × (1 / playback rate) + start time (where playback rate and start time |
891 | | // are the playback rate and start time of animation, respectively). |
892 | 0 | return (time.value() * (1.0 / m_playback_rate)) + m_start_time.value(); |
893 | 0 | } |
894 | | |
895 | | // https://www.w3.org/TR/web-animations-1/#animation-time-to-origin-relative-time |
896 | | Optional<double> Animation::convert_a_timeline_time_to_an_origin_relative_time(Optional<double> time) const |
897 | 0 | { |
898 | | // 1. Let timeline time be the result of converting time from an animation time to a timeline time. |
899 | 0 | auto timeline_time = convert_an_animation_time_to_timeline_time(time); |
900 | | |
901 | | // 2. If timeline time is unresolved, return time. |
902 | 0 | if (!timeline_time.has_value()) |
903 | 0 | return time; |
904 | | |
905 | | // 3. If animation is not associated with a timeline, return an unresolved time value. |
906 | 0 | if (!m_timeline) |
907 | 0 | return {}; |
908 | | |
909 | | // 4. If animation is associated with an inactive timeline, return an unresolved time value. |
910 | 0 | if (m_timeline->is_inactive()) |
911 | 0 | return {}; |
912 | | |
913 | | // 5. If there is no procedure to convert a timeline time to an origin-relative time for the timeline associated |
914 | | // with animation, return an unresolved time value. |
915 | 0 | if (!m_timeline->can_convert_a_timeline_time_to_an_origin_relative_time()) |
916 | 0 | return {}; |
917 | | |
918 | | // 6. Return the result of converting timeline time to an origin-relative time using the procedure defined for the |
919 | | // timeline associated with animation. |
920 | 0 | return m_timeline->convert_a_timeline_time_to_an_origin_relative_time(timeline_time); |
921 | 0 | } |
922 | | |
923 | | // https://www.w3.org/TR/web-animations-1/#animation-document-for-timing |
924 | | JS::GCPtr<DOM::Document> Animation::document_for_timing() const |
925 | 0 | { |
926 | | // An animation’s document for timing is the Document with which its timeline is associated. If an animation is not |
927 | | // associated with a timeline, or its timeline is not associated with a document, then it has no document for |
928 | | // timing. |
929 | 0 | if (!m_timeline) |
930 | 0 | return {}; |
931 | 0 | return m_timeline->associated_document(); |
932 | 0 | } |
933 | | |
934 | | void Animation::notify_timeline_time_did_change() |
935 | 0 | { |
936 | 0 | update_finished_state(DidSeek::No, SynchronouslyNotify::Yes); |
937 | | |
938 | | // Act on the pending play or pause task |
939 | 0 | if (m_pending_play_task == TaskState::Scheduled) { |
940 | 0 | m_pending_play_task = TaskState::None; |
941 | 0 | run_pending_play_task(); |
942 | 0 | } |
943 | |
|
944 | 0 | if (m_pending_pause_task == TaskState::Scheduled) { |
945 | 0 | m_pending_pause_task = TaskState::None; |
946 | 0 | run_pending_pause_task(); |
947 | 0 | } |
948 | 0 | } |
949 | | |
950 | | void Animation::effect_timing_changed(Badge<AnimationEffect>) |
951 | 0 | { |
952 | 0 | update_finished_state(DidSeek::No, SynchronouslyNotify::Yes); |
953 | 0 | } |
954 | | |
955 | | // https://www.w3.org/TR/web-animations-1/#associated-effect-end |
956 | | double Animation::associated_effect_end() const |
957 | 0 | { |
958 | | // The associated effect end of an animation is equal to the end time of the animation’s associated effect. If the |
959 | | // animation has no associated effect, the associated effect end is zero. |
960 | 0 | return m_effect ? m_effect->end_time() : 0.0; |
961 | 0 | } |
962 | | |
963 | | // https://www.w3.org/TR/web-animations-1/#effective-playback-rate |
964 | | double Animation::effective_playback_rate() const |
965 | 0 | { |
966 | | // The effective playback rate of an animation is its pending playback rate, if set, otherwise it is the animation’s |
967 | | // playback rate. |
968 | 0 | return m_pending_playback_rate.has_value() ? m_pending_playback_rate.value() : m_playback_rate; |
969 | 0 | } |
970 | | |
971 | | // https://www.w3.org/TR/web-animations-1/#apply-any-pending-playback-rate |
972 | | void Animation::apply_any_pending_playback_rate() |
973 | 0 | { |
974 | | // 1. If animation does not have a pending playback rate, abort these steps. |
975 | 0 | if (!m_pending_playback_rate.has_value()) |
976 | 0 | return; |
977 | | |
978 | | // 2. Set animation’s playback rate to its pending playback rate. |
979 | 0 | m_playback_rate = m_pending_playback_rate.value(); |
980 | | |
981 | | // 3. Clear animation’s pending playback rate. |
982 | 0 | m_pending_playback_rate = {}; |
983 | 0 | } |
984 | | |
985 | | // https://www.w3.org/TR/web-animations-1/#animation-silently-set-the-current-time |
986 | | WebIDL::ExceptionOr<void> Animation::silently_set_current_time(Optional<double> seek_time) |
987 | 0 | { |
988 | | // 1. If seek time is an unresolved time value, then perform the following steps. |
989 | 0 | if (!seek_time.has_value()) { |
990 | | // 1. If the current time is resolved, then throw a TypeError. |
991 | 0 | if (current_time().has_value()) { |
992 | 0 | return WebIDL::SimpleException { |
993 | 0 | WebIDL::SimpleExceptionType::TypeError, |
994 | 0 | "Cannot change an animation's current time from a resolve value to an unresolved value"sv |
995 | 0 | }; |
996 | 0 | } |
997 | | |
998 | | // 2. Abort these steps. |
999 | 0 | return {}; |
1000 | 0 | } |
1001 | | |
1002 | | // 2. Update either animation’s hold time or start time as follows: |
1003 | | |
1004 | | // -> If any of the following conditions are true: |
1005 | | // - animation’s hold time is resolved, or |
1006 | | // - animation’s start time is unresolved, or |
1007 | | // - animation has no associated timeline or the associated timeline is inactive, or |
1008 | | // - animation’s playback rate is 0, |
1009 | 0 | if (m_hold_time.has_value() || !m_start_time.has_value() || !m_timeline || m_timeline->is_inactive() || m_playback_rate == 0.0) { |
1010 | | // Set animation’s hold time to seek time. |
1011 | 0 | m_hold_time = seek_time; |
1012 | 0 | } |
1013 | | // -> Otherwise, |
1014 | 0 | else { |
1015 | | // Set animation’s start time to the result of evaluating timeline time - (seek time / playback rate) where |
1016 | | // timeline time is the current time value of timeline associated with animation. |
1017 | 0 | m_start_time = m_timeline->current_time().value() - (seek_time.value() / m_playback_rate); |
1018 | 0 | } |
1019 | | |
1020 | | // 3. If animation has no associated timeline or the associated timeline is inactive, make animation’s start time |
1021 | | // unresolved. |
1022 | 0 | if (!m_timeline || m_timeline->is_inactive()) |
1023 | 0 | m_start_time = {}; |
1024 | | |
1025 | | // 4. Make animation’s previous current time unresolved. |
1026 | 0 | m_previous_current_time = {}; |
1027 | |
|
1028 | 0 | return {}; |
1029 | 0 | } |
1030 | | |
1031 | | // https://www.w3.org/TR/web-animations-1/#update-an-animations-finished-state |
1032 | | void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify synchronously_notify) |
1033 | 0 | { |
1034 | 0 | auto& realm = this->realm(); |
1035 | | |
1036 | | // 1. Let the unconstrained current time be the result of calculating the current time substituting an unresolved |
1037 | | // time value for the hold time if did seek is false. If did seek is true, the unconstrained current time is |
1038 | | // equal to the current time. |
1039 | | // |
1040 | | // Note: This is required to accommodate timelines that may change direction. Without this definition, a once- |
1041 | | // finished animation would remain finished even when its timeline progresses in the opposite direction. |
1042 | 0 | Optional<double> unconstrained_current_time; |
1043 | 0 | if (did_seek == DidSeek::No) { |
1044 | 0 | TemporaryChange change(m_hold_time, {}); |
1045 | 0 | unconstrained_current_time = current_time(); |
1046 | 0 | } else { |
1047 | 0 | unconstrained_current_time = current_time(); |
1048 | 0 | } |
1049 | | |
1050 | | // 2. If all three of the following conditions are true, |
1051 | | // - the unconstrained current time is resolved, and |
1052 | | // - animation’s start time is resolved, and |
1053 | | // - animation does not have a pending play task or a pending pause task, |
1054 | 0 | if (unconstrained_current_time.has_value() && m_start_time.has_value() && !pending()) { |
1055 | | // then update animation’s hold time based on the first matching condition for animation from below, if any: |
1056 | | |
1057 | | // -> If playback rate > 0 and unconstrained current time is greater than or equal to associated effect end, |
1058 | 0 | auto associated_effect_end = this->associated_effect_end(); |
1059 | 0 | if (m_playback_rate > 0.0 && unconstrained_current_time.value() >= associated_effect_end) { |
1060 | | // If did seek is true, let the hold time be the value of unconstrained current time. |
1061 | 0 | if (did_seek == DidSeek::Yes) { |
1062 | 0 | m_hold_time = unconstrained_current_time; |
1063 | 0 | } |
1064 | | // If did seek is false, let the hold time be the maximum value of previous current time and associated |
1065 | | // effect end. If the previous current time is unresolved, let the hold time be associated effect end. |
1066 | 0 | else if (m_previous_current_time.has_value()) { |
1067 | 0 | m_hold_time = max(m_previous_current_time.value(), associated_effect_end); |
1068 | 0 | } else { |
1069 | 0 | m_hold_time = associated_effect_end; |
1070 | 0 | } |
1071 | 0 | } |
1072 | | // -> If playback rate < 0 and unconstrained current time is less than or equal to 0, |
1073 | 0 | else if (m_playback_rate < 0.0 && unconstrained_current_time.value() <= 0.0) { |
1074 | | // If did seek is true, let the hold time be the value of unconstrained current time. |
1075 | 0 | if (did_seek == DidSeek::Yes) { |
1076 | 0 | m_hold_time = unconstrained_current_time; |
1077 | 0 | } |
1078 | | // If did seek is false, let the hold time be the minimum value of previous current time and zero. If the |
1079 | | // previous current time is unresolved, let the hold time be zero. |
1080 | 0 | else if (m_previous_current_time.has_value()) { |
1081 | 0 | m_hold_time = min(m_previous_current_time.value(), 0.0); |
1082 | 0 | } else { |
1083 | 0 | m_hold_time = 0.0; |
1084 | 0 | } |
1085 | 0 | } |
1086 | | // -> If playback rate ≠ 0, and animation is associated with an active timeline, |
1087 | 0 | else if (m_playback_rate != 0.0 && m_timeline && !m_timeline->is_inactive()) { |
1088 | | // Perform the following steps: |
1089 | | |
1090 | | // 1. If did seek is true and the hold time is resolved, let animation’s start time be equal to the result |
1091 | | // of evaluating timeline time - (hold time / playback rate) where timeline time is the current time |
1092 | | // value of timeline associated with animation. |
1093 | 0 | if (did_seek == DidSeek::Yes && m_hold_time.has_value()) |
1094 | 0 | m_start_time = m_timeline->current_time().value() - (m_hold_time.value() / m_playback_rate); |
1095 | | |
1096 | | // 2. Let the hold time be unresolved. |
1097 | 0 | m_hold_time = {}; |
1098 | 0 | } |
1099 | 0 | } |
1100 | | |
1101 | | // 3. Set the previous current time of animation be the result of calculating its current time. |
1102 | 0 | m_previous_current_time = current_time(); |
1103 | | |
1104 | | // 4. Let current finished state be true if the play state of animation is finished. Otherwise, let it be false. |
1105 | 0 | auto current_finished_state = play_state() == Bindings::AnimationPlayState::Finished; |
1106 | | |
1107 | | // 5. If current finished state is true and the current finished promise is not yet resolved, perform the following |
1108 | | // steps: |
1109 | 0 | if (current_finished_state && !m_is_finished) { |
1110 | | // 1. Let finish notification steps refer to the following procedure: |
1111 | 0 | auto finish_notification_steps = JS::create_heap_function(heap(), [this, &realm]() { |
1112 | | // 1. If animation’s play state is not equal to finished, abort these steps. |
1113 | 0 | if (play_state() != Bindings::AnimationPlayState::Finished) |
1114 | 0 | return; |
1115 | | |
1116 | | // 2. Resolve animation’s current finished promise object with animation. |
1117 | 0 | HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm) }; |
1118 | 0 | WebIDL::resolve_promise(realm, current_finished_promise(), this); |
1119 | 0 | m_is_finished = true; |
1120 | | |
1121 | | // 3. Create an AnimationPlaybackEvent, finishEvent. |
1122 | | // 4. Set finishEvent’s type attribute to finish. |
1123 | | // 5. Set finishEvent’s currentTime attribute to the current time of animation. |
1124 | 0 | AnimationPlaybackEventInit init; |
1125 | 0 | init.current_time = current_time(); |
1126 | 0 | auto finish_event = AnimationPlaybackEvent::create(realm, HTML::EventNames::finish, init); |
1127 | | |
1128 | | // 6. Set finishEvent’s timelineTime attribute to the current time of the timeline with which animation is |
1129 | | // associated. If animation is not associated with a timeline, or the timeline is inactive, let |
1130 | | // timelineTime be null. |
1131 | 0 | if (m_timeline && !m_timeline->is_inactive()) |
1132 | 0 | finish_event->set_timeline_time(m_timeline->current_time()); |
1133 | 0 | else |
1134 | 0 | finish_event->set_timeline_time({}); |
1135 | | |
1136 | | // 7. If animation has a document for timing, then append finishEvent to its document for timing's pending |
1137 | | // animation event queue along with its target, animation. For the scheduled event time, use the result |
1138 | | // of converting animation’s associated effect end to an origin-relative time. |
1139 | 0 | if (auto document_for_timing = this->document_for_timing()) { |
1140 | 0 | document_for_timing->append_pending_animation_event({ |
1141 | 0 | .event = finish_event, |
1142 | 0 | .animation = *this, |
1143 | 0 | .target = *this, |
1144 | 0 | .scheduled_event_time = convert_a_timeline_time_to_an_origin_relative_time(associated_effect_end()), |
1145 | 0 | }); |
1146 | 0 | } |
1147 | | // Otherwise, queue a task to dispatch finishEvent at animation. The task source for this task is the DOM |
1148 | | // manipulation task source. |
1149 | 0 | else { |
1150 | | // Manually create a task so its ID can be saved |
1151 | 0 | auto& document = verify_cast<HTML::Window>(realm.global_object()).associated_document(); |
1152 | 0 | auto task = HTML::Task::create(vm(), HTML::Task::Source::DOMManipulation, &document, |
1153 | 0 | JS::create_heap_function(heap(), [this, finish_event]() { |
1154 | 0 | dispatch_event(finish_event); |
1155 | 0 | })); |
1156 | 0 | m_pending_finish_microtask_id = task->id(); |
1157 | 0 | HTML::main_thread_event_loop().task_queue().add(task); |
1158 | 0 | } |
1159 | 0 | }); |
1160 | | |
1161 | | // 2. If synchronously notify is true, cancel any queued microtask to run the finish notification steps for this |
1162 | | // animation, and run the finish notification steps immediately. |
1163 | 0 | if (synchronously_notify == SynchronouslyNotify::Yes) { |
1164 | 0 | if (m_pending_finish_microtask_id.has_value()) { |
1165 | 0 | HTML::main_thread_event_loop().task_queue().remove_tasks_matching([id = move(m_pending_finish_microtask_id)](auto const& task) { |
1166 | 0 | return task.id() == id; |
1167 | 0 | }); |
1168 | 0 | } |
1169 | 0 | finish_notification_steps->function()(); |
1170 | 0 | } |
1171 | | // Otherwise, if synchronously notify is false, queue a microtask to run finish notification steps for |
1172 | | // animation unless there is already a microtask queued to run those steps for animation. |
1173 | 0 | else if (!m_pending_finish_microtask_id.has_value()) { |
1174 | 0 | auto& document = verify_cast<HTML::Window>(realm.global_object()).associated_document(); |
1175 | 0 | auto task = HTML::Task::create(vm(), HTML::Task::Source::DOMManipulation, &document, move(finish_notification_steps)); |
1176 | 0 | m_pending_finish_microtask_id = task->id(); |
1177 | 0 | HTML::main_thread_event_loop().task_queue().add(move(task)); |
1178 | 0 | } |
1179 | 0 | } |
1180 | | |
1181 | | // 6. If current finished state is false and animation’s current finished promise is already resolved, set |
1182 | | // animation’s current finished promise to a new promise in the relevant Realm of animation. |
1183 | 0 | if (!current_finished_state && m_is_finished) { |
1184 | 0 | HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm) }; |
1185 | 0 | m_current_finished_promise = WebIDL::create_promise(realm); |
1186 | 0 | m_is_finished = false; |
1187 | 0 | } |
1188 | |
|
1189 | 0 | invalidate_effect(); |
1190 | 0 | } |
1191 | | |
1192 | | // https://www.w3.org/TR/web-animations-1/#animation-reset-an-animations-pending-tasks |
1193 | | void Animation::reset_an_animations_pending_tasks() |
1194 | 0 | { |
1195 | 0 | auto& realm = this->realm(); |
1196 | | |
1197 | | // 1. If animation does not have a pending play task or a pending pause task, abort this procedure. |
1198 | 0 | if (!pending()) |
1199 | 0 | return; |
1200 | | |
1201 | | // 2. If animation has a pending play task, cancel that task. |
1202 | 0 | m_pending_play_task = TaskState::None; |
1203 | | |
1204 | | // 3. If animation has a pending pause task, cancel that task. |
1205 | 0 | m_pending_pause_task = TaskState::None; |
1206 | | |
1207 | | // 4. Apply any pending playback rate on animation. |
1208 | 0 | apply_any_pending_playback_rate(); |
1209 | | |
1210 | | // 5. Reject animation’s current ready promise with a DOMException named "AbortError". |
1211 | 0 | auto dom_exception = WebIDL::AbortError::create(realm, "Animation was cancelled"_string); |
1212 | 0 | WebIDL::reject_promise(realm, current_ready_promise(), dom_exception); |
1213 | | |
1214 | | // 6. Set the [[PromiseIsHandled]] internal slot of animation’s current ready promise to true. |
1215 | 0 | WebIDL::mark_promise_as_handled(current_ready_promise()); |
1216 | | |
1217 | | // 7. Let animation’s current ready promise be the result of creating a new resolved Promise object with value |
1218 | | // animation in the relevant Realm of animation. |
1219 | 0 | m_current_ready_promise = WebIDL::create_resolved_promise(realm, this); |
1220 | 0 | } |
1221 | | |
1222 | | // Step 12 of https://www.w3.org/TR/web-animations-1/#playing-an-animation-section |
1223 | | void Animation::run_pending_play_task() |
1224 | 0 | { |
1225 | | // 1. Assert that at least one of animation’s start time or hold time is resolved. |
1226 | 0 | VERIFY(m_start_time.has_value() || m_hold_time.has_value()); |
1227 | | |
1228 | | // 2. Let ready time be the time value of the timeline associated with animation at the moment when animation became |
1229 | | // ready. |
1230 | 0 | auto ready_time = m_saved_play_time.release_value(); |
1231 | | |
1232 | | // 3. Perform the steps corresponding to the first matching condition below, if any: |
1233 | | |
1234 | | // -> If animation’s hold time is resolved, |
1235 | 0 | if (m_hold_time.has_value()) { |
1236 | | // 1. Apply any pending playback rate on animation. |
1237 | 0 | apply_any_pending_playback_rate(); |
1238 | | |
1239 | | // 2. Let new start time be the result of evaluating ready time - hold time / playback rate for animation. If |
1240 | | // the playback rate is zero, let new start time be simply ready time. |
1241 | 0 | auto new_start_time = m_playback_rate != 0.0 ? ready_time - (m_hold_time.value() / m_playback_rate) : ready_time; |
1242 | | |
1243 | | // 3. Set the start time of animation to new start time. |
1244 | 0 | m_start_time = new_start_time; |
1245 | | |
1246 | | // 4. If animation’s playback rate is not 0, make animation’s hold time unresolved. |
1247 | 0 | if (m_playback_rate != 0.0) |
1248 | 0 | m_hold_time = {}; |
1249 | 0 | } |
1250 | | // -> If animation’s start time is resolved and animation has a pending playback rate, |
1251 | 0 | else if (m_start_time.has_value() && m_pending_playback_rate.has_value()) { |
1252 | | // 1. Let current time to match be the result of evaluating (ready time - start time) × playback rate for |
1253 | | // animation. |
1254 | 0 | auto current_time_to_match = (ready_time - m_start_time.value()) * m_playback_rate; |
1255 | | |
1256 | | // 2. Apply any pending playback rate on animation. |
1257 | 0 | apply_any_pending_playback_rate(); |
1258 | | |
1259 | | // 3. If animation’s playback rate is zero, let animation’s hold time be current time to match. |
1260 | 0 | if (m_playback_rate == 0.0) |
1261 | 0 | m_hold_time = current_time_to_match; |
1262 | | |
1263 | | // 4. Let new start time be the result of evaluating ready time - current time to match / playback rate for |
1264 | | // animation. If the playback rate is zero, let new start time be simply ready time. |
1265 | 0 | auto new_start_time = m_playback_rate != 0.0 ? ready_time - (current_time_to_match / m_playback_rate) : ready_time; |
1266 | | |
1267 | | // 5. Set the start time of animation to new start time. |
1268 | 0 | m_start_time = new_start_time; |
1269 | 0 | } |
1270 | | |
1271 | | // 4. Resolve animation’s current ready promise with animation. |
1272 | 0 | HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm()) }; |
1273 | 0 | WebIDL::resolve_promise(realm(), current_ready_promise(), this); |
1274 | | |
1275 | | // 5. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false, |
1276 | | // and the synchronously notify flag set to false. |
1277 | 0 | update_finished_state(DidSeek::No, SynchronouslyNotify::No); |
1278 | 0 | } |
1279 | | |
1280 | | // Step 10 of https://www.w3.org/TR/web-animations-1/#pause-an-animation |
1281 | | void Animation::run_pending_pause_task() |
1282 | 0 | { |
1283 | | // 1. Let ready time be the time value of the timeline associated with animation at the moment when the user agent |
1284 | | // completed processing necessary to suspend playback of animation’s associated effect. |
1285 | 0 | VERIFY(m_saved_pause_time.has_value()); |
1286 | 0 | auto ready_time = m_saved_pause_time.release_value(); |
1287 | | |
1288 | | // 2. If animation’s start time is resolved and its hold time is not resolved, let animation’s hold time be the |
1289 | | // result of evaluating (ready time - start time) × playback rate. |
1290 | | // Note: The hold time might be already set if the animation is finished, or if the animation has a pending play |
1291 | | // task. In either case we want to preserve the hold time as we enter the paused state. |
1292 | 0 | if (m_start_time.has_value() && !m_hold_time.has_value()) |
1293 | 0 | m_hold_time = (ready_time - m_start_time.value()) * m_playback_rate; |
1294 | | |
1295 | | // 3. Apply any pending playback rate on animation. |
1296 | 0 | apply_any_pending_playback_rate(); |
1297 | | |
1298 | | // 4. Make animation’s start time unresolved. |
1299 | 0 | m_start_time = {}; |
1300 | | |
1301 | | // 5. Resolve animation’s current ready promise with animation. |
1302 | 0 | HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm()) }; |
1303 | 0 | WebIDL::resolve_promise(realm(), current_ready_promise(), this); |
1304 | | |
1305 | | // 6. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false, |
1306 | | // and the synchronously notify flag set to false. |
1307 | 0 | update_finished_state(DidSeek::No, SynchronouslyNotify::No); |
1308 | 0 | } |
1309 | | |
1310 | | JS::NonnullGCPtr<WebIDL::Promise> Animation::current_ready_promise() const |
1311 | 0 | { |
1312 | 0 | if (!m_current_ready_promise) { |
1313 | | // The current ready promise is initially a resolved Promise created using the procedure to create a new |
1314 | | // resolved Promise with the animation itself as its value and created in the relevant Realm of the animation. |
1315 | 0 | m_current_ready_promise = WebIDL::create_resolved_promise(realm(), this); |
1316 | 0 | } |
1317 | |
|
1318 | 0 | return *m_current_ready_promise; |
1319 | 0 | } |
1320 | | |
1321 | | JS::NonnullGCPtr<WebIDL::Promise> Animation::current_finished_promise() const |
1322 | 0 | { |
1323 | 0 | if (!m_current_finished_promise) { |
1324 | | // The current finished promise is initially a pending Promise object. |
1325 | 0 | m_current_finished_promise = WebIDL::create_promise(realm()); |
1326 | 0 | } |
1327 | |
|
1328 | 0 | return *m_current_finished_promise; |
1329 | 0 | } |
1330 | | |
1331 | | void Animation::invalidate_effect() |
1332 | 0 | { |
1333 | 0 | if (m_effect) { |
1334 | 0 | if (auto target = m_effect->target(); target && target->paintable()) { |
1335 | 0 | target->document().set_needs_animated_style_update(); |
1336 | 0 | target->paintable()->set_needs_display(); |
1337 | 0 | } |
1338 | 0 | } |
1339 | 0 | } |
1340 | | |
1341 | | Animation::Animation(JS::Realm& realm) |
1342 | 0 | : DOM::EventTarget(realm) |
1343 | 0 | { |
1344 | 0 | static unsigned int next_animation_list_order = 0; |
1345 | 0 | m_global_animation_list_order = next_animation_list_order++; |
1346 | 0 | } |
1347 | | |
1348 | | void Animation::initialize(JS::Realm& realm) |
1349 | 0 | { |
1350 | 0 | Base::initialize(realm); |
1351 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(Animation); |
1352 | 0 | } |
1353 | | |
1354 | | void Animation::visit_edges(Cell::Visitor& visitor) |
1355 | 0 | { |
1356 | 0 | Base::visit_edges(visitor); |
1357 | 0 | visitor.visit(m_effect); |
1358 | 0 | visitor.visit(m_timeline); |
1359 | 0 | visitor.visit(m_current_ready_promise); |
1360 | 0 | visitor.visit(m_current_finished_promise); |
1361 | 0 | visitor.visit(m_owning_element); |
1362 | 0 | } |
1363 | | |
1364 | | } |