Sfoglia il codice sorgente

LibWeb: Remove TimingFunction in favor of EasingStyleValue::Function

Now that EasingStyleValue is a lot nicer to use, there isn't much reason
to keep TimingFunction around.
Matthew Olsson 1 anno fa
parent
commit
7950992fc2

+ 0 - 2
Meta/gn/secondary/Userland/Libraries/LibWeb/Animations/BUILD.gn

@@ -16,7 +16,5 @@ source_set("Animations") {
     "DocumentTimeline.h",
     "KeyframeEffect.cpp",
     "KeyframeEffect.h",
-    "TimingFunction.cpp",
-    "TimingFunction.h",
   ]
 }

+ 6 - 7
Userland/Libraries/LibWeb/Animations/AnimationEffect.cpp

@@ -76,7 +76,7 @@ EffectTiming AnimationEffect::get_timing() const
         .iterations = m_iteration_count,
         .duration = m_iteration_duration,
         .direction = m_playback_direction,
-        .easing = m_easing_function,
+        .easing = m_timing_function.to_string(),
     };
 }
 
@@ -111,7 +111,7 @@ ComputedEffectTiming AnimationEffect::get_computed_timing() const
             .iterations = m_iteration_count,
             .duration = duration,
             .direction = m_playback_direction,
-            .easing = m_easing_function,
+            .easing = m_timing_function.to_string(),
         },
 
         end_time(),
@@ -159,6 +159,7 @@ WebIDL::ExceptionOr<void> AnimationEffect::update_timing(OptionalEffectTiming ti
         easing_value = parse_easing_string(realm(), timing.easing.value());
         if (!easing_value)
             return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid easing function"sv };
+        VERIFY(easing_value->is_easing());
     }
 
     // 5. Assign each member that exists in input to the corresponding timing property of effect as follows:
@@ -192,10 +193,8 @@ WebIDL::ExceptionOr<void> AnimationEffect::update_timing(OptionalEffectTiming ti
         m_playback_direction = timing.direction.value();
 
     //    - easing → timing function
-    if (easing_value) {
-        m_easing_function = timing.easing.value();
-        m_timing_function = TimingFunction::from_easing_style_value(easing_value->as_easing());
-    }
+    if (easing_value)
+        m_timing_function = easing_value->as_easing().function();
 
     if (auto animation = m_associated_animation)
         animation->effect_timing_changed({});
@@ -590,7 +589,7 @@ Optional<double> AnimationEffect::transformed_progress() const
 
     // 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);
+    return m_timing_function.evaluate_at(directed_progress.value(), before_flag);
 }
 
 RefPtr<CSS::StyleValue const> AnimationEffect::parse_easing_string(JS::Realm& realm, StringView value)

+ 4 - 10
Userland/Libraries/LibWeb/Animations/AnimationEffect.h

@@ -9,10 +9,10 @@
 #include <AK/Optional.h>
 #include <AK/String.h>
 #include <AK/Variant.h>
-#include <LibWeb/Animations/TimingFunction.h>
 #include <LibWeb/Bindings/AnimationEffectPrototype.h>
 #include <LibWeb/Bindings/PlatformObject.h>
 #include <LibWeb/CSS/Enums.h>
