/src/serenity/Userland/Libraries/LibWeb/Animations/AnimationEffect.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2023-2024, Matthew Olsson <mattco@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibJS/Runtime/VM.h> |
8 | | #include <LibWeb/Animations/Animation.h> |
9 | | #include <LibWeb/Animations/AnimationEffect.h> |
10 | | #include <LibWeb/Animations/AnimationTimeline.h> |
11 | | #include <LibWeb/Bindings/AnimationEffectPrototype.h> |
12 | | #include <LibWeb/Bindings/Intrinsics.h> |
13 | | #include <LibWeb/CSS/Parser/Parser.h> |
14 | | #include <LibWeb/WebIDL/ExceptionOr.h> |
15 | | |
16 | | namespace Web::Animations { |
17 | | |
18 | | JS_DEFINE_ALLOCATOR(AnimationEffect); |
19 | | |
20 | | Bindings::FillMode css_fill_mode_to_bindings_fill_mode(CSS::AnimationFillMode mode) |
21 | 0 | { |
22 | 0 | switch (mode) { |
23 | 0 | case CSS::AnimationFillMode::Backwards: |
24 | 0 | return Bindings::FillMode::Backwards; |
25 | 0 | case CSS::AnimationFillMode::Both: |
26 | 0 | return Bindings::FillMode::Both; |
27 | 0 | case CSS::AnimationFillMode::Forwards: |
28 | 0 | return Bindings::FillMode::Forwards; |
29 | 0 | case CSS::AnimationFillMode::None: |
30 | 0 | return Bindings::FillMode::None; |
31 | 0 | default: |
32 | 0 | VERIFY_NOT_REACHED(); |
33 | 0 | } |
34 | 0 | } |
35 | | |
36 | | Bindings::PlaybackDirection css_animation_direction_to_bindings_playback_direction(CSS::AnimationDirection direction) |
37 | 0 | { |
38 | 0 | switch (direction) { |
39 | 0 | case CSS::AnimationDirection::Alternate: |
40 | 0 | return Bindings::PlaybackDirection::Alternate; |
41 | 0 | case CSS::AnimationDirection::AlternateReverse: |
42 | 0 | return Bindings::PlaybackDirection::AlternateReverse; |
43 | 0 | case CSS::AnimationDirection::Normal: |
44 | 0 | return Bindings::PlaybackDirection::Normal; |
45 | 0 | case CSS::AnimationDirection::Reverse: |
46 | 0 | return Bindings::PlaybackDirection::Reverse; |
47 | 0 | default: |
48 | 0 | VERIFY_NOT_REACHED(); |
49 | 0 | } |
50 | 0 | } |
51 | | |
52 | | OptionalEffectTiming EffectTiming::to_optional_effect_timing() const |
53 | 0 | { |
54 | 0 | return { |
55 | 0 | .delay = delay, |
56 | 0 | .end_delay = end_delay, |
57 | 0 | .fill = fill, |
58 | 0 | .iteration_start = iteration_start, |
59 | 0 | .iterations = iterations, |
60 | 0 | .duration = duration, |
61 | 0 | .direction = direction, |
62 | 0 | .easing = easing, |
63 | 0 | }; |
64 | 0 | } |
65 | | |
66 | | // https://www.w3.org/TR/web-animations-1/#dom-animationeffect-gettiming |
67 | | EffectTiming AnimationEffect::get_timing() const |
68 | 0 | { |
69 | | // 1. Returns the specified timing properties for this animation effect. |
70 | 0 | return { |
71 | 0 | .delay = m_start_delay, |
72 | 0 | .end_delay = m_end_delay, |
73 | 0 | .fill = m_fill_mode, |
74 | 0 | .iteration_start = m_iteration_start, |
75 | 0 | .iterations = m_iteration_count, |
76 | 0 | .duration = m_iteration_duration, |
77 | 0 | .direction = m_playback_direction, |
78 | 0 | .easing = m_timing_function.to_string(), |
79 | 0 | }; |
80 | 0 | } |
81 | | |
82 | | // https://www.w3.org/TR/web-animations-1/#dom-animationeffect-getcomputedtiming |
83 | | ComputedEffectTiming AnimationEffect::get_computed_timing() const |
84 | 0 | { |
85 | | // 1. Returns the calculated timing properties for this animation effect. |
86 | | |
87 | | // Note: Although some of the attributes of the object returned by getTiming() and getComputedTiming() are common, |
88 | | // their values may differ in the following ways: |
89 | | |
90 | | // - duration: while getTiming() may return the string auto, getComputedTiming() must return a number |
91 | | // corresponding to the calculated value of the iteration duration as defined in the description of the |
92 | | // duration member of the EffectTiming interface. |
93 | | // |
94 | | // In this level of the specification, that simply means that an auto value is replaced by zero. |
95 | 0 | auto duration = m_iteration_duration.has<String>() ? 0.0 : m_iteration_duration.get<double>(); |
96 | | |
97 | | // - fill: likewise, while getTiming() may return the string auto, getComputedTiming() must return the specific |
98 | | // FillMode used for timing calculations as defined in the description of the fill member of the EffectTiming |
99 | | // interface. |
100 | | // |
101 | | // In this level of the specification, that simply means that an auto value is replaced by the none FillMode. |
102 | 0 | auto fill = m_fill_mode == Bindings::FillMode::Auto ? Bindings::FillMode::None : m_fill_mode; |
103 | |
|
104 | 0 | return { |
105 | 0 | { |
106 | 0 | .delay = m_start_delay, |
107 | 0 | .end_delay = m_end_delay, |
108 | 0 | .fill = fill, |
109 | 0 | .iteration_start = m_iteration_start, |
110 | 0 | .iterations = m_iteration_count, |
111 | 0 | .duration = duration, |
112 | 0 | .direction = m_playback_direction, |
113 | 0 | .easing = m_timing_function.to_string(), |
114 | 0 | }, |
115 | |
|
116 | 0 | end_time(), |
117 | 0 | active_duration(), |
118 | 0 | local_time(), |
119 | 0 | transformed_progress(), |
120 | 0 | current_iteration(), |
121 | 0 | }; |
122 | 0 | } |
123 | | |
124 | | // https://www.w3.org/TR/web-animations-1/#dom-animationeffect-updatetiming |
125 | | // https://www.w3.org/TR/web-animations-1/#update-the-timing-properties-of-an-animation-effect |
126 | | WebIDL::ExceptionOr<void> AnimationEffect::update_timing(OptionalEffectTiming timing) |
127 | 0 | { |
128 | | // 1. If the iterationStart member of input exists and is less than zero, throw a TypeError and abort this |
129 | | // procedure. |
130 | 0 | if (timing.iteration_start.has_value() && timing.iteration_start.value() < 0.0) |
131 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid iteration start value"sv }; |
132 | | |
133 | | // 2. If the iterations member of input exists, and is less than zero or is the value NaN, throw a TypeError and |
134 | | // abort this procedure. |
135 | 0 | if (timing.iterations.has_value() && (timing.iterations.value() < 0.0 || isnan(timing.iterations.value()))) |
136 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid iteration count value"sv }; |
137 | | |
138 | | // 3. If the duration member of input exists, and is less than zero or is the value NaN, throw a TypeError and |
139 | | // abort this procedure. |
140 | | // Note: "auto", the only valid string value, is treated as 0. |
141 | 0 | auto& duration = timing.duration; |
142 | 0 | auto has_valid_duration_value = [&] { |
143 | 0 | if (!duration.has_value()) |
144 | 0 | return true; |
145 | 0 | if (duration->has<double>() && (duration->get<double>() < 0.0 || isnan(duration->get<double>()))) |
146 | 0 | return false; |
147 | 0 | if (duration->has<String>() && (duration->get<String>() != "auto")) |
148 | 0 | return false; |
149 | 0 | return true; |
150 | 0 | }(); |
151 | 0 | if (!has_valid_duration_value) |
152 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid duration value"sv }; |
153 | | |
154 | | // 4. If the easing member of input exists but cannot be parsed using the <easing-function> production |
155 | | // [CSS-EASING-1], throw a TypeError and abort this procedure. |
156 | 0 | RefPtr<CSS::CSSStyleValue const> easing_value; |
157 | 0 | if (timing.easing.has_value()) { |
158 | 0 | easing_value = parse_easing_string(realm(), timing.easing.value()); |
159 | 0 | if (!easing_value) |
160 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid easing function"sv }; |
161 | 0 | VERIFY(easing_value->is_easing()); |
162 | 0 | } |
163 | | |
164 | | // 5. Assign each member that exists in input to the corresponding timing property of effect as follows: |
165 | | |
166 | | // - delay → start delay |
167 | 0 | if (timing.delay.has_value()) |
168 | 0 | m_start_delay = timing.delay.value(); |
169 | | |
170 | | // - endDelay → end delay |
171 | 0 | if (timing.end_delay.has_value()) |
172 | 0 | m_end_delay = timing.end_delay.value(); |
173 | | |
174 | | // - fill → fill mode |
175 | 0 | if (timing.fill.has_value()) |
176 | 0 | m_fill_mode = timing.fill.value(); |
177 | | |
178 | | // - iterationStart → iteration start |
179 | 0 | if (timing.iteration_start.has_value()) |
180 | 0 | m_iteration_start = timing.iteration_start.value(); |
181 | | |
182 | | // - iterations → iteration count |
183 | 0 | if (timing.iterations.has_value()) |
184 | 0 | m_iteration_count = timing.iterations.value(); |
185 | | |
186 | | // - duration → iteration duration |
187 | 0 | if (timing.duration.has_value()) |
188 | 0 | m_iteration_duration = timing.duration.value(); |
189 | | |
190 | | // - direction → playback direction |
191 | 0 | if (timing.direction.has_value()) |
192 | 0 | m_playback_direction = timing.direction.value(); |
193 | | |
194 | | // - easing → timing function |
195 | 0 | if (easing_value) |
196 | 0 | m_timing_function = easing_value->as_easing().function(); |
197 | |
|
198 | 0 | if (auto animation = m_associated_animation) |
199 | 0 | animation->effect_timing_changed({}); |
200 | |
|
201 | 0 | return {}; |
202 | 0 | } |
203 | | |
204 | | void AnimationEffect::set_associated_animation(JS::GCPtr<Animation> value) |
205 | 0 | { |
206 | 0 | m_associated_animation = value; |
207 | 0 | } |
208 | | |
209 | | // https://www.w3.org/TR/web-animations-1/#animation-direction |
210 | | AnimationDirection AnimationEffect::animation_direction() const |
211 | 0 | { |
212 | | // "backwards" if the effect is associated with an animation and the associated animation’s playback rate is less |
213 | | // than zero; in all other cases, the animation direction is "forwards". |
214 | 0 | if (m_associated_animation && m_associated_animation->playback_rate() < 0.0) |
215 | 0 | return AnimationDirection::Backwards; |
216 | 0 | return AnimationDirection::Forwards; |
217 | 0 | } |
218 | | |
219 | | // https://www.w3.org/TR/web-animations-1/#end-time |
220 | | double AnimationEffect::end_time() const |
221 | 0 | { |
222 | | // 1. The end time of an animation effect is the result of evaluating |
223 | | // max(start delay + active duration + end delay, 0). |
224 | 0 | return max(m_start_delay + active_duration() + m_end_delay, 0.0); |
225 | 0 | } |
226 | | |
227 | | // https://www.w3.org/TR/web-animations-1/#local-time |
228 | | Optional<double> AnimationEffect::local_time() const |
229 | 0 | { |
230 | | // The local time of an animation effect at a given moment is based on the first matching condition from the |
231 | | // following: |
232 | | |
233 | | // -> If the animation effect is associated with an animation, |
234 | 0 | if (m_associated_animation) { |
235 | | // the local time is the current time of the animation. |
236 | 0 | return m_associated_animation->current_time(); |
237 | 0 | } |
238 | | |
239 | | // -> Otherwise, |
240 | | // the local time is unresolved. |
241 | 0 | return {}; |
242 | 0 | } |
243 | | |
244 | | // https://www.w3.org/TR/web-animations-1/#active-duration |
245 | | double AnimationEffect::active_duration() const |
246 | 0 | { |
247 | | // The active duration is calculated as follows: |
248 | | // active duration = iteration duration × iteration count |
249 | | // If either the iteration duration or iteration count are zero, the active duration is zero. This clarification is |
250 | | // needed since the result of infinity multiplied by zero is undefined according to IEEE 754-2008. |
251 | 0 | if (m_iteration_duration.has<String>() || m_iteration_duration.get<double>() == 0.0 || m_iteration_count == 0.0) |
252 | 0 | return 0.0; |
253 | | |
254 | 0 | return m_iteration_duration.get<double>() * m_iteration_count; |
255 | 0 | } |
256 | | |
257 | | Optional<double> AnimationEffect::active_time() const |
258 | 0 | { |
259 | 0 | return active_time_using_fill(m_fill_mode); |
260 | 0 | } |
261 | | |
262 | | // https://www.w3.org/TR/web-animations-1/#calculating-the-active-time |
263 | | Optional<double> AnimationEffect::active_time_using_fill(Bindings::FillMode fill_mode) const |
264 | 0 | { |
265 | | // The active time is based on the local time and start delay. However, it is only defined when the animation effect |
266 | | // should produce an output and hence depends on its fill mode and phase as follows, |
267 | | |
268 | | // -> If the animation effect is in the before phase, |
269 | 0 | if (is_in_the_before_phase()) { |
270 | | // The result depends on the first matching condition from the following, |
271 | | |
272 | | // -> If the fill mode is backwards or both, |
273 | 0 | if (fill_mode == Bindings::FillMode::Backwards || fill_mode == Bindings::FillMode::Both) { |
274 | | // Return the result of evaluating max(local time - start delay, 0). |
275 | 0 | return max(local_time().value() - m_start_delay, 0.0); |
276 | 0 | } |
277 | | |
278 | | // -> Otherwise, |
279 | | // Return an unresolved time value. |
280 | 0 | return {}; |
281 | 0 | } |
282 | | |
283 | | // -> If the animation effect is in the active phase, |
284 | 0 | if (is_in_the_active_phase()) { |
285 | | // Return the result of evaluating local time - start delay. |
286 | 0 | return local_time().value() - m_start_delay; |
287 | 0 | } |
288 | | |
289 | | // -> If the animation effect is in the after phase, |
290 | 0 | if (is_in_the_after_phase()) { |
291 | | // The result depends on the first matching condition from the following, |
292 | | |
293 | | // -> If the fill mode is forwards or both, |
294 | 0 | if (fill_mode == Bindings::FillMode::Forwards || fill_mode == Bindings::FillMode::Both) { |
295 | | // Return the result of evaluating max(min(local time - start delay, active duration), 0). |
296 | 0 | return max(min(local_time().value() - m_start_delay, active_duration()), 0.0); |
297 | 0 | } |
298 | | |
299 | | // -> Otherwise, |
300 | | // Return an unresolved time value. |
301 | 0 | return {}; |
302 | 0 | } |
303 | | |
304 | | // -> Otherwise (the local time is unresolved), |
305 | | // Return an unresolved time value. |
306 | 0 | return {}; |
307 | 0 | } |
308 | | |
309 | | // https://www.w3.org/TR/web-animations-1/#in-play |
310 | | bool AnimationEffect::is_in_play() const |
311 | 0 | { |
312 | | // An animation effect is in play if all of the following conditions are met: |
313 | | // - the animation effect is in the active phase, and |
314 | | // - the animation effect is associated with an animation that is not finished. |
315 | 0 | return is_in_the_active_phase() && m_associated_animation && !m_associated_animation->is_finished(); |
316 | 0 | } |
317 | | |
318 | | // https://www.w3.org/TR/web-animations-1/#current |
319 | | bool AnimationEffect::is_current() const |
320 | 0 | { |
321 | | // An animation effect is current if any of the following conditions are true: |
322 | | |
323 | | // - the animation effect is in play, or |
324 | 0 | if (is_in_play()) |
325 | 0 | return true; |
326 | | |
327 | 0 | if (auto animation = m_associated_animation) { |
328 | 0 | auto playback_rate = animation->playback_rate(); |
329 | | |
330 | | // - the animation effect is associated with an animation with a playback rate > 0 and the animation effect is |
331 | | // in the before phase, or |
332 | 0 | if (playback_rate > 0.0 && is_in_the_before_phase()) |
333 | 0 | return true; |
334 | | |
335 | | // - the animation effect is associated with an animation with a playback rate < 0 and the animation effect is |
336 | | // in the after phase, or |
337 | 0 | if (playback_rate < 0.0 && is_in_the_after_phase()) |
338 | 0 | return true; |
339 | | |
340 | | // - the animation effect is associated with an animation not in the idle play state with a non-null associated |
341 | | // timeline that is not monotonically increasing. |
342 | 0 | if (animation->play_state() != Bindings::AnimationPlayState::Idle && animation->timeline() && !animation->timeline()->is_monotonically_increasing()) |
343 | 0 | return true; |
344 | 0 | } |
345 | | |
346 | 0 | return false; |
347 | 0 | } |
348 | | |
349 | | // https://www.w3.org/TR/web-animations-1/#in-effect |
350 | | bool AnimationEffect::is_in_effect() const |
351 | 0 | { |
352 | | // An animation effect is in effect if its active time, as calculated according to the procedure in |
353 | | // §4.8.3.1 Calculating the active time, is not unresolved. |
354 | 0 | return active_time().has_value(); |
355 | 0 | } |
356 | | |
357 | | // https://www.w3.org/TR/web-animations-1/#before-active-boundary-time |
358 | | double AnimationEffect::before_active_boundary_time() const |
359 | 0 | { |
360 | | // max(min(start delay, end time), 0) |
361 | 0 | return max(min(m_start_delay, end_time()), 0.0); |
362 | 0 | } |
363 | | |
364 | | // https://www.w3.org/TR/web-animations-1/#active-after-boundary-time |
365 | | double AnimationEffect::after_active_boundary_time() const |
366 | 0 | { |
367 | | // max(min(start delay + active duration, end time), 0) |
368 | 0 | return max(min(m_start_delay + active_duration(), end_time()), 0.0); |
369 | 0 | } |
370 | | |
371 | | // https://www.w3.org/TR/web-animations-1/#animation-effect-before-phase |
372 | | bool AnimationEffect::is_in_the_before_phase() const |
373 | 0 | { |
374 | | // An animation effect is in the before phase if the animation effect’s local time is not unresolved and either of |
375 | | // the following conditions are met: |
376 | 0 | auto local_time = this->local_time(); |
377 | 0 | if (!local_time.has_value()) |
378 | 0 | return false; |
379 | | |
380 | | // - the local time is less than the before-active boundary time, or |
381 | 0 | auto before_active_boundary_time = this->before_active_boundary_time(); |
382 | 0 | if (local_time.value() < before_active_boundary_time) |
383 | 0 | return true; |
384 | | |
385 | | // - the animation direction is "backwards" and the local time is equal to the before-active boundary time. |
386 | 0 | return animation_direction() == AnimationDirection::Backwards && local_time.value() == before_active_boundary_time; |
387 | 0 | } |
388 | | |
389 | | // https://www.w3.org/TR/web-animations-1/#animation-effect-after-phase |
390 | | bool AnimationEffect::is_in_the_after_phase() const |
391 | 0 | { |
392 | | // An animation effect is in the after phase if the animation effect’s local time is not unresolved and either of |
393 | | // the following conditions are met: |
394 | 0 | auto local_time = this->local_time(); |
395 | 0 | if (!local_time.has_value()) |
396 | 0 | return false; |
397 | | |
398 | | // - the local time is greater than the active-after boundary time, or |
399 | 0 | auto after_active_boundary_time = this->after_active_boundary_time(); |
400 | 0 | if (local_time.value() > after_active_boundary_time) |
401 | 0 | return true; |
402 | | |
403 | | // - the animation direction is "forwards" and the local time is equal to the active-after boundary time. |
404 | 0 | return animation_direction() == AnimationDirection::Forwards && local_time.value() == after_active_boundary_time; |
405 | 0 | } |
406 | | |
407 | | // https://www.w3.org/TR/web-animations-1/#animation-effect-active-phase |
408 | | bool AnimationEffect::is_in_the_active_phase() const |
409 | 0 | { |
410 | | // An animation effect is in the active phase if the animation effect’s local time is not unresolved and it is not |
411 | | // in either the before phase nor the after phase. |
412 | 0 | return local_time().has_value() && !is_in_the_before_phase() && !is_in_the_after_phase(); |
413 | 0 | } |
414 | | |
415 | | // https://www.w3.org/TR/web-animations-1/#animation-effect-idle-phase |
416 | | bool AnimationEffect::is_in_the_idle_phase() const |
417 | 0 | { |
418 | | // It is often convenient to refer to the case when an animation effect is in none of the above phases as being in |
419 | | // the idle phase |
420 | 0 | return !is_in_the_before_phase() && !is_in_the_active_phase() && !is_in_the_after_phase(); |
421 | 0 | } |
422 | | |
423 | | AnimationEffect::Phase AnimationEffect::phase() const |
424 | 0 | { |
425 | | // This is a convenience method that returns the phase of the animation effect, to avoid having to call all of the |
426 | | // phase functions separately. |
427 | 0 | auto local_time = this->local_time(); |
428 | 0 | if (!local_time.has_value()) |
429 | 0 | return Phase::Idle; |
430 | | |
431 | 0 | auto before_active_boundary_time = this->before_active_boundary_time(); |
432 | | // - the local time is less than the before-active boundary time, or |
433 | | // - the animation direction is "backwards" and the local time is equal to the before-active boundary time. |
434 | 0 | if (local_time.value() < before_active_boundary_time || (animation_direction() == AnimationDirection::Backwards && local_time.value() == before_active_boundary_time)) |
435 | 0 | return Phase::Before; |
436 | | |
437 | 0 | auto after_active_boundary_time = this->after_active_boundary_time(); |
438 | | // - the local time is greater than the active-after boundary time, or |
439 | | // - the animation direction is "forwards" and the local time is equal to the active-after boundary time. |
440 | 0 | if (local_time.value() > after_active_boundary_time || (animation_direction() == AnimationDirection::Forwards && local_time.value() == after_active_boundary_time)) |
441 | 0 | return Phase::After; |
442 | | |
443 | | // - An animation effect is in the active phase if the animation effect’s local time is not unresolved and it is not |
444 | | // - in either the before phase nor the after phase. |
445 | 0 | return Phase::Active; |
446 | 0 | } |
447 | | |
448 | | // https://www.w3.org/TR/web-animations-1/#overall-progress |
449 | | Optional<double> AnimationEffect::overall_progress() const |
450 | 0 | { |
451 | | // 1. If the active time is unresolved, return unresolved. |
452 | 0 | auto active_time = this->active_time(); |
453 | 0 | if (!active_time.has_value()) |
454 | 0 | return {}; |
455 | | |
456 | | // 2. Calculate an initial value for overall progress based on the first matching condition from below, |
457 | 0 | double overall_progress; |
458 | | |
459 | | // -> If the iteration duration is zero, |
460 | 0 | if (m_iteration_duration.has<String>() || m_iteration_duration.get<double>() == 0.0) { |
461 | | // If the animation effect is in the before phase, let overall progress be zero, otherwise, let it be equal to |
462 | | // the iteration count. |
463 | 0 | if (is_in_the_before_phase()) |
464 | 0 | overall_progress = 0.0; |
465 | 0 | else |
466 | 0 | overall_progress = m_iteration_count; |
467 | 0 | } |
468 | | // Otherwise, |
469 | 0 | else { |
470 | | // Let overall progress be the result of calculating active time / iteration duration. |
471 | 0 | overall_progress = active_time.value() / m_iteration_duration.get<double>(); |
472 | 0 | } |
473 | | |
474 | | // 3. Return the result of calculating overall progress + iteration start. |
475 | 0 | return overall_progress + m_iteration_start; |
476 | 0 | } |
477 | | |
478 | | // https://www.w3.org/TR/web-animations-1/#directed-progress |
479 | | Optional<double> AnimationEffect::directed_progress() const |
480 | 0 | { |
481 | | // 1. If the simple iteration progress is unresolved, return unresolved. |
482 | 0 | auto simple_iteration_progress = this->simple_iteration_progress(); |
483 | 0 | if (!simple_iteration_progress.has_value()) |
484 | 0 | return {}; |
485 | | |
486 | | // 2. Calculate the current direction using the first matching condition from the following list: |
487 | 0 | auto current_direction = this->current_direction(); |
488 | | |
489 | | // 3. If the current direction is forwards then return the simple iteration progress. |
490 | 0 | if (current_direction == AnimationDirection::Forwards) |
491 | 0 | return simple_iteration_progress; |
492 | | |
493 | | // Otherwise, return 1.0 - simple iteration progress. |
494 | 0 | return 1.0 - simple_iteration_progress.value(); |
495 | 0 | } |
496 | | |
497 | | // https://www.w3.org/TR/web-animations-1/#directed-progress |
498 | | AnimationDirection AnimationEffect::current_direction() const |
499 | 0 | { |
500 | | // 2. Calculate the current direction using the first matching condition from the following list: |
501 | | // -> If playback direction is normal, |
502 | 0 | if (m_playback_direction == Bindings::PlaybackDirection::Normal) { |
503 | | // Let the current direction be forwards. |
504 | 0 | return AnimationDirection::Forwards; |
505 | 0 | } |
506 | | |
507 | | // -> If playback direction is reverse, |
508 | 0 | if (m_playback_direction == Bindings::PlaybackDirection::Reverse) { |
509 | | // Let the current direction be reverse. |
510 | 0 | return AnimationDirection::Backwards; |
511 | 0 | } |
512 | | // -> Otherwise, |
513 | | // 1. Let d be the current iteration. |
514 | 0 | double d = current_iteration().value(); |
515 | | |
516 | | // 2. If playback direction is alternate-reverse increment d by 1. |
517 | 0 | if (m_playback_direction == Bindings::PlaybackDirection::AlternateReverse) |
518 | 0 | d += 1.0; |
519 | | |
520 | | // 3. If d % 2 == 0, let the current direction be forwards, otherwise let the current direction be reverse. If d |
521 | | // is infinity, let the current direction be forwards. |
522 | 0 | if (isinf(d)) |
523 | 0 | return AnimationDirection::Forwards; |
524 | 0 | if (fmod(d, 2.0) == 0.0) |
525 | 0 | return AnimationDirection::Forwards; |
526 | 0 | return AnimationDirection::Backwards; |
527 | 0 | } |
528 | | |
529 | | // https://www.w3.org/TR/web-animations-1/#simple-iteration-progress |
530 | | Optional<double> AnimationEffect::simple_iteration_progress() const |
531 | 0 | { |
532 | | // 1. If the overall progress is unresolved, return unresolved. |
533 | 0 | auto overall_progress = this->overall_progress(); |
534 | 0 | if (!overall_progress.has_value()) |
535 | 0 | return {}; |
536 | | |
537 | | // 2. If overall progress is infinity, let the simple iteration progress be iteration start % 1.0, otherwise, let |
538 | | // the simple iteration progress be overall progress % 1.0. |
539 | 0 | double simple_iteration_progress = isinf(overall_progress.value()) ? fmod(m_iteration_start, 1.0) : fmod(overall_progress.value(), 1.0); |
540 | | |
541 | | // 3. If all of the following conditions are true, |
542 | | // - the simple iteration progress calculated above is zero, and |
543 | | // - the animation effect is in the active phase or the after phase, and |
544 | | // - the active time is equal to the active duration, and |
545 | | // - the iteration count is not equal to zero. |
546 | 0 | auto active_time = this->active_time(); |
547 | 0 | if (simple_iteration_progress == 0.0 && (is_in_the_active_phase() || is_in_the_after_phase()) && active_time.has_value() && active_time.value() == active_duration() && m_iteration_count != 0.0) { |
548 | | // let the simple iteration progress be 1.0. |
549 | 0 | simple_iteration_progress = 1.0; |
550 | 0 | } |
551 | | |
552 | | // 4. Return simple iteration progress. |
553 | 0 | return simple_iteration_progress; |
554 | 0 | } |
555 | | |
556 | | // https://www.w3.org/TR/web-animations-1/#current-iteration |
557 | | Optional<double> AnimationEffect::current_iteration() const |
558 | 0 | { |
559 | | // 1. If the active time is unresolved, return unresolved. |
560 | 0 | auto active_time = this->active_time(); |
561 | 0 | if (!active_time.has_value()) |
562 | 0 | return {}; |
563 | | |
564 | | // 2. If the animation effect is in the after phase and the iteration count is infinity, return infinity. |
565 | 0 | if (is_in_the_after_phase() && isinf(m_iteration_count)) |
566 | 0 | return m_iteration_count; |
567 | | |
568 | | // 3. If the simple iteration progress is 1.0, return floor(overall progress) - 1. |
569 | 0 | auto simple_iteration_progress = this->simple_iteration_progress(); |
570 | 0 | if (simple_iteration_progress.has_value() && simple_iteration_progress.value() == 1.0) |
571 | 0 | return floor(overall_progress().value()) - 1.0; |
572 | | |
573 | | // 4. Otherwise, return floor(overall progress). |
574 | 0 | return floor(overall_progress().value()); |
575 | 0 | } |
576 | | |
577 | | // https://www.w3.org/TR/web-animations-1/#transformed-progress |
578 | | Optional<double> AnimationEffect::transformed_progress() const |
579 | 0 | { |
580 | | // 1. If the directed progress is unresolved, return unresolved. |
581 | 0 | auto directed_progress = this->directed_progress(); |
582 | 0 | if (!directed_progress.has_value()) |
583 | 0 | return {}; |
584 | | |
585 | | // 2. Calculate the value of the before flag as follows: |
586 | | |
587 | | // 1. Determine the current direction using the procedure defined in §4.9.1 Calculating the directed progress. |
588 | 0 | auto current_direction = this->current_direction(); |
589 | | |
590 | | // 2. If the current direction is forwards, let going forwards be true, otherwise it is false. |
591 | 0 | auto going_forwards = current_direction == AnimationDirection::Forwards; |
592 | | |
593 | | // 3. The before flag is set if the animation effect is in the before phase and going forwards is true; or if the animation effect |
594 | | // is in the after phase and going forwards is false. |
595 | 0 | auto before_flag = (is_in_the_before_phase() && going_forwards) || (is_in_the_after_phase() && !going_forwards); |
596 | | |
597 | | // 3. Return the result of evaluating the animation effect’s timing function passing directed progress as the input progress value and |
598 | | // before flag as the before flag. |
599 | 0 | return m_timing_function.evaluate_at(directed_progress.value(), before_flag); |
600 | 0 | } |
601 | | |
602 | | RefPtr<CSS::CSSStyleValue const> AnimationEffect::parse_easing_string(JS::Realm& realm, StringView value) |
603 | 0 | { |
604 | 0 | auto parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), value); |
605 | |
|
606 | 0 | if (auto style_value = parser.parse_as_css_value(CSS::PropertyID::AnimationTimingFunction)) { |
607 | 0 | if (style_value->is_easing()) |
608 | 0 | return style_value; |
609 | 0 | } |
610 | | |
611 | 0 | return {}; |
612 | 0 | } |
613 | | |
614 | | AnimationEffect::AnimationEffect(JS::Realm& realm) |
615 | 0 | : Bindings::PlatformObject(realm) |
616 | 0 | { |
617 | 0 | } |
618 | | |
619 | | void AnimationEffect::initialize(JS::Realm& realm) |
620 | 0 | { |
621 | 0 | Base::initialize(realm); |
622 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(AnimationEffect); |
623 | 0 | } |
624 | | |
625 | | void AnimationEffect::visit_edges(JS::Cell::Visitor& visitor) |
626 | 0 | { |
627 | 0 | Base::visit_edges(visitor); |
628 | 0 | visitor.visit(m_associated_animation); |
629 | 0 | } |
630 | | |
631 | | } |