[Web Animations] Account for provided easings when computing progress and resolving...
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 25 Jan 2018 20:21:47 +0000 (20:21 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 25 Jan 2018 20:21:47 +0000 (20:21 +0000)
https://bugs.webkit.org/show_bug.cgi?id=182098
<rdar://problem/36866149>

Reviewed by Dean Jackson.

LayoutTests/imported/w3c:

Update expected values with a few adjusted failures and many progressions.

* web-platform-tests/css-timing-1/step-timing-functions-output-expected.txt:
* web-platform-tests/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation-expected.txt:
* web-platform-tests/web-animations/interfaces/Animation/effect-expected.txt:
* web-platform-tests/web-animations/interfaces/AnimationEffectTiming/easing-expected.txt:
* web-platform-tests/web-animations/interfaces/KeyframeEffect/iterationComposite-expected.txt:
* web-platform-tests/web-animations/timing-model/time-transformations/transformed-progress-expected.txt:

Source/WebCore:

We now account for the timing functions provided through the "easing" propreties on whole animation effects
and individual keyframes. Exposing those exposed shortcomings of our keyframe resolution in general through
WPT tests so we now implement the "effect value of a keyframe effect" procedure from the spec to correctly
resolve keyframes in KeyframeEffect::setAnimatedPropertiesInStyle(). The tests also showed some shortcomings
in our TimingFunction code where our step() function resolution wasn't fully compliant and our cubic-bezier()
resolution not accurate enough. We now have microsecond accuracy when resolving cubic-bezier() timing functions
and identify cubic-bezier(0, 0, 0, 0), cubic-bezier(0, 0, 1, 1) and cubic-bezier(1, 1, 1, 1) as linear timing
functions, as called out by the WPT tests.

* animation/AnimationEffect.cpp:
(WebCore::AnimationEffect::transformedProgress const): Account for the effect-wide timing function when computing
the progress.
(WebCore::AnimationEffect::iterationProgress const): Use the transformed progress now that we support this procedure.
* animation/AnimationEffect.h:
* animation/KeyframeEffect.cpp:
(WebCore::KeyframeEffect::apply): We now use the computed progress from AnimationEffect rather than compute based
on the provided time, which we've dropped as an argument.
(WebCore::KeyframeEffect::getAnimatedStyle):
(WebCore::KeyframeEffect::setAnimatedPropertiesInStyle): Implement the "effect value of a keyframe effect" procedure
in full as specified (save for composite operations).
(WebCore::KeyframeEffect::applyAtLocalTime): Deleted.
* animation/KeyframeEffect.h:
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::resolve):
* css/CSSTimingFunctionValue.h: Fix a small error made in a previous patch where we used "int" instead of "unsigned".
* platform/animation/TimingFunction.cpp:
(WebCore::TimingFunction::transformTime const):
* platform/animation/TimingFunction.h:

LayoutTests:

Update an animated value due to more accurate resolution of cubic-bezier() timing functions.

* platform/mac/transitions/default-timing-function-expected.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@227622 268f45cc-cd09-0410-ab3c-d52691b4dbfc

18 files changed:
LayoutTests/ChangeLog
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/css-timing-1/step-timing-functions-output-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/Animation/effect-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/AnimationEffectTiming/easing-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/KeyframeEffect/iterationComposite-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/timing-model/time-transformations/transformed-progress-expected.txt
LayoutTests/platform/mac/transitions/default-timing-function-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/animation/AnimationEffect.cpp
Source/WebCore/animation/AnimationEffect.h
Source/WebCore/animation/KeyframeEffect.cpp
Source/WebCore/animation/KeyframeEffect.h
Source/WebCore/animation/WebAnimation.cpp
Source/WebCore/css/CSSTimingFunctionValue.h
Source/WebCore/platform/animation/TimingFunction.cpp
Source/WebCore/platform/animation/TimingFunction.h

index 9a4fc08..f00cef9 100644 (file)
@@ -1,3 +1,15 @@
+2018-01-25  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Account for provided easings when computing progress and resolving keyframe effect values
+        https://bugs.webkit.org/show_bug.cgi?id=182098
+        <rdar://problem/36866149>
+
+        Reviewed by Dean Jackson.
+
+        Update an animated value due to more accurate resolution of cubic-bezier() timing functions.
+
+        * platform/mac/transitions/default-timing-function-expected.txt:
+
 2018-01-25  Per Arne Vollan  <pvollan@apple.com>
 
         [Win] Update test expectations.
index cdd46b0..3c344f9 100644 (file)
@@ -1,3 +1,20 @@
+2018-01-25  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Account for provided easings when computing progress and resolving keyframe effect values
+        https://bugs.webkit.org/show_bug.cgi?id=182098
+        <rdar://problem/36866149>
+
+        Reviewed by Dean Jackson.
+
+        Update expected values with a few adjusted failures and many progressions.
+
+        * web-platform-tests/css-timing-1/step-timing-functions-output-expected.txt:
+        * web-platform-tests/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation-expected.txt:
+        * web-platform-tests/web-animations/interfaces/Animation/effect-expected.txt:
+        * web-platform-tests/web-animations/interfaces/AnimationEffectTiming/easing-expected.txt:
+        * web-platform-tests/web-animations/interfaces/KeyframeEffect/iterationComposite-expected.txt:
+        * web-platform-tests/web-animations/timing-model/time-transformations/transformed-progress-expected.txt:
+
 2018-01-25  Youenn Fablet  <youenn@apple.com>
 
         Set integrity fetch options for loading scripts and CSS