+#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
 
 namespace Web::Animations {
 
@@ -92,11 +92,8 @@ public:
     Bindings::PlaybackDirection playback_direction() const { return m_playback_direction; }
     void set_playback_direction(Bindings::PlaybackDirection playback_direction) { m_playback_direction = playback_direction; }
 
-    String const& easing_function() const { return m_easing_function; }
-    void set_easing_function(String easing_function) { m_easing_function = move(easing_function); }
-
-    TimingFunction const& timing_function() { return m_timing_function; }
-    void set_timing_function(TimingFunction value) { m_timing_function = move(value); }
+    CSS::EasingStyleValue::Function const& timing_function() { return m_timing_function; }
+    void set_timing_function(CSS::EasingStyleValue::Function value) { m_timing_function = move(value); }
 
     JS::GCPtr<Animation> associated_animation() const { return m_associated_animation; }
     void set_associated_animation(JS::GCPtr<Animation> value);
@@ -177,14 +174,11 @@ protected:
     // https://www.w3.org/TR/web-animations-1/#playback-direction
     Bindings::PlaybackDirection m_playback_direction { Bindings::PlaybackDirection::Normal };
 
-    // https://www.w3.org/TR/css-easing-1/#easing-function
-    String m_easing_function { "linear"_string };
-
     // https://www.w3.org/TR/web-animations-1/#animation-associated-effect
     JS::GCPtr<Animation> m_associated_animation {};
 
     // https://www.w3.org/TR/web-animations-1/#time-transformations
-    TimingFunction m_timing_function { LinearTimingFunction {} };
+    CSS::EasingStyleValue::Function m_timing_function { CSS::EasingStyleValue::Linear {} };
 
     // Used for calculating transitions in StyleComputer
     Phase m_previous_phase { Phase::Idle };

+ 0 - 1
Userland/Libraries/LibWeb/Animations/KeyframeEffect.cpp

@@ -762,7 +762,6 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<KeyframeEffect>> KeyframeEffect::construct_
     effect->m_playback_direction = source->m_playback_direction;
 
     //     - timing function.
-    effect->m_easing_function = source->m_easing_function;
     effect->m_timing_function = source->m_timing_function;
 
     return effect;

+ 0 - 212
Userland/Libraries/LibWeb/Animations/TimingFunction.cpp

@@ -1,212 +0,0 @@
-/*
- * Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
- * Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#include <AK/BinarySearch.h>
-#include <LibWeb/Animations/TimingFunction.h>
-#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
-#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
-#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
-#include <math.h>
-
-namespace Web::Animations {
-
-// https://www.w3.org/TR/css-easing-1/#linear-easing-function
-double LinearTimingFunction::operator()(double input_progress, bool) const
-{
-    return input_progress;
-}
-
-static double cubic_bezier_at(double x1, double x2, double t)
-{
-    auto a = 1.0 - 3.0 * x2 + 3.0 * x1;
-    auto b = 3.0 * x2 - 6.0 * x1;
-    auto c = 3.0 * x1;
-
-    auto t2 = t * t;
-    auto t3 = t2 * t;
-
-    return (a * t3) + (b * t2) + (c * t);
-}
-
-// https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
-double CubicBezierTimingFunction::operator()(double input_progress, bool) const
-{
-    // For input progress values outside the range [0, 1], the curve is extended infinitely using tangent of the curve
-    // at the closest endpoint as follows:
-
-    // - For input progress values less than zero,
-    if (input_progress < 0.0) {
-        // 1. If the x value of P1 is greater than zero, use a straight line that passes through P1 and P0 as the
-        //    tangent.
-        if (x1 > 0.0)
-            return y1 / x1 * input_progress;
-
-        // 2. Otherwise, if the x value of P2 is greater than zero, use a straight line that passes through P2 and P0 as
-        //    the tangent.
-        if (x2 > 0.0)
-            return y2 / x2 * input_progress;
-
-        // 3. Otherwise, let the output progress value be zero for all input progress values in the range [-∞, 0).
-        return 0.0;
-    }
-
-    // - For input progress values greater than one,
-    if (input_progress > 1.0) {
-        // 1. If the x value of P2 is less than one, use a straight line that passes through P2 and P3 as the tangent.
-        if (x2 < 1.0)
-            return (1.0 - y2) / (1.0 - x2) * (input_progress - 1.0) + 1.0;
-
-        // 2. Otherwise, if the x value of P1 is less than one, use a straight line that passes through P1 and P3 as the
-        //    tangent.
-        if (x1 < 1.0)
-            return (1.0 - y1) / (1.0 - x1) * (input_progress - 1.0) + 1.0;
-
-        // 3. Otherwise, let the output progress value be one for all input progress values in the range (1, ∞].
-        return 1.0;
-    }
-
-    // Note: The spec does not specify the precise algorithm for calculating values in the range [0, 1]:
-    //       "The evaluation of this curve is covered in many sources such as [FUND-COMP-GRAPHICS]."
-
-    auto x = input_progress;
-
-    auto solve = [&](auto t) {
-        auto x = cubic_bezier_at(x1, x2, t);
-        auto y = cubic_bezier_at(y1, y2, t);
-        return CachedSample { x, y, t };
-    };
-
-    if (m_cached_x_samples.is_empty())
-        m_cached_x_samples.append(solve(0.));
-
-    size_t nearby_index = 0;
-    if (auto found = binary_search(m_cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
-            if (x > sample.x)
-                return 1;
-            if (x < sample.x)
-                return -1;
-            return 0;
-        }))
-        return found->y;
-
-    if (nearby_index == m_cached_x_samples.size() || nearby_index + 1 == m_cached_x_samples.size()) {
-        // Produce more samples until we have enough.
-        auto last_t = m_cached_x_samples.last().t;
-        auto last_x = m_cached_x_samples.last().x;
-        while (last_x <= x && last_t < 1.0) {
-            last_t += 1. / 60.;
-            auto solution = solve(last_t);
-            m_cached_x_samples.append(solution);
-            last_x = solution.x;
-        }
-
-        if (auto found = binary_search(m_cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
-                if (x > sample.x)
-                    return 1;
-                if (x < sample.x)
-                    return -1;
-                return 0;
-            }))
-            return found->y;
-    }
-
-    // We have two samples on either side of the x value we want, so we can linearly interpolate between them.
-    auto& sample1 = m_cached_x_samples[nearby_index];
-    auto& sample2 = m_cached_x_samples[nearby_index + 1];
-    auto factor = (x - sample1.x) / (sample2.x - sample1.x);
-    return sample1.y + factor * (sample2.y - sample1.y);
-}
-
-// https://www.w3.org/TR/css-easing-1/#step-easing-algo
-double StepsTimingFunction::operator()(double input_progress, bool before_flag) const
-{
-    // 1. Calculate the current step as floor(input progress value × steps).
-    auto current_step = floor(input_progress * number_of_steps);
-
-    // 2. If the step position property is one of:
-    //    - jump-start,
-    //    - jump-both,
-    //    increment current step by one.
-    if (jump_at_start)
-        current_step += 1;
-
-    // 3. If both of the following conditions are true:
-    //    - the before flag is set, and
-    //    - input progress value × steps mod 1 equals zero (that is, if input progress value × steps is integral), then
-    //    decrement current step by one.
-    auto step_progress = input_progress * number_of_steps;
-    if (before_flag && trunc(step_progress) == step_progress)
-        current_step -= 1;
-
-    // 4. If input progress value ≥ 0 and current step < 0, let current step be zero.
-    if (input_progress >= 0.0 && current_step < 0.0)
-        current_step = 0.0;
-
-    // 5. Calculate jumps based on the step position as follows:
-
-    //    jump-start or jump-end -> steps
-    //    jump-none -> steps - 1
-    //    jump-both -> steps + 1
-    double jumps;
-    if (jump_at_start ^ jump_at_end)
-        jumps = number_of_steps;
-    else if (jump_at_start && jump_at_end)
-        jumps = number_of_steps + 1;
-    else
-        jumps = number_of_steps - 1;
-
-    // 6. If input progress value ≤ 1 and current step > jumps, let current step be jumps.
-    if (input_progress <= 1.0 && current_step > jumps)
-        current_step = jumps;
-
-    // 7. The output progress value is current step / jumps.
-    return current_step / jumps;
-}
-
-TimingFunction TimingFunction::from_easing_style_value(CSS::EasingStyleValue const& easing_value)
-{
-    return easing_value.function().visit(
-        [](CSS::EasingStyleValue::Linear const& linear) {
-            if (!linear.stops.is_empty()) {
-                dbgln("FIXME: Handle linear easing functions with stops");
-            }
-            return TimingFunction { LinearTimingFunction {} };
-        },
-        [](CSS::EasingStyleValue::CubicBezier const& bezier) {
-            return TimingFunction { CubicBezierTimingFunction { bezier.x1, bezier.y1, bezier.x2, bezier.y2 } };
-        },
-        [](CSS::EasingStyleValue::Steps const& steps) {
-            auto jump_at_start = false;
-            auto jump_at_end = false;
-
-            switch (steps.position) {
-            case CSS::EasingStyleValue::Steps::Position::Start:
-            case CSS::EasingStyleValue::Steps::Position::JumpStart:
-                jump_at_start = true;
-                break;
-            case CSS::EasingStyleValue::Steps::Position::End:
-            case CSS::EasingStyleValue::Steps::Position::JumpEnd:
-                jump_at_end = true;
-                break;
-            case CSS::EasingStyleValue::Steps::Position::JumpBoth:
-                jump_at_start = true;
-                jump_at_end = true;
-                break;
-            case CSS::EasingStyleValue::Steps::Position::JumpNone:
-                break;
-            }
-
-            return TimingFunction { StepsTimingFunction { steps.number_of_intervals, jump_at_start, jump_at_end } };
-        });
-}
-
-double TimingFunction::operator()(double input_progress, bool before_flag) const
-{
-    return function.visit([&](auto const& f) { return f(input_progress, before_flag); });
-}
-
-}

+ 0 - 59
Userland/Libraries/LibWeb/Animations/TimingFunction.h

@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
- * Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#pragma once
-
-#include <AK/Function.h>
-#include <AK/Vector.h>
-
-namespace Web::CSS {
-class EasingStyleValue;
-}
-
-namespace Web::Animations {
-
-// https://www.w3.org/TR/css-easing-1/#the-linear-easing-function
-struct LinearTimingFunction {
-    double operator()(double t, bool) const;
-};
-
-// https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions
-struct CubicBezierTimingFunction {
-    double x1;
-    double y1;
-    double x2;
-    double y2;
-
-    struct CachedSample {
-        double x;
-        double y;
-        double t;
-    };
-
-    mutable Vector<CachedSample, 64> m_cached_x_samples = {};
-
-    double operator()(double input_progress, bool) const;
-};
-
-// https://www.w3.org/TR/css-easing-1/#step-easing-functions
-struct StepsTimingFunction {
-    size_t number_of_steps;
-    bool jump_at_start;
-    bool jump_at_end;
-
-    double operator()(double input_progress, bool before_flag) const;
-};
-
-struct TimingFunction {
-    static TimingFunction from_easing_style_value(CSS::EasingStyleValue const&);
-
-    Variant<LinearTimingFunction, CubicBezierTimingFunction, StepsTimingFunction> function;
-
-    double operator()(double input_progress, bool before_flag) const;
-};
-
-}

+ 0 - 1
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -9,7 +9,6 @@ set(SOURCES
     Animations/AnimationTimeline.cpp
     Animations/DocumentTimeline.cpp
     Animations/KeyframeEffect.cpp
-    Animations/TimingFunction.cpp
     ARIA/AriaData.cpp
     ARIA/ARIAMixin.cpp
     ARIA/Roles.cpp

+ 0 - 1
Userland/Libraries/LibWeb/CSS/CSSTransition.h

@@ -7,7 +7,6 @@
 #pragma once
 
 #include <LibWeb/Animations/Animation.h>
-#include <LibWeb/Animations/TimingFunction.h>
 #include <LibWeb/CSS/PropertyID.h>
 #include <LibWeb/CSS/StyleValue.h>
 #include <LibWeb/CSS/Time.h>

+ 2 - 4
Userland/Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -26,7 +26,6 @@
 #include <LibGfx/Font/WOFF2/Font.h>
 #include <LibWeb/Animations/AnimationEffect.h>
 #include <LibWeb/Animations/DocumentTimeline.h>
-#include <LibWeb/Animations/TimingFunction.h>
 #include <LibWeb/CSS/AnimationEvent.h>
 #include <LibWeb/CSS/CSSAnimation.h>
 #include <LibWeb/CSS/CSSFontFaceRule.h>
@@ -1557,10 +1556,9 @@ static void apply_animation_properties(DOM::Document& document, StyleProperties&
             play_state = *play_state_value;
     }
 
-    static Animations::TimingFunction ease_timing_function = Animations::TimingFunction::from_easing_style_value(*CSS::EasingStyleValue::create(CSS::EasingStyleValue::CubicBezier::ease()));
-    Animations::TimingFunction timing_function = ease_timing_function;
+    CSS::EasingStyleValue::Function timing_function { CSS::EasingStyleValue::CubicBezier::ease() };
     if (auto timing_property = style.maybe_null_property(PropertyID::AnimationTimingFunction); timing_property && timing_property->is_easing())
-        timing_function = Animations::TimingFunction::from_easing_style_value(timing_property->as_easing());
+        timing_function = timing_property->as_easing().function();
 
     auto iteration_duration = duration.has_value()
         ? Variant<double, String> { duration.release_value().to_milliseconds() }

+ 159 - 2
Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp

@@ -9,6 +9,7 @@
  */
 
 #include "EasingStyleValue.h"
+#include <AK/BinarySearch.h>
 #include <AK/StringBuilder.h>
 
 namespace Web::CSS {
@@ -49,10 +50,166 @@ EasingStyleValue::Steps EasingStyleValue::Steps::step_end() {
     return steps;
 }
 
-String EasingStyleValue::to_string() const
+bool EasingStyleValue::CubicBezier::operator==(Web::CSS::EasingStyleValue::CubicBezier const& other) const
+{
+    return x1 == other.x1 && y1 == other.y1 && x2 == other.x2 && y2 == other.y2;
+}
+
+double EasingStyleValue::Function::evaluate_at(double input_progress, bool before_flag) const
+{
+    constexpr static auto cubic_bezier_at = [](double x1, double x2, double t)
+    {
+        auto a = 1.0 - 3.0 * x2 + 3.0 * x1;
+        auto b = 3.0 * x2 - 6.0 * x1;
+        auto c = 3.0 * x1;
+
+        auto t2 = t * t;
+        auto t3 = t2 * t;
+
+        return (a * t3) + (b * t2) + (c * t);
+    };
+
+    return visit(
+        [&](Linear const&) { return input_progress; },
+        [&](CubicBezier const& bezier) {
+            auto const& [x1, y1, x2, y2, cached_x_samples] = bezier;
+
+            // https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
+            // For input progress values outside the range [0, 1], the curve is extended infinitely using tangent of the curve
+            // at the closest endpoint as follows:
+
+            // - For input progress values less than zero,
+            if (input_progress < 0.0) {
+                // 1. If the x value of P1 is greater than zero, use a straight line that passes through P1 and P0 as the
+                //    tangent.
+                if (x1 > 0.0)
+                    return y1 / x1 * input_progress;
+
+                // 2. Otherwise, if the x value of P2 is greater than zero, use a straight line that passes through P2 and P0 as
+                //    the tangent.
+                if (x2 > 0.0)
+                    return y2 / x2 * input_progress;
+
+                // 3. Otherwise, let the output progress value be zero for all input progress values in the range [-∞, 0).
+                return 0.0;
+            }
+
+            // - For input progress values greater than one,
+            if (input_progress > 1.0) {
+                // 1. If the x value of P2 is less than one, use a straight line that passes through P2 and P3 as the tangent.
+                if (x2 < 1.0)
+                    return (1.0 - y2) / (1.0 - x2) * (input_progress - 1.0) + 1.0;
+
+                // 2. Otherwise, if the x value of P1 is less than one, use a straight line that passes through P1 and P3 as the
+                //    tangent.
+                if (x1 < 1.0)
+                    return (1.0 - y1) / (1.0 - x1) * (input_progress - 1.0) + 1.0;
+
+                // 3. Otherwise, let the output progress value be one for all input progress values in the range (1, ∞].
+                return 1.0;
+            }
+
+            // Note: The spec does not specify the precise algorithm for calculating values in the range [0, 1]:
+            //       "The evaluation of this curve is covered in many sources such as [FUND-COMP-GRAPHICS]."
+
+            auto x = input_progress;
+
+            auto solve = [&](auto t) {
+                auto x = cubic_bezier_at(x1, x2, t);
+                auto y = cubic_bezier_at(y1, y2, t);
+                return CubicBezier::CachedSample { x, y, t };
+            };
+
+            if (cached_x_samples.is_empty())
+                cached_x_samples.append(solve(0.));
+
+            size_t nearby_index = 0;
+            if (auto found = binary_search(cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
+                    if (x > sample.x)
+                        return 1;
+                    if (x < sample.x)
+                        return -1;
+                    return 0;
+                }))
+                return found->y;
+
+            if (nearby_index == cached_x_samples.size() || nearby_index + 1 == cached_x_samples.size()) {
+                // Produce more samples until we have enough.
+                auto last_t = cached_x_samples.last().t;
+                auto last_x = cached_x_samples.last().x;
+                while (last_x <= x && last_t < 1.0) {
+                    last_t += 1. / 60.;
+                    auto solution = solve(last_t);
+                    cached_x_samples.append(solution);
+                    last_x = solution.x;
+                }
+
+                if (auto found = binary_search(cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
+                        if (x > sample.x)
+                            return 1;
+                        if (x < sample.x)
+                            return -1;
+                        return 0;
+                    }))
+                    return found->y;
+            }
+
+            // We have two samples on either side of the x value we want, so we can linearly interpolate between them.
+            auto& sample1 = cached_x_samples[nearby_index];
+            auto& sample2 = cached_x_samples[nearby_index + 1];
+            auto factor = (x - sample1.x) / (sample2.x - sample1.x);
+            return sample1.y + factor * (sample2.y - sample1.y);
+        },
+        [&](Steps const& steps) {
+            // https://www.w3.org/TR/css-easing-1/#step-easing-algo
+            // 1. Calculate the current step as floor(input progress value × steps).
+            auto [number_of_steps, position] = steps;
+            auto current_step = floor(input_progress * number_of_steps);
+
+            // 2. If the step position property is one of:
+            //    - jump-start,
+            //    - jump-both,
+            //    increment current step by one.
+            if (position == Steps::Position::JumpStart || position == Steps::Position::JumpBoth)
+                current_step += 1;
+
+            // 3. If both of the following conditions are true:
+            //    - the before flag is set, and
+            //    - input progress value × steps mod 1 equals zero (that is, if input progress value × steps is integral), then
+            //    decrement current step by one.
+            auto step_progress = input_progress * number_of_steps;
+            if (before_flag && trunc(step_progress) == step_progress)
+                current_step -= 1;
+
+            // 4. If input progress value ≥ 0 and current step < 0, let current step be zero.
+            if (input_progress >= 0.0 && current_step < 0.0)
+                current_step = 0.0;
+
+            // 5. Calculate jumps based on the step position as follows:
+
+            //    jump-start or jump-end -> steps
+            //    jump-none -> steps - 1
+            //    jump-both -> steps + 1
+            auto jumps = steps.number_of_intervals;
+            if (position == Steps::Position::JumpNone) {
+                jumps--;
+            } else if (position == Steps::Position::JumpBoth) {
+                jumps++;
+            }
+
+            // 6. If input progress value ≤ 1 and current step > jumps, let current step be jumps.
+            if (input_progress <= 1.0 && current_step > jumps)
+                current_step = jumps;
+
+            // 7. The output progress value is current step / jumps.
+            return current_step / jumps;
+        });
+}
+
+String EasingStyleValue::Function::to_string() const
 {
     StringBuilder builder;
-    m_function.visit(
+    visit(
         [&](Linear const& linear) {
             builder.append("linear"sv);
             if (!linear.stops.is_empty()) {

+ 18 - 3
Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h

@@ -40,7 +40,15 @@ public:
         double x2;
         double y2;
 
-        bool operator==(CubicBezier const&) const = default;
+        struct CachedSample {
+            double x;
+            double y;
+            double t;
+        };
+
+        mutable Vector<CachedSample, 64> m_cached_x_samples {};
+
+        bool operator==(CubicBezier const&) const;
     };
 
     struct Steps {
@@ -62,7 +70,14 @@ public:
         bool operator==(Steps const&) const = default;
     };
 
-    using Function = Variant<Linear, CubicBezier, Steps>;
+    struct Function : public Variant<Linear, CubicBezier, Steps>
+    {
+        using Variant::Variant;
+
+        double evaluate_at(double input_progress, bool before_flag) const;
+
+        String to_string() const;
+    };
 
     static ValueComparingNonnullRefPtr<EasingStyleValue> create(Function const& function)
     {
@@ -72,7 +87,7 @@ public:
 
     Function const& function() const { return m_function; }
 
-    virtual String to_string() const override;
+    virtual String to_string() const override { return m_function.to_string(); }
 
     bool properties_equal(EasingStyleValue const& other) const { return m_function == other.m_function; }