mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 21:02:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			615 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			615 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | ||
|  * Copyright (c) 2023-2024, Matthew Olsson <mattco@serenityos.org>
 | ||
|  *
 | ||
|  * SPDX-License-Identifier: BSD-2-Clause
 | ||
|  */
 | ||
| 
 | ||
| #include <LibJS/Runtime/VM.h>
 | ||
| #include <LibWeb/Animations/Animation.h>
 | ||
| #include <LibWeb/Animations/AnimationEffect.h>
 | ||
| #include <LibWeb/Animations/AnimationTimeline.h>
 | ||
| #include <LibWeb/Bindings/Intrinsics.h>
 | ||
| #include <LibWeb/CSS/Parser/Parser.h>
 | ||
| #include <LibWeb/DOM/Element.h>
 | ||
| #include <LibWeb/WebIDL/ExceptionOr.h>
 | ||
| 
 | ||
| namespace Web::Animations {
 | ||
| 
 | ||
| JS_DEFINE_ALLOCATOR(AnimationEffect);
 | ||
| 
 | ||
| Bindings::FillMode css_fill_mode_to_bindings_fill_mode(CSS::AnimationFillMode mode)
 | ||
| {
 | ||
|     switch (mode) {
 | ||
|     case CSS::AnimationFillMode::Backwards:
 | ||
|         return Bindings::FillMode::Backwards;
 | ||
|     case CSS::AnimationFillMode::Both:
 | ||
|         return Bindings::FillMode::Both;
 | ||
|     case CSS::AnimationFillMode::Forwards:
 | ||
|         return Bindings::FillMode::Forwards;
 | ||
|     case CSS::AnimationFillMode::None:
 | ||
|         return Bindings::FillMode::None;
 | ||
|     default:
 | ||
|         VERIFY_NOT_REACHED();
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| Bindings::PlaybackDirection css_animation_direction_to_bindings_playback_direction(CSS::AnimationDirection direction)
 | ||
| {
 | ||
|     switch (direction) {
 | ||
|     case CSS::AnimationDirection::Alternate:
 | ||
|         return Bindings::PlaybackDirection::Alternate;
 | ||
|     case CSS::AnimationDirection::AlternateReverse:
 | ||
|         return Bindings::PlaybackDirection::AlternateReverse;
 | ||
|     case CSS::AnimationDirection::Normal:
 | ||
|         return Bindings::PlaybackDirection::Normal;
 | ||
|     case CSS::AnimationDirection::Reverse:
 | ||
|         return Bindings::PlaybackDirection::Reverse;
 | ||
|     default:
 | ||
|         VERIFY_NOT_REACHED();
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| JS::NonnullGCPtr<AnimationEffect> AnimationEffect::create(JS::Realm& realm)
 | ||
| {
 | ||
|     return realm.heap().allocate<AnimationEffect>(realm, realm);
 | ||
| }
 | ||
| 
 | ||
| OptionalEffectTiming EffectTiming::to_optional_effect_timing() const
 | ||
| {
 | ||
|     return {
 | ||
|         .delay = delay,
 | ||
|         .end_delay = end_delay,
 | ||
|         .fill = fill,
 | ||
|         .iteration_start = iteration_start,
 | ||
|         .iterations = iterations,
 | ||
|         .duration = duration,
 | ||
|         .direction = direction,
 | ||
|         .easing = easing,
 | ||
|     };
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#dom-animationeffect-gettiming
 | ||
| EffectTiming AnimationEffect::get_timing() const
 | ||
| {
 | ||
|     // 1. Returns the specified timing properties for this animation effect.
 | ||
|     return {
 | ||
|         .delay = m_start_delay,
 | ||
|         .end_delay = m_end_delay,
 | ||
|         .fill = m_fill_mode,
 | ||
|         .iteration_start = m_iteration_start,
 | ||
|         .iterations = m_iteration_count,
 | ||
|         .duration = m_iteration_duration,
 | ||
|         .direction = m_playback_direction,
 | ||
|         .easing = m_easing_function,
 | ||
|     };
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#dom-animationeffect-getcomputedtiming
 | ||
| ComputedEffectTiming AnimationEffect::get_computed_timing() const
 | ||
| {
 | ||
|     // 1. Returns the calculated timing properties for this animation effect.
 | ||
| 
 | ||
|     // Note: Although some of the attributes of the object returned by getTiming() and getComputedTiming() are common,
 | ||
|     //       their values may differ in the following ways:
 | ||
| 
 | ||
|     //     - duration: while getTiming() may return the string auto, getComputedTiming() must return a number
 | ||
|     //       corresponding to the calculated value of the iteration duration as defined in the description of the
 | ||
|     //       duration member of the EffectTiming interface.
 | ||
|     //
 | ||
|     //       In this level of the specification, that simply means that an auto value is replaced by zero.
 | ||
|     auto duration = m_iteration_duration.has<String>() ? 0.0 : m_iteration_duration.get<double>();
 | ||
| 
 | ||
|     //     - fill: likewise, while getTiming() may return the string auto, getComputedTiming() must return the specific
 | ||
|     //       FillMode used for timing calculations as defined in the description of the fill member of the EffectTiming
 | ||
|     //       interface.
 | ||
|     //
 | ||
|     //       In this level of the specification, that simply means that an auto value is replaced by the none FillMode.
 | ||
|     auto fill = m_fill_mode == Bindings::FillMode::Auto ? Bindings::FillMode::None : m_fill_mode;
 | ||
| 
 | ||
|     return {
 | ||
|         {
 | ||
|             .delay = m_start_delay,
 | ||
|             .end_delay = m_end_delay,
 | ||
|             .fill = fill,
 | ||
|             .iteration_start = m_iteration_start,
 | ||
|             .iterations = m_iteration_count,
 | ||
|             .duration = duration,
 | ||
|             .direction = m_playback_direction,
 | ||
|             .easing = m_easing_function,
 | ||
|         },
 | ||
| 
 | ||
|         end_time(),
 | ||
|         active_duration(),
 | ||
|         local_time(),
 | ||
|         transformed_progress(),
 | ||
|         current_iteration(),
 | ||
|     };
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#dom-animationeffect-updatetiming
 | ||
| // https://www.w3.org/TR/web-animations-1/#update-the-timing-properties-of-an-animation-effect
 | ||
| WebIDL::ExceptionOr<void> AnimationEffect::update_timing(OptionalEffectTiming timing)
 | ||
| {
 | ||
|     // 1. If the iterationStart member of input exists and is less than zero, throw a TypeError and abort this
 | ||
|     //    procedure.
 | ||
|     if (timing.iteration_start.has_value() && timing.iteration_start.value() < 0.0)
 | ||
|         return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid iteration start value"sv };
 | ||
| 
 | ||
|     // 2. If the iterations member of input exists, and is less than zero or is the value NaN, throw a TypeError and
 | ||
|     //    abort this procedure.
 | ||
|     if (timing.iterations.has_value() && (timing.iterations.value() < 0.0 || isnan(timing.iterations.value())))
 | ||
|         return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid iteration count value"sv };
 | ||
| 
 | ||
|     // 3. If the duration member of input exists, and is less than zero or is the value NaN, throw a TypeError and
 | ||
|     //    abort this procedure.
 | ||
|     // Note: "auto", the only valid string value, is treated as 0.
 | ||
|     auto& duration = timing.duration;
 | ||
|     if (duration.has_value() && duration->has<double>() && (duration->get<double>() < 0.0 || isnan(duration->get<double>())))
 | ||
|         return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid duration value"sv };
 | ||
| 
 | ||
|     // FIXME:
 | ||
|     // 4. If the easing member of input exists but cannot be parsed using the <easing-function> production
 | ||
|     //    [CSS-EASING-1], throw a TypeError and abort this procedure.
 | ||
| 
 | ||
|     // 5. Assign each member that exists in input to the corresponding timing property of effect as follows:
 | ||
| 
 | ||
|     //    - delay → start delay
 | ||
|     if (timing.delay.has_value())
 | ||
|         m_start_delay = timing.delay.value();
 | ||
| 
 | ||
|     //    - endDelay → end delay
 | ||
|     if (timing.end_delay.has_value())
 | ||
|         m_end_delay = timing.end_delay.value();
 | ||
| 
 | ||
|     //    - fill → fill mode
 | ||
|     if (timing.fill.has_value())
 | ||
|         m_fill_mode = timing.fill.value();
 | ||
| 
 | ||
|     //    - iterationStart → iteration start
 | ||
|     if (timing.iteration_start.has_value())
 | ||
|         m_iteration_start = timing.iteration_start.value();
 | ||
| 
 | ||
|     //    - iterations → iteration count
 | ||
|     if (timing.iterations.has_value())
 | ||
|         m_iteration_count = timing.iterations.value();
 | ||
| 
 | ||
|     //    - duration → iteration duration
 | ||
|     if (timing.duration.has_value())
 | ||
|         m_iteration_duration = timing.duration.value();
 | ||
| 
 | ||
|     //    - direction → playback direction
 | ||
|     if (timing.direction.has_value())
 | ||
|         m_playback_direction = timing.direction.value();
 | ||
| 
 | ||
|     //    - easing → timing function
 | ||
|     if (timing.easing.has_value()) {
 | ||
|         m_easing_function = timing.easing.value();
 | ||
|         if (auto timing_function = parse_easing_string(realm(), m_easing_function)) {
 | ||
|             m_timing_function = TimingFunction::from_easing_style_value(timing_function->as_easing());
 | ||
|         } else {
 | ||
|             m_timing_function = Animations::linear_timing_function;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     if (auto animation = m_associated_animation)
 | ||
|         animation->effect_timing_changed({});
 | ||
| 
 | ||
|     return {};
 | ||
| }
 | ||
| 
 | ||
| void AnimationEffect::set_associated_animation(JS::GCPtr<Animation> value)
 | ||
| {
 | ||
|     m_associated_animation = value;
 | ||
|     if (auto* target = this->target())
 | ||
|         target->invalidate_style();
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#animation-direction
 | ||
| AnimationDirection AnimationEffect::animation_direction() const
 | ||
| {
 | ||
|     // "backwards" if the effect is associated with an animation and the associated animation’s playback rate is less
 | ||
|     // than zero; in all other cases, the animation direction is "forwards".
 | ||
|     if (m_associated_animation && m_associated_animation->playback_rate() < 0.0)
 | ||
|         return AnimationDirection::Backwards;
 | ||
|     return AnimationDirection::Forwards;
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#end-time
 | ||
| double AnimationEffect::end_time() const
 | ||
| {
 | ||
|     // 1. The end time of an animation effect is the result of evaluating
 | ||
|     //    max(start delay + active duration + end delay, 0).
 | ||
|     return max(m_start_delay + active_duration() + m_end_delay, 0.0);
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#local-time
 | ||
| Optional<double> AnimationEffect::local_time() const
 | ||
| {
 | ||
|     // The local time of an animation effect at a given moment is based on the first matching condition from the
 | ||
|     // following:
 | ||
| 
 | ||
|     // -> If the animation effect is associated with an animation,
 | ||
|     if (m_associated_animation) {
 | ||
|         // the local time is the current time of the animation.
 | ||
|         return m_associated_animation->current_time();
 | ||
|     }
 | ||
| 
 | ||
|     // -> Otherwise,
 | ||
|     //    the local time is unresolved.
 | ||
|     return {};
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#active-duration
 | ||
| double AnimationEffect::active_duration() const
 | ||
| {
 | ||
|     // The active duration is calculated as follows:
 | ||
|     //     active duration = iteration duration × iteration count
 | ||
|     // If either the iteration duration or iteration count are zero, the active duration is zero. This clarification is
 | ||
|     // needed since the result of infinity multiplied by zero is undefined according to IEEE 754-2008.
 | ||
|     if (m_iteration_duration.has<String>() || m_iteration_duration.get<double>() == 0.0 || m_iteration_count == 0.0)
 | ||
|         return 0.0;
 | ||
| 
 | ||
|     return m_iteration_duration.get<double>() * m_iteration_count;
 | ||
| }
 | ||
| 
 | ||
| Optional<double> AnimationEffect::active_time() const
 | ||
| {
 | ||
|     return active_time_using_fill(m_fill_mode);
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#calculating-the-active-time
 | ||
| Optional<double> AnimationEffect::active_time_using_fill(Bindings::FillMode fill_mode) const
 | ||
| {
 | ||
|     // The active time is based on the local time and start delay. However, it is only defined when the animation effect
 | ||
|     // should produce an output and hence depends on its fill mode and phase as follows,
 | ||
| 
 | ||
|     // -> If the animation effect is in the before phase,
 | ||
|     if (is_in_the_before_phase()) {
 | ||
|         // The result depends on the first matching condition from the following,
 | ||
| 
 | ||
|         // -> If the fill mode is backwards or both,
 | ||
|         if (fill_mode == Bindings::FillMode::Backwards || fill_mode == Bindings::FillMode::Both) {
 | ||
|             // Return the result of evaluating max(local time - start delay, 0).
 | ||
|             return max(local_time().value() - m_start_delay, 0.0);
 | ||
|         }
 | ||
| 
 | ||
|         // -> Otherwise,
 | ||
|         //    Return an unresolved time value.
 | ||
|         return {};
 | ||
|     }
 | ||
| 
 | ||
|     // -> If the animation effect is in the active phase,
 | ||
|     if (is_in_the_active_phase()) {
 | ||
|         // Return the result of evaluating local time - start delay.
 | ||
|         return local_time().value() - m_start_delay;
 | ||
|     }
 | ||
| 
 | ||
|     // -> If the animation effect is in the after phase,
 | ||
|     if (is_in_the_after_phase()) {
 | ||
|         // The result depends on the first matching condition from the following,
 | ||
| 
 | ||
|         // -> If the fill mode is forwards or both,
 | ||
|         if (fill_mode == Bindings::FillMode::Forwards || fill_mode == Bindings::FillMode::Both) {
 | ||
|             // Return the result of evaluating max(min(local time - start delay, active duration), 0).
 | ||
|             return max(min(local_time().value() - m_start_delay, active_duration()), 0.0);
 | ||
|         }
 | ||
| 
 | ||
|         // -> Otherwise,
 | ||
|         //    Return an unresolved time value.
 | ||
|         return {};
 | ||
|     }
 | ||
| 
 | ||
|     // -> Otherwise (the local time is unresolved),
 | ||
|     //    Return an unresolved time value.
 | ||
|     return {};
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#in-play
 | ||
| bool AnimationEffect::is_in_play() const
 | ||
| {
 | ||
|     // An animation effect is in play if all of the following conditions are met:
 | ||
|     // - the animation effect is in the active phase, and
 | ||
|     // - the animation effect is associated with an animation that is not finished.
 | ||
|     return is_in_the_active_phase() && m_associated_animation && !m_associated_animation->is_finished();
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#current
 | ||
| bool AnimationEffect::is_current() const
 | ||
| {
 | ||
|     // An animation effect is current if any of the following conditions are true:
 | ||
| 
 | ||
|     // - the animation effect is in play, or
 | ||
|     if (is_in_play())
 | ||
|         return true;
 | ||
| 
 | ||
|     if (auto animation = m_associated_animation) {
 | ||
|         auto playback_rate = animation->playback_rate();
 | ||
| 
 | ||
|         // - the animation effect is associated with an animation with a playback rate > 0 and the animation effect is
 | ||
|         //   in the before phase, or
 | ||
|         if (playback_rate > 0.0 && is_in_the_before_phase())
 | ||
|             return true;
 | ||
| 
 | ||
|         // - the animation effect is associated with an animation with a playback rate < 0 and the animation effect is
 | ||
|         //   in the after phase, or
 | ||
|         if (playback_rate < 0.0 && is_in_the_after_phase())
 | ||
|             return true;
 | ||
| 
 | ||
|         // - the animation effect is associated with an animation not in the idle play state with a non-null associated
 | ||
|         //   timeline that is not monotonically increasing.
 | ||
|         if (animation->play_state() != Bindings::AnimationPlayState::Idle && animation->timeline() && !animation->timeline()->is_monotonically_increasing())
 | ||
|             return true;
 | ||
|     }
 | ||
| 
 | ||
|     return false;
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#in-effect
 | ||
| bool AnimationEffect::is_in_effect() const
 | ||
| {
 | ||
|     // An animation effect is in effect if its active time, as calculated according to the procedure in
 | ||
|     // §4.8.3.1 Calculating the active time, is not unresolved.
 | ||
|     return active_time().has_value();
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#before-active-boundary-time
 | ||
| double AnimationEffect::before_active_boundary_time() const
 | ||
| {
 | ||
|     // max(min(start delay, end time), 0)
 | ||
|     return max(min(m_start_delay, end_time()), 0.0);
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#active-after-boundary-time
 | ||
| double AnimationEffect::after_active_boundary_time() const
 | ||
| {
 | ||
|     // max(min(start delay + active duration, end time), 0)
 | ||
|     return max(min(m_start_delay + active_duration(), end_time()), 0.0);
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#animation-effect-before-phase
 | ||
| bool AnimationEffect::is_in_the_before_phase() const
 | ||
| {
 | ||
|     // An animation effect is in the before phase if the animation effect’s local time is not unresolved and either of
 | ||
|     // the following conditions are met:
 | ||
|     auto local_time = this->local_time();
 | ||
|     if (!local_time.has_value())
 | ||
|         return false;
 | ||
| 
 | ||
|     // - the local time is less than the before-active boundary time, or
 | ||
|     auto before_active_boundary_time = this->before_active_boundary_time();
 | ||
|     if (local_time.value() < before_active_boundary_time)
 | ||
|         return true;
 | ||
| 
 | ||
|     // - the animation direction is "backwards" and the local time is equal to the before-active boundary time.
 | ||
|     return animation_direction() == AnimationDirection::Backwards && local_time.value() == before_active_boundary_time;
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#animation-effect-after-phase
 | ||
| bool AnimationEffect::is_in_the_after_phase() const
 | ||
| {
 | ||
|     // An animation effect is in the after phase if the animation effect’s local time is not unresolved and either of
 | ||
|     // the following conditions are met:
 | ||
|     auto local_time = this->local_time();
 | ||
|     if (!local_time.has_value())
 | ||
|         return false;
 | ||
| 
 | ||
|     // - the local time is greater than the active-after boundary time, or
 | ||
|     auto after_active_boundary_time = this->after_active_boundary_time();
 | ||
|     if (local_time.value() > after_active_boundary_time)
 | ||
|         return true;
 | ||
| 
 | ||
|     // - the animation direction is "forwards" and the local time is equal to the active-after boundary time.
 | ||
|     return animation_direction() == AnimationDirection::Forwards && local_time.value() == after_active_boundary_time;
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#animation-effect-active-phase
 | ||
| bool AnimationEffect::is_in_the_active_phase() const
 | ||
| {
 | ||
|     // An animation effect is in the active phase if the animation effect’s local time is not unresolved and it is not
 | ||
|     // in either the before phase nor the after phase.
 | ||
|     return local_time().has_value() && !is_in_the_before_phase() && !is_in_the_after_phase();
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#animation-effect-idle-phase
 | ||
| bool AnimationEffect::is_in_the_idle_phase() const
 | ||
| {
 | ||
|     // It is often convenient to refer to the case when an animation effect is in none of the above phases as being in
 | ||
|     // the idle phase
 | ||
|     return !is_in_the_before_phase() && !is_in_the_active_phase() && !is_in_the_after_phase();
 | ||
| }
 | ||
| 
 | ||
| AnimationEffect::Phase AnimationEffect::phase() const
 | ||
| {
 | ||
|     // This is a convenience method that returns the phase of the animation effect, to avoid having to call all of the
 | ||
|     // phase functions separately.
 | ||
|     // FIXME: There is a lot of duplicated condition checking here which can probably be inlined into this function
 | ||
| 
 | ||
|     if (is_in_the_before_phase())
 | ||
|         return Phase::Before;
 | ||
|     if (is_in_the_active_phase())
 | ||
|         return Phase::Active;
 | ||
|     if (is_in_the_after_phase())
 | ||
|         return Phase::After;
 | ||
|     return Phase::Idle;
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#overall-progress
 | ||
| Optional<double> AnimationEffect::overall_progress() const
 | ||
| {
 | ||
|     // 1. If the active time is unresolved, return unresolved.
 | ||
|     auto active_time = this->active_time();
 | ||
|     if (!active_time.has_value())
 | ||
|         return {};
 | ||
| 
 | ||
|     // 2. Calculate an initial value for overall progress based on the first matching condition from below,
 | ||
|     double overall_progress;
 | ||
| 
 | ||
|     // -> If the iteration duration is zero,
 | ||
|     if (m_iteration_duration.has<String>() || m_iteration_duration.get<double>() == 0.0) {
 | ||
|         // If the animation effect is in the before phase, let overall progress be zero, otherwise, let it be equal to
 | ||
|         // the iteration count.
 | ||
|         if (is_in_the_before_phase())
 | ||
|             overall_progress = 0.0;
 | ||
|         else
 | ||
|             overall_progress = m_iteration_count;
 | ||
|     }
 | ||
|     // Otherwise,
 | ||
|     else {
 | ||
|         // Let overall progress be the result of calculating active time / iteration duration.
 | ||
|         overall_progress = active_time.value() / m_iteration_duration.get<double>();
 | ||
|     }
 | ||
| 
 | ||
|     // 3. Return the result of calculating overall progress + iteration start.
 | ||
|     return overall_progress + m_iteration_start;
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#directed-progress
 | ||
| Optional<double> AnimationEffect::directed_progress() const
 | ||
| {
 | ||
|     // 1. If the simple iteration progress is unresolved, return unresolved.
 | ||
|     auto simple_iteration_progress = this->simple_iteration_progress();
 | ||
|     if (!simple_iteration_progress.has_value())
 | ||
|         return {};
 | ||
| 
 | ||
|     // 2. Calculate the current direction using the first matching condition from the following list:
 | ||
|     auto current_direction = this->current_direction();
 | ||
| 
 | ||
|     // 3. If the current direction is forwards then return the simple iteration progress.
 | ||
|     if (current_direction == AnimationDirection::Forwards)
 | ||
|         return simple_iteration_progress;
 | ||
| 
 | ||
|     //    Otherwise, return 1.0 - simple iteration progress.
 | ||
|     return 1.0 - simple_iteration_progress.value();
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#directed-progress
 | ||
| AnimationDirection AnimationEffect::current_direction() const
 | ||
| {
 | ||
|     // 2. Calculate the current direction using the first matching condition from the following list:
 | ||
|     // -> If playback direction is normal,
 | ||
|     if (m_playback_direction == Bindings::PlaybackDirection::Normal) {
 | ||
|         // Let the current direction be forwards.
 | ||
|         return AnimationDirection::Forwards;
 | ||
|     }
 | ||
| 
 | ||
|     // -> If playback direction is reverse,
 | ||
|     if (m_playback_direction == Bindings::PlaybackDirection::Reverse) {
 | ||
|         // Let the current direction be reverse.
 | ||
|         return AnimationDirection::Backwards;
 | ||
|     }
 | ||
|     // -> Otherwise,
 | ||
|     //    1. Let d be the current iteration.
 | ||
|     double d = current_iteration().value();
 | ||
| 
 | ||
|     //    2. If playback direction is alternate-reverse increment d by 1.
 | ||
|     if (m_playback_direction == Bindings::PlaybackDirection::AlternateReverse)
 | ||
|         d += 1.0;
 | ||
| 
 | ||
|     //    3. If d % 2 == 0, let the current direction be forwards, otherwise let the current direction be reverse. If d
 | ||
|     //       is infinity, let the current direction be forwards.
 | ||
|     if (isinf(d))
 | ||
|         return AnimationDirection::Forwards;
 | ||
|     if (fmod(d, 2.0) == 0.0)
 | ||
|         return AnimationDirection::Forwards;
 | ||
|     return AnimationDirection::Backwards;
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#simple-iteration-progress
 | ||
| Optional<double> AnimationEffect::simple_iteration_progress() const
 | ||
| {
 | ||
|     // 1. If the overall progress is unresolved, return unresolved.
 | ||
|     auto overall_progress = this->overall_progress();
 | ||
|     if (!overall_progress.has_value())
 | ||
|         return {};
 | ||
| 
 | ||
|     // 2. If overall progress is infinity, let the simple iteration progress be iteration start % 1.0, otherwise, let
 | ||
|     //    the simple iteration progress be overall progress % 1.0.
 | ||
|     double simple_iteration_progress = isinf(overall_progress.value()) ? fmod(m_iteration_start, 1.0) : fmod(overall_progress.value(), 1.0);
 | ||
| 
 | ||
|     // 3. If all of the following conditions are true,
 | ||
|     //    - the simple iteration progress calculated above is zero, and
 | ||
|     //    - the animation effect is in the active phase or the after phase, and
 | ||
|     //    - the active time is equal to the active duration, and
 | ||
|     //    - the iteration count is not equal to zero.
 | ||
|     auto active_time = this->active_time();
 | ||
|     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) {
 | ||
|         // let the simple iteration progress be 1.0.
 | ||
|         simple_iteration_progress = 1.0;
 | ||
|     }
 | ||
| 
 | ||
|     // 4. Return simple iteration progress.
 | ||
|     return simple_iteration_progress;
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#current-iteration
 | ||
| Optional<double> AnimationEffect::current_iteration() const
 | ||
| {
 | ||
|     // 1. If the active time is unresolved, return unresolved.
 | ||
|     auto active_time = this->active_time();
 | ||
|     if (!active_time.has_value())
 | ||
|         return {};
 | ||
| 
 | ||
|     // 2. If the animation effect is in the after phase and the iteration count is infinity, return infinity.
 | ||
|     if (is_in_the_after_phase() && isinf(m_iteration_count))
 | ||
|         return m_iteration_count;
 | ||
| 
 | ||
|     // 3. If the simple iteration progress is 1.0, return floor(overall progress) - 1.
 | ||
|     auto simple_iteration_progress = this->simple_iteration_progress();
 | ||
|     if (simple_iteration_progress.has_value() && simple_iteration_progress.value() == 1.0)
 | ||
|         return floor(overall_progress().value()) - 1.0;
 | ||
| 
 | ||
|     // 4. Otherwise, return floor(overall progress).
 | ||
|     return floor(overall_progress().value());
 | ||
| }
 | ||
| 
 | ||
| // https://www.w3.org/TR/web-animations-1/#transformed-progress
 | ||
| Optional<double> AnimationEffect::transformed_progress() const
 | ||
| {
 | ||
|     // 1. If the directed progress is unresolved, return unresolved.
 | ||
|     auto directed_progress = this->directed_progress();
 | ||
|     if (!directed_progress.has_value())
 | ||
|         return {};
 | ||
| 
 | ||
|     // 2. Calculate the value of the before flag as follows:
 | ||
| 
 | ||
|     //    1. Determine the current direction using the procedure defined in §4.9.1 Calculating the directed progress.
 | ||
|     auto current_direction = this->current_direction();
 | ||
| 
 | ||
|     //    2. If the current direction is forwards, let going forwards be true, otherwise it is false.
 | ||
|     auto going_forwards = current_direction == AnimationDirection::Forwards;
 | ||
| 
 | ||
|     //    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
 | ||
|     //       is in the after phase and going forwards is false.
 | ||
|     auto before_flag = (is_in_the_before_phase() && going_forwards) || (is_in_the_after_phase() && !going_forwards);
 | ||
| 
 | ||
|     // 3. Return the result of evaluating the animation effect’s timing function passing directed progress as the input progress value and
 | ||
|     //    before flag as the before flag.
 | ||
|     return m_timing_function(directed_progress.value(), before_flag);
 | ||
| }
 | ||
| 
 | ||
| RefPtr<CSS::StyleValue const> AnimationEffect::parse_easing_string(JS::Realm& realm, StringView value)
 | ||
| {
 | ||
|     auto maybe_parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), value);
 | ||
|     if (maybe_parser.is_error())
 | ||
|         return {};
 | ||
| 
 | ||
|     if (auto style_value = maybe_parser.release_value().parse_as_css_value(CSS::PropertyID::AnimationTimingFunction)) {
 | ||
|         if (style_value->is_easing())
 | ||
|             return style_value;
 | ||
|     }
 | ||
| 
 | ||
|     return {};
 | ||
| }
 | ||
| 
 | ||
| AnimationEffect::AnimationEffect(JS::Realm& realm)
 | ||
|     : Bindings::PlatformObject(realm)
 | ||
| {
 | ||
| }
 | ||
| 
 | ||
| void AnimationEffect::initialize(JS::Realm& realm)
 | ||
| {
 | ||
|     Base::initialize(realm);
 | ||
|     set_prototype(&Bindings::ensure_web_prototype<Bindings::AnimationEffectPrototype>(realm, "AnimationEffect"_fly_string));
 | ||
| }
 | ||
| 
 | ||
| }
 | 