index bba6a8e..aa720a4 100644 (file)
@@ -1,8 +1,8 @@
 
-FAIL step-start easing with input progress greater than 1 assert_equals: expected "100px" but got "0px"
+FAIL step-start easing with input progress greater than 1 assert_equals: expected "200px" but got "100px"
 FAIL step-end easing with input progress greater than 1 assert_equals: expected "100px" but got "0px"
 FAIL step-end easing with input progress greater than 2 assert_equals: expected "200px" but got "0px"
-FAIL step-start easing with input progress less than 0 assert_equals: expected "100px" but got "0px"
-FAIL step-start easing with input progress less than -1 assert_equals: expected "100px" but got "0px"
+FAIL step-start easing with input progress less than 0 assert_equals: expected "0px" but got "100px"
+FAIL step-start easing with input progress less than -1 assert_equals: expected "0px" but got "100px"
 FAIL step-end easing with input progress less than 0 assert_equals: expected "-100px" but got "0px"
 
index 7823dd5..760864f 100644 (file)
@@ -15,7 +15,7 @@ FAIL iteration composition of filter brightness for different unit animation ass
 FAIL iteration composition of filter brightness animation assert_equals: Animated filter brightness style at 50s of the first iteration expected "brightness(0.5)" but got "brightness(1)"
 FAIL iteration composition of filter drop-shadow animation assert_equals: Animated filter drop-shadow style at 50s of the first iteration expected "drop-shadow(rgb(60, 60, 60) 5px 5px 5px)" but got "drop-shadow(rgb(120, 120, 120) 10px 10px 10px)"
 FAIL iteration composition of same filter list animation assert_equals: Animated filter list at 50s of the first iteration expected "brightness(1.5) contrast(1.5)" but got "brightness(2) contrast(2)"
-FAIL iteration composition of discrete filter list because of mismatch of the order assert_equals: Animated filter list at 0s of the third iteration expected "brightness(1) contrast(1)" but got "none"
+FAIL iteration composition of discrete filter list because of mismatch of the order assert_equals: Animated filter list at 0s of the third iteration expected "brightness(1) contrast(1)" but got "contrast(2) brightness(2)"
 FAIL iteration composition of different length filter list animation assert_equals: Animated filter list at 50s of the first iteration expected "sepia(0.5) contrast(1.5)" but got "sepia(1) contrast(2)"
 FAIL iteration composition of transform(rotate) animation assert_regexp_match: Actual value is not a matrix expected object "/^matrix(?:3d)*\((.+)\)/" but got "none"
 FAIL iteration composition of transform: [ scale(0), scale(1) ] animation assert_regexp_match: Actual value is not a matrix expected object "/^matrix(?:3d)*\((.+)\)/" but got "none"
@@ -30,5 +30,5 @@ FAIL iteration composition of transform of matrix3d function assert_regexp_match
 FAIL iteration composition of transform of rotate3d function assert_regexp_match: Actual value is not a matrix expected object "/^matrix(?:3d)*\((.+)\)/" but got "none"
 FAIL iteration composition starts with non-zero value animation assert_equals: Animated margin-left style at 0s of the third iteration expected "50px" but got "15px"
 FAIL iteration composition with negative final value animation assert_equals: Animated margin-left style at 0s of the third iteration expected "-10px" but got "0px"
-FAIL duration changes with an iteration composition operation of accumulate assert_equals: Animated style at 50s of the third iteration expected "25px" but got "0px"
+FAIL duration changes with an iteration composition operation of accumulate assert_equals: Animated style at 50s of the third iteration expected "25px" but got "5px"
 
index f7781e4..269b071 100644 (file)
@@ -1,4 +1,4 @@
 
 FAIL effect is set correctly. Can't find variable: KeyframeEffectReadOnly
-FAIL Clearing and setting Animation.effect should update the computed style of the target element assert_equals: animation is initially having an effect expected "100px" but got "auto"
+FAIL Clearing and setting Animation.effect should update the computed style of the target element assert_equals: animation no longer has an effect expected "auto" but got "100px"
 
index 094369e..35d3789 100644 (file)
@@ -1,20 +1,20 @@
 
 PASS Has the default value 'linear' 
-FAIL step-start function assert_approx_equals: The progress of the animation should be approximately 1 at 0ms expected 1 +/- 0.01 but got 0
-FAIL steps(1, start) function assert_approx_equals: The progress of the animation should be approximately 1 at 0ms expected 1 +/- 0.01 but got 0
-FAIL steps(2, start) function assert_approx_equals: The progress of the animation should be approximately 0.5 at 0ms expected 0.5 +/- 0.01 but got 0
-FAIL step-end function assert_approx_equals: The progress of the animation should be approximately 0 at 250000ms expected 0 +/- 0.01 but got 0.25
-FAIL steps(1) function assert_approx_equals: The progress of the animation should be approximately 0 at 250000ms expected 0 +/- 0.01 but got 0.25
-FAIL steps(1, end) function assert_approx_equals: The progress of the animation should be approximately 0 at 250000ms expected 0 +/- 0.01 but got 0.25
-FAIL steps(2, end) function assert_approx_equals: The progress of the animation should be approximately 0 at 250000ms expected 0 +/- 0.01 but got 0.25
+PASS step-start function 
+PASS steps(1, start) function 
+PASS steps(2, start) function 
+PASS step-end function 
+PASS steps(1) function 
+PASS steps(1, end) function 
+PASS steps(2, end) function 
 PASS frames function 
 PASS linear function 
-FAIL ease function assert_approx_equals: The progress of the animation should be approximately 0.40851059137130497 at 250000ms expected 0.40851059137130497 +/- 0.01 but got 0.25
-FAIL ease-in function assert_approx_equals: The progress of the animation should be approximately 0.09346465078656778 at 250000ms expected 0.09346465078656778 +/- 0.01 but got 0.25
-FAIL ease-in-out function assert_approx_equals: The progress of the animation should be approximately 0.129161930735705 at 250000ms expected 0.129161930735705 +/- 0.01 but got 0.25
-FAIL ease-out function assert_approx_equals: The progress of the animation should be approximately 0.37813813135508706 at 250000ms expected 0.37813813135508706 +/- 0.01 but got 0.25
-FAIL easing function which produces values greater than 1 assert_approx_equals: The progress of the animation should be approximately 1.0240666638411384 at 250000ms expected 1.0240666638411384 +/- 0.01 but got 0.25
-FAIL easing function which produces values less than 1 assert_approx_equals: The progress of the animation should be approximately -0.29501119758965655 at 250000ms expected -0.29501119758965655 +/- 0.01 but got 0.25
+PASS ease function 
+PASS ease-in function 
+PASS ease-in-out function 
+PASS ease-out function 
+PASS easing function which produces values greater than 1 
+PASS easing function which produces values less than 1 
 PASS Throws on invalid easing: '' 
 PASS Throws on invalid easing: '7' 
 PASS Throws on invalid easing: 'test' 
@@ -53,5 +53,5 @@ PASS Canonical easing 'cubic-bezier(0.1, 5, 0.23, 0)' is returned as set
 PASS Canonical easing 'steps(3, start)' is returned as set 
 PASS Canonical easing 'steps(3)' is returned as set 
 PASS Canonical easing 'frames(3)' is returned as set 
-FAIL Allows the easing to be changed while the animation is in progress assert_equals: change currentTime to active phase expected 0.5 but got 0.75
+PASS Allows the easing to be changed while the animation is in progress 
 
index 430d6fb..408e898 100644 (file)
@@ -1,3 +1,3 @@
 
-FAIL iterationComposite can be updated while an animation is in progress assert_equals: Animated style at 50s of the third iteration expected "25px" but got "0px"
+FAIL iterationComposite can be updated while an animation is in progress assert_equals: Animated style at 50s of the third iteration expected "25px" but got "5px"
 
index f61349c..f65c3a6 100644 (file)
@@ -1,30 +1,30 @@
 
-FAIL Transformed progress for step-start function assert_approx_equals: The progress should be approximately 1 at 0ms expected 1 +/- 0.01 but got 0
-FAIL Transformed progress for steps(1, start) function assert_approx_equals: The progress should be approximately 1 at 0ms expected 1 +/- 0.01 but got 0
-FAIL Transformed progress for steps(2, start) function assert_approx_equals: The progress should be approximately 0.5 at 0ms expected 0.5 +/- 0.01 but got 0
-FAIL Transformed progress for step-end function assert_approx_equals: The progress should be approximately 0 at 250ms expected 0 +/- 0.01 but got 0.25
-FAIL Transformed progress for steps(1) function assert_approx_equals: The progress should be approximately 0 at 250ms expected 0 +/- 0.01 but got 0.25
-FAIL Transformed progress for steps(1, end) function assert_approx_equals: The progress should be approximately 0 at 250ms expected 0 +/- 0.01 but got 0.25
-FAIL Transformed progress for steps(2, end) function assert_approx_equals: The progress should be approximately 0 at 250ms expected 0 +/- 0.01 but got 0.25
+PASS Transformed progress for step-start function 
+PASS Transformed progress for steps(1, start) function 
+PASS Transformed progress for steps(2, start) function 
+PASS Transformed progress for step-end function 
+PASS Transformed progress for steps(1) function 
+PASS Transformed progress for steps(1, end) function 
+PASS Transformed progress for steps(2, end) function 
 PASS Transformed progress for frames function 
 PASS Transformed progress for linear function 
-FAIL Transformed progress for ease function assert_approx_equals: The progress should be approximately 0.40851059137130497 at 250ms expected 0.40851059137130497 +/- 0.01 but got 0.25
-FAIL Transformed progress for ease-in function assert_approx_equals: The progress should be approximately 0.09346465078656778 at 250ms expected 0.09346465078656778 +/- 0.01 but got 0.25
-FAIL Transformed progress for ease-in-out function assert_approx_equals: The progress should be approximately 0.129161930735705 at 250ms expected 0.129161930735705 +/- 0.01 but got 0.25
-FAIL Transformed progress for ease-out function assert_approx_equals: The progress should be approximately 0.37813813135508706 at 250ms expected 0.37813813135508706 +/- 0.01 but got 0.25
-FAIL Transformed progress for easing function which produces values greater than 1 assert_approx_equals: The progress should be approximately 1.0240666638411384 at 250ms expected 1.0240666638411384 +/- 0.01 but got 0.25
-FAIL Transformed progress for easing function which produces values less than 1 assert_approx_equals: The progress should be approximately -0.29501119758965655 at 250ms expected -0.29501119758965655 +/- 0.01 but got 0.25
-FAIL Test bounds point of step-start easing assert_equals: Progress at 1000ms expected 0.5 but got 0
-FAIL Test bounds point of step-start easing with reverse direction assert_equals: Progress at 1001ms expected 1 but got 0.999
-FAIL Test bounds point of step-start easing with iterationStart not at a transition point assert_equals: Progress at 0ms expected 0.5 but got 0.25
-FAIL Test bounds point of step-start easing with iterationStart and delay assert_equals: Progress at 1000ms expected 1 but got 0.5
-FAIL Test bounds point of step-start easing with iterationStart and reverse direction assert_equals: Progress at 0ms expected 1 but got 0.5
-FAIL Test bounds point of step(4, start) easing with iterationStart 0.75 and delay assert_equals: Progress at 1000ms expected 1 but got 0.75
-FAIL Test bounds point of step-start easing with alternate direction assert_equals: Progress at 0ms expected 1 but got 0.5
-FAIL Test bounds point of step-start easing with alternate-reverse direction assert_equals: Progress at 0ms expected 1 but got 0.5
-FAIL Test bounds point of step-end easing assert_equals: Progress at 1499ms expected 0 but got 0.499
-FAIL Test bounds point of step-end easing with iterationStart and delay assert_equals: Progress at 0ms expected 0 but got 0.5
-FAIL Test bounds point of step-end easing with iterationStart not at a transition point assert_equals: Progress at 0ms expected 0.5 but got 0.75
-FAIL Test bounds point of frames easing assert_equals: Progress at 1499ms expected 0 but got 0.499
-FAIL Test bounds point of frames easing with iterationStart and delay assert_equals: Progress at 0ms expected 1 but got 0.5
+PASS Transformed progress for ease function 
+PASS Transformed progress for ease-in function 
+PASS Transformed progress for ease-in-out function 
+PASS Transformed progress for ease-out function 
+PASS Transformed progress for easing function which produces values greater than 1 
+PASS Transformed progress for easing function which produces values less than 1 
+PASS Test bounds point of step-start easing 
+PASS Test bounds point of step-start easing with reverse direction 
+PASS Test bounds point of step-start easing with iterationStart not at a transition point 
+PASS Test bounds point of step-start easing with iterationStart and delay 
+PASS Test bounds point of step-start easing with iterationStart and reverse direction 
+PASS Test bounds point of step(4, start) easing with iterationStart 0.75 and delay 
+PASS Test bounds point of step-start easing with alternate direction 
+PASS Test bounds point of step-start easing with alternate-reverse direction 
+PASS Test bounds point of step-end easing 
+PASS Test bounds point of step-end easing with iterationStart and delay 
+PASS Test bounds point of step-end easing with iterationStart not at a transition point 
+PASS Test bounds point of frames easing 
+PASS Test bounds point of frames easing with iterationStart and delay 
 
index ccb16fc..ec2754b 100644 (file)
@@ -14,7 +14,7 @@ layer at (8,8) size 784x200
   RenderBlock (relative positioned) {DIV} at (0,0) size 784x200
 layer at (330,8) size 100x200
   RenderBlock (positioned) {DIV} at (322,0) size 100x200 [bgcolor=#FF0000]
-layer at (330,8) size 100x100
+layer at (329,8) size 100x100
   RenderBlock (relative positioned) {DIV} at (0,0) size 100x100 [bgcolor=#008000]
 layer at (8,108) size 100x100
   RenderBlock (relative positioned) {DIV} at (0,100) size 100x100 [bgcolor=#008000]
index dd49ec3..8e32eb8 100644 (file)
@@ -1,5 +1,42 @@
 2018-01-25  Antoine Quint  <graouts@apple.com>
 
+        [Web Animations] Account for provided easings when computing progress and resolving keyframe effect values
+        https://bugs.webkit.org/show_bug.cgi?id=182098
+        <rdar://problem/36866149>
+
+        Reviewed by Dean Jackson.
+
+        We now account for the timing functions provided through the "easing" propreties on whole animation effects
+        and individual keyframes. Exposing those exposed shortcomings of our keyframe resolution in general through
+        WPT tests so we now implement the "effect value of a keyframe effect" procedure from the spec to correctly
+        resolve keyframes in KeyframeEffect::setAnimatedPropertiesInStyle(). The tests also showed some shortcomings
+        in our TimingFunction code where our step() function resolution wasn't fully compliant and our cubic-bezier()
+        resolution not accurate enough. We now have microsecond accuracy when resolving cubic-bezier() timing functions
+        and identify cubic-bezier(0, 0, 0, 0), cubic-bezier(0, 0, 1, 1) and cubic-bezier(1, 1, 1, 1) as linear timing
+        functions, as called out by the WPT tests.
+
+        * animation/AnimationEffect.cpp:
+        (WebCore::AnimationEffect::transformedProgress const): Account for the effect-wide timing function when computing
+        the progress.
+        (WebCore::AnimationEffect::iterationProgress const): Use the transformed progress now that we support this procedure.
+        * animation/AnimationEffect.h:
+        * animation/KeyframeEffect.cpp:
+        (WebCore::KeyframeEffect::apply): We now use the computed progress from AnimationEffect rather than compute based
+        on the provided time, which we've dropped as an argument.
+        (WebCore::KeyframeEffect::getAnimatedStyle):
+        (WebCore::KeyframeEffect::setAnimatedPropertiesInStyle): Implement the "effect value of a keyframe effect" procedure
+        in full as specified (save for composite operations).
+        (WebCore::KeyframeEffect::applyAtLocalTime): Deleted.
+        * animation/KeyframeEffect.h:
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::resolve):
+        * css/CSSTimingFunctionValue.h: Fix a small error made in a previous patch where we used "int" instead of "unsigned".
+        * platform/animation/TimingFunction.cpp:
+        (WebCore::TimingFunction::transformTime const):
+        * platform/animation/TimingFunction.h:
+
+2018-01-25  Antoine Quint  <graouts@apple.com>
+
         [Web Animations] Avoid querying the current time multiple time when resolving the play state
         https://bugs.webkit.org/show_bug.cgi?id=182099
 
index 30e2798..831fdad 100644 (file)
@@ -259,9 +259,45 @@ std::optional<double> AnimationEffect::directedProgress() const
     return 1 - effectSimpleIterationProgress.value();
 }
 
+std::optional<double> AnimationEffect::transformedProgress() const
+{
+    // 3.10.1. Calculating the transformed progress
+    // https://drafts.csswg.org/web-animations-1/#calculating-the-transformed-progress
+
+    // The transformed progress is calculated from the directed progress using the following steps:
+    //
+    // 1. If the directed progress is unresolved, return unresolved.
+    auto effectDirectedProgress = directedProgress();
+    if (!effectDirectedProgress)
+        return std::nullopt;
+
+    auto effectDirectedProgressValue = effectDirectedProgress.value();
+
+    if (auto iterationDuration = m_timing->iterationDuration().seconds()) {
+        bool before = false;
+        auto* timingFunction = m_timing->timingFunction();
+        // 2. Calculate the value of the before flag as follows:
+        if (is<StepsTimingFunction>(timingFunction)) {
+            // 1. Determine the current direction using the procedure defined in §3.9.1 Calculating the directed progress.
+            // 2. If the current direction is forwards, let going forwards be true, otherwise it is false.
+            bool goingForwards = currentDirection() == AnimationEffect::ComputedDirection::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 effectPhase = phase();
+            before = (effectPhase == Phase::Before && goingForwards) || (effectPhase == Phase::After && !goingForwards);
+        }
+
+        // 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 timingFunction->transformTime(effectDirectedProgressValue, iterationDuration, before);
+    }
+
+    return effectDirectedProgressValue;
+}
+
 std::optional<double> AnimationEffect::iterationProgress() const
 {
-    return directedProgress();
+    return transformedProgress();
 }
 
 ComputedTimingProperties AnimationEffect::getComputedTiming()
index d8f177d..6afb880 100644 (file)
@@ -42,7 +42,7 @@ public:
     bool isKeyframeEffect() const { return m_classType == KeyframeEffectClass; }
     AnimationEffectTiming* timing() const { return m_timing.get(); }
     ComputedTimingProperties getComputedTiming();
-    virtual void applyAtLocalTime(Seconds, RenderStyle&) = 0;
+    virtual void apply(RenderStyle&) = 0;
 
     WebAnimation* animation() const { return m_animation.get(); }
     void setAnimation(RefPtr<WebAnimation>&& animation) { m_animation = animation; }
@@ -71,6 +71,7 @@ private:
     std::optional<double> currentIteration() const;
     AnimationEffect::ComputedDirection currentDirection() const;
     std::optional<double> directedProgress() const;
+    std::optional<double> transformedProgress() const;
 
     ClassType m_classType;
     RefPtr<WebAnimation> m_animation;
index 9ae89e9..c559af3 100644 (file)
@@ -627,24 +627,20 @@ void KeyframeEffect::computeStackingContextImpact()
     }
 }
 
-void KeyframeEffect::applyAtLocalTime(Seconds localTime, RenderStyle& targetStyle)
+void KeyframeEffect::apply(RenderStyle& targetStyle)
 {
     if (!m_target)
         return;
 
-    if (m_startedAccelerated && localTime >= timing()->iterationDuration()) {
+    auto progress = iterationProgress();
+    if (!progress)
+        return;
+
+    if (m_startedAccelerated && progress.value() >= 1) {
         m_startedAccelerated = false;
         animation()->acceleratedRunningStateDidChange();
     }
 
-    // FIXME: Assume animations only apply in the range [0, duration[
-    // until we support fill modes, delays and iterations.
-    if (localTime < 0_s || localTime >= timing()->iterationDuration())
-        return;
-
-    if (!timing()->iterationDuration())
-        return;
-
     bool needsToStartAccelerated = false;
 
     if (!m_started && !m_startedAccelerated) {
@@ -656,7 +652,7 @@ void KeyframeEffect::applyAtLocalTime(Seconds localTime, RenderStyle& targetStyl
     m_started = true;
 
     if (!needsToStartAccelerated && !m_startedAccelerated)
-        setAnimatedPropertiesInStyle(targetStyle, localTime / timing()->iterationDuration());
+        setAnimatedPropertiesInStyle(targetStyle, progress.value());
 
     // https://w3c.github.io/web-animations/#side-effects-section
     // For every property targeted by at least one animation effect that is current or in effect, the user agent
@@ -676,69 +672,157 @@ bool KeyframeEffect::shouldRunAccelerated()
 
 void KeyframeEffect::getAnimatedStyle(std::unique_ptr<RenderStyle>& animatedStyle)
 {
-    if (!animation() || !timing()->iterationDuration())
+    if (!animation())
         return;
 
-    auto localTime = animation()->currentTime();
-
-    // FIXME: Assume animations only apply in the range [0, duration[
-    // until we support fill modes, delays and iterations.
-    if (!localTime || localTime < 0_s || localTime >= timing()->iterationDuration())
+    if (!m_keyframes.size())
         return;
 
-    if (!m_keyframes.size())
+    auto progress = iterationProgress();
+    if (!progress)
         return;
 
     if (!animatedStyle)
         animatedStyle = RenderStyle::clonePtr(renderer()->style());
 
-    setAnimatedPropertiesInStyle(*animatedStyle.get(), localTime.value() / timing()->iterationDuration());
+    setAnimatedPropertiesInStyle(*animatedStyle.get(), progress.value());
 }
 
-void KeyframeEffect::setAnimatedPropertiesInStyle(RenderStyle& targetStyle, double progress)
+void KeyframeEffect::setAnimatedPropertiesInStyle(RenderStyle& targetStyle, double iterationProgress)
 {
-    size_t numberOfKeyframes = m_keyframes.size();
-    for (auto cssPropertyId : m_keyframes.properties()) {
-        int startKeyframeIndex = -1;
-        int endKeyframeIndex = -1;
+    // 4.4.3. The effect value of a keyframe effect
+    // https://drafts.csswg.org/web-animations-1/#the-effect-value-of-a-keyframe-animation-effect
+    //
+    // The effect value of a single property referenced by a keyframe effect as one of its target properties,
+    // for a given iteration progress, current iteration and underlying value is calculated as follows.
 
-        for (size_t i = 0; i < numberOfKeyframes; ++i) {
+    for (auto cssPropertyId : m_keyframes.properties()) {
+        // 1. If iteration progress is unresolved abort this procedure.
+        // 2. Let target property be the longhand property for which the effect value is to be calculated.
+        // 3. If animation type of the target property is not animatable abort this procedure since the effect cannot be applied.
+        // 4. Define the neutral value for composition as a value which, when combined with an underlying value using the add composite operation,
+        //    produces the underlying value.
+
+        // 5. Let property-specific keyframes be the result of getting the set of computed keyframes for this keyframe effect.
+        // 6. Remove any keyframes from property-specific keyframes that do not have a property value for target property.
+        unsigned numberOfKeyframesWithZeroOffset = 0;
+        unsigned numberOfKeyframesWithOneOffset = 0;
+        Vector<std::optional<size_t>> propertySpecificKeyframes;
+        for (size_t i = 0; i < m_keyframes.size(); ++i) {
             auto& keyframe = m_keyframes[i];
             if (!keyframe.containsProperty(cssPropertyId))
                 continue;
+            auto offset = keyframe.key();
+            if (!offset)
+                numberOfKeyframesWithZeroOffset++;
+            if (offset == 1)
+                numberOfKeyframesWithOneOffset++;
+            propertySpecificKeyframes.append(i);
+        }
 
-            if (keyframe.key() > progress) {
-                endKeyframeIndex = i;
-                break;
-            }
+        // 7. If property-specific keyframes is empty, return underlying value.
+        if (propertySpecificKeyframes.isEmpty())
+            continue;
 
-            startKeyframeIndex = i;
+        // 8. If there is no keyframe in property-specific keyframes with a computed keyframe offset of 0, create a new keyframe with a computed keyframe
+        //    offset of 0, a property value set to the neutral value for composition, and a composite operation of add, and prepend it to the beginning of
+        //    property-specific keyframes.
+        if (!numberOfKeyframesWithZeroOffset) {
+            propertySpecificKeyframes.insert(0, std::nullopt);
+            numberOfKeyframesWithZeroOffset = 1;
         }
 
-        // If we didn't find a start keyframe, this means there is an implicit start keyframe.
-        double startOffset;
-        const RenderStyle* startStyle;
-        if (startKeyframeIndex == -1) {
-            startOffset = 0;
-            startStyle = &targetStyle;
-        } else {
-            startOffset = m_keyframes[startKeyframeIndex].key();
-            startStyle = m_keyframes[startKeyframeIndex].style();
+        // 9. Similarly, if there is no keyframe in property-specific keyframes with a computed keyframe offset of 1, create a new keyframe with a computed
+        //    keyframe offset of 1, a property value set to the neutral value for composition, and a composite operation of add, and append it to the end of
+        //    property-specific keyframes.
+        if (!numberOfKeyframesWithOneOffset) {
+            propertySpecificKeyframes.append(std::nullopt);
+            numberOfKeyframesWithOneOffset = 1;
         }
 
-        // If we didn't find an end keyframe, this means there is an implicit end keyframe.
-        double endOffset;
-        const RenderStyle* endStyle;
-        if (endKeyframeIndex == -1) {
-            endOffset = 1;
-            endStyle = &targetStyle;
+        // 10. Let interval endpoints be an empty sequence of keyframes.
+        Vector<std::optional<size_t>> intervalEndpoints;
+
+        // 11. Populate interval endpoints by following the steps from the first matching condition from below:
+        if (iterationProgress < 0 && numberOfKeyframesWithZeroOffset > 1) {
+            // If iteration progress < 0 and there is more than one keyframe in property-specific keyframes with a computed keyframe offset of 0,
+            // Add the first keyframe in property-specific keyframes to interval endpoints.
+            intervalEndpoints.append(propertySpecificKeyframes.first());
+        } else if (iterationProgress >= 1 && numberOfKeyframesWithOneOffset > 1) {
+            // If iteration progress ≥ 1 and there is more than one keyframe in property-specific keyframes with a computed keyframe offset of 1,
+            // Add the last keyframe in property-specific keyframes to interval endpoints.
+            intervalEndpoints.append(propertySpecificKeyframes.last());
         } else {
-            endOffset = m_keyframes[endKeyframeIndex].key();
-            endStyle = m_keyframes[endKeyframeIndex].style();
+            // Otherwise,
+            // 1. Append to interval endpoints the last keyframe in property-specific keyframes whose computed keyframe offset is less than or equal
+            //    to iteration progress and less than 1. If there is no such keyframe (because, for example, the iteration progress is negative),
+            //    add the last keyframe whose computed keyframe offset is 0.
+            // 2. Append to interval endpoints the next keyframe in property-specific keyframes after the one added in the previous step.
+            size_t indexOfLastKeyframeWithZeroOffset = 0;
+            int indexOfFirstKeyframeToAddToIntervalEndpoints = -1;
+            for (size_t i = 0; i < propertySpecificKeyframes.size(); ++i) {
+                auto keyframeIndex = propertySpecificKeyframes[i];
+                auto offset = [&] () -> double {
+                    if (!keyframeIndex)
+                        return i ? 1 : 0;
+                    return m_keyframes[keyframeIndex.value()].key();
+                }();
+                if (!offset)
+                    indexOfLastKeyframeWithZeroOffset = i;
+                if (offset <= iterationProgress && offset < 1)
+                    indexOfFirstKeyframeToAddToIntervalEndpoints = i;
+                else
+                    break;
+            }
+
+            if (indexOfFirstKeyframeToAddToIntervalEndpoints >= 0) {
+                intervalEndpoints.append(propertySpecificKeyframes[indexOfFirstKeyframeToAddToIntervalEndpoints]);
+                intervalEndpoints.append(propertySpecificKeyframes[indexOfFirstKeyframeToAddToIntervalEndpoints + 1]);
+            } else {
+                ASSERT(indexOfLastKeyframeWithZeroOffset < propertySpecificKeyframes.size() - 1);
+                intervalEndpoints.append(propertySpecificKeyframes[indexOfLastKeyframeWithZeroOffset]);
+                intervalEndpoints.append(propertySpecificKeyframes[indexOfLastKeyframeWithZeroOffset + 1]);
+            }
+        }
+
+        // 12. For each keyframe in interval endpoints…
+        // FIXME: we don't support this step yet since we don't deal with any composite operation other than "replace".
+
+        // 13. If there is only one keyframe in interval endpoints return the property value of target property on that keyframe.
+        if (intervalEndpoints.size() == 1) {
+            auto keyframeIndex = intervalEndpoints[0];
+            auto keyframeStyle = !keyframeIndex ? &targetStyle : m_keyframes[keyframeIndex.value()].style();
+            CSSPropertyAnimation::blendProperties(this, cssPropertyId, &targetStyle, keyframeStyle, keyframeStyle, 0);
+            continue;
+        }
+
+        // 14. Let start offset be the computed keyframe offset of the first keyframe in interval endpoints.
+        auto startKeyframeIndex = intervalEndpoints.first();
+        auto startOffset = !startKeyframeIndex ? 0 : m_keyframes[startKeyframeIndex.value()].key();
+
+        // 15. Let end offset be the computed keyframe offset of last keyframe in interval endpoints.
+        auto endKeyframeIndex = intervalEndpoints.last();
+        auto endOffset = !endKeyframeIndex ? 1 : m_keyframes[endKeyframeIndex.value()].key();
+
+        // 16. Let interval distance be the result of evaluating (iteration progress - start offset) / (end offset - start offset).
+        auto intervalDistance = (iterationProgress - startOffset) / (endOffset - startOffset);
+
+        // 17. Let transformed distance be the result of evaluating the timing function associated with the first keyframe in interval endpoints
+        //     passing interval distance as the input progress.
+        auto transformedDistance = intervalDistance;
+        if (startKeyframeIndex) {
+            if (auto iterationDuration = timing()->iterationDuration()) {
+                auto rangeDuration = (endOffset - startOffset) * iterationDuration.seconds();
+                transformedDistance = m_timingFunctions[startKeyframeIndex.value()]->transformTime(intervalDistance, rangeDuration);
+            }
         }
 
-        auto progressInRange = (progress - startOffset) / (endOffset - startOffset);
-        CSSPropertyAnimation::blendProperties(this, cssPropertyId, &targetStyle, startStyle, endStyle, progressInRange);
+        // 18. Return the result of applying the interpolation procedure defined by the animation type of the target property, to the values of the target
+        //     property specified on the two keyframes in interval endpoints taking the first such value as Vstart and the second as Vend and using transformed
+        //     distance as the interpolation parameter p.
+        auto startStyle = !startKeyframeIndex ? &targetStyle : m_keyframes[startKeyframeIndex.value()].style();
+        auto endStyle = !endKeyframeIndex ? &targetStyle : m_keyframes[endKeyframeIndex.value()].style();
+        CSSPropertyAnimation::blendProperties(this, cssPropertyId, &targetStyle, startStyle, endStyle, transformedDistance);
     }
 }
 
index 03c90b5..a5b6002 100644 (file)
@@ -85,7 +85,7 @@ public:
     void setComposite(CompositeOperation compositeOperation) { m_compositeOperation = compositeOperation; }
 
     void getAnimatedStyle(std::unique_ptr<RenderStyle>& animatedStyle);
-    void applyAtLocalTime(Seconds, RenderStyle&) override;
+    void apply(RenderStyle&) override;
     void startOrStopAccelerated();
     bool isRunningAccelerated() const { return m_startedAccelerated; }
 
index 0fe85b5..fe5c982 100644 (file)
@@ -817,8 +817,8 @@ Seconds WebAnimation::timeToNextRequiredTick(Seconds timelineTime) const
 
 void WebAnimation::resolve(RenderStyle& targetStyle)
 {
-    if (m_effect && currentTime())
-        m_effect->applyAtLocalTime(currentTime().value(), targetStyle);
+    if (m_effect)
+        m_effect->apply(targetStyle);
 }
 
 void WebAnimation::acceleratedRunningStateDidChange()
index 2283b34..6060979 100644 (file)
@@ -101,7 +101,7 @@ public:
     bool equals(const CSSFramesTimingFunctionValue&) const;
 
 private:
-    CSSFramesTimingFunctionValue(int frames)
+    CSSFramesTimingFunctionValue(unsigned frames)
         : CSSValue(FramesTimingFunctionClass)
         , m_frames(frames)
     {
index 6a62709..941d4ad 100644 (file)
@@ -64,22 +64,41 @@ TextStream& operator<<(TextStream& ts, const TimingFunction& timingFunction)
     return ts;
 }
 
-double TimingFunction::transformTime(double inputTime, double duration) const
+double TimingFunction::transformTime(double inputTime, double duration, bool before) const
 {
     switch (m_type) {
     case TimingFunction::CubicBezierFunction: {
         auto& function = downcast<CubicBezierTimingFunction>(*this);
+        if (function.isLinear())
+            return inputTime;
         // The epsilon value we pass to UnitBezier::solve given that the animation is going to run over |dur| seconds. The longer the
         // animation, the more precision we need in the timing function result to avoid ugly discontinuities.
-        auto epsilon = 1.0 / (200.0 * duration);
+        auto epsilon = 1.0 / (1000.0 * duration);
         return UnitBezier(function.x1(), function.y1(), function.x2(), function.y2()).solve(inputTime, epsilon);
     }
     case TimingFunction::StepsFunction: {
+        // https://drafts.csswg.org/css-timing/#step-timing-functions
         auto& function = downcast<StepsTimingFunction>(*this);
-        auto numberOfSteps = function.numberOfSteps();
+        auto steps = function.numberOfSteps();
+        // 1. Calculate the current step as floor(input progress value × steps).
+        auto currentStep = std::floor(inputTime * steps);
+        // 2. If the step position property is start, increment current step by one.
         if (function.stepAtStart())
-            return std::min(1.0, (std::floor(numberOfSteps * inputTime) + 1) / numberOfSteps);
-        return std::floor(numberOfSteps * inputTime) / numberOfSteps;
+            currentStep++;
+        // 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.
+        if (before && !fmod(inputTime * steps, 1))
+            currentStep--;
+        // 4. If input progress value ≥ 0 and current step < 0, let current step be zero.
+        if (inputTime >= 0 && currentStep < 0)
+            currentStep = 0;
+        // 5. If input progress value ≤ 1 and current step > steps, let current step be steps.
+        if (inputTime <= 1 && currentStep > steps)
+            currentStep = steps;
+        // 6. The output progress value is current step / steps.
+        return currentStep / steps;
     }
     case TimingFunction::FramesFunction: {
         // https://drafts.csswg.org/css-timing/#frames-timing-functions
index 99966e6..2197ab1 100644 (file)
@@ -52,8 +52,8 @@ public:
     virtual bool operator==(const TimingFunction&) const = 0;
     bool operator!=(const TimingFunction& other) const { return !(*this == other); }
 
-    double transformTime(double, double) const;
     static ExceptionOr<RefPtr<TimingFunction>> createFromCSSText(const String&);
+    double transformTime(double, double, bool before = false) const;
     String cssText() const;
 
 protected:
@@ -161,6 +161,11 @@ public:
         return create(1.0 - m_x2, 1.0 - m_y2, 1.0 - m_x1, 1.0 - m_y1);
     }
 
+    bool isLinear() const
+    {
+        return (!m_x1 && !m_y1 && !m_x2 && !m_y2) || (m_x1 == 1.0 && m_y1 == 1.0 && m_x2 == 1.0 && m_y2 == 1.0) || (m_x1 == 0.0 && m_y1 == 0.0 && m_x2 == 1.0 && m_y2 == 1.0);
+    }
+
 private:
     explicit CubicBezierTimingFunction(TimingFunctionPreset preset = Ease, double x1 = 0.25, double y1 = 0.1, double x2 = 0.25, double y2 = 1.0)
         : TimingFunction(CubicBezierFunction)