Coverage Report

Created: 2025-09-05 06:52

/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
}