[Web Animations] Complete support for keyframe animations
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 21 Dec 2017 19:51:35 +0000 (19:51 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 21 Dec 2017 19:51:35 +0000 (19:51 +0000)
https://bugs.webkit.org/show_bug.cgi?id=179708

Reviewed by Dean Jackson.

Source/WebCore:

We implement section 5.10.3 "Processing a keyframes argument" of the Web Animations spec to handle
multiple keyframes, rather than only two, provided either in iterable (ie. array) or property-index
(ie. dictionary) form. Although we don't currently support timing function or composite operations,
we parse them as well.

While there are minimal test changes, there will be a host of changes in the next patch in which
we turn Element.animate() on.

* animation/KeyframeEffect.cpp:
(WebCore::IDLAttributeNameToAnimationPropertyName): New utility to convert an IDL-parsed JS property
into a WebCore CSSPropertyID.
(WebCore::computeMissingKeyframeOffsets): Converts "null" offsets into computed offset values as specified
in section 4.4.2. "Calculating computed keyframes".
(WebCore::processIterableKeyframes): Process the iterable form of the keyframes argument.
(WebCore::processKeyframeLikeObject): Process a keyframe-like object found in the property-indexed form.
(WebCore::processPropertyIndexedKeyframes): Process the property-indexed form of the keyframes argument.
(WebCore::KeyframeEffect::setKeyframes): Since this method and processKeyframes() share the same signature,
we can just return the value from processKeyframes() directly.
(WebCore::KeyframeEffect::processKeyframes): Process the keyframes argument as specified.
(WebCore::KeyframeEffect::applyAtLocalTime): Delegate blending to the new setAnimatedPropertiesInStyle()
since this task is now more complex due to handling of multiple keyframes.
(WebCore::KeyframeEffect::getAnimatedStyle): Delegate blending to the new setAnimatedPropertiesInStyle()
since this task is now more complex due to handling of multiple keyframes.
(WebCore::KeyframeEffect::setAnimatedPropertiesInStyle): Handle multiple and implicit start and end keyframes.
* animation/KeyframeEffect.h: Add some new structures used for parsing purposes.
* animation/KeyframeEffect.idl: Expose the CompositeOperation enum and the BasePropertyIndexedKeyframe dictionary
used in processKeyframeLikeObject().
* css/CSSStyleDeclaration.cpp:
(WebCore::CSSStyleDeclaration::getCSSPropertyIDFromJavaScriptPropertyName):
* css/CSSStyleDeclaration.h:
* page/animation/CSSPropertyAnimation.cpp:
(WebCore::CSSPropertyAnimation::isPropertyAnimatable):
* page/animation/CSSPropertyAnimation.h:

LayoutTests:

Rebase some WPT expectations with progressions due to supporting all keyframes arguments.

* http/wpt/web-animations/interfaces/KeyframeEffect/constructor-expected.txt:
* http/wpt/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-002-expected.txt:
* http/wpt/web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt:

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

12 files changed:
LayoutTests/ChangeLog
LayoutTests/http/wpt/web-animations/interfaces/KeyframeEffect/constructor-expected.txt
LayoutTests/http/wpt/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-002-expected.txt
LayoutTests/http/wpt/web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/animation/KeyframeEffect.cpp
Source/WebCore/animation/KeyframeEffect.h
Source/WebCore/animation/KeyframeEffect.idl
Source/WebCore/css/CSSStyleDeclaration.cpp
Source/WebCore/css/CSSStyleDeclaration.h
Source/WebCore/page/animation/CSSPropertyAnimation.cpp
Source/WebCore/page/animation/CSSPropertyAnimation.h

index d98b880..c063d43 100644 (file)
@@ -1,3 +1,16 @@
+2017-12-21  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Complete support for keyframe animations
+        https://bugs.webkit.org/show_bug.cgi?id=179708
+
+        Reviewed by Dean Jackson.
+
+        Rebase some WPT expectations with progressions due to supporting all keyframes arguments.
+
+        * http/wpt/web-animations/interfaces/KeyframeEffect/constructor-expected.txt:
+        * http/wpt/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-002-expected.txt:
+        * http/wpt/web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt:
+
 2017-12-21  Ryan Haddad  <ryanhaddad@apple.com>
 
         Skip http/tests/resourceLoadStatistics/clear-in-memory-and-persistent-store-one-hour.html because it frequently times out.
 2017-12-21  Ryan Haddad  <ryanhaddad@apple.com>
 
         Skip http/tests/resourceLoadStatistics/clear-in-memory-and-persistent-store-one-hour.html because it frequently times out.
index 57b6fee..4aca8a9 100644 (file)
@@ -197,7 +197,5 @@ FAIL Invalid KeyframeEffectReadOnly option by a multi-value easing assert_throws
     }" threw object "ReferenceError: Can't find variable: KeyframeEffectReadOnly" ("ReferenceError") expected object "[object Object]" ("TypeError")
 FAIL a KeyframeEffectReadOnly constructed with null target Can't find variable: KeyframeEffectReadOnly
 PASS KeyframeEffect constructor creates an AnimationEffectTiming timing object 
     }" threw object "ReferenceError: Can't find variable: KeyframeEffectReadOnly" ("ReferenceError") expected object "[object Object]" ("TypeError")
 FAIL a KeyframeEffectReadOnly constructed with null target Can't find variable: KeyframeEffectReadOnly
 PASS KeyframeEffect constructor creates an AnimationEffectTiming timing object 
-FAIL KeyframeEffect constructor propagates exceptions generated by accessing the options object assert_throws: function "function () {
-    new KeyframeEffect(target, { get left() { throw test_error }})
-  }" threw object "TypeError: Type error" ("TypeError") expected object "[object Object]" ("test")
+PASS KeyframeEffect constructor propagates exceptions generated by accessing the options object 
 
 
index 30b3933..c5cb801 100644 (file)
@@ -1,9 +1,13 @@
 
 
-FAIL easing values are parsed correctly when set on a property-indexed keyframe Type error
+FAIL easing values are parsed correctly when set on a property-indexed keyframe effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
 FAIL easing values are parsed correctly when using a keyframe sequence effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
 FAIL easing values are parsed correctly when using a keyframe sequence effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
-PASS Invalid easing values are correctly rejected when set on a property-indexed keyframe 
-PASS Invalid easing values are correctly rejected when using a keyframe sequence 
-FAIL Errors from invalid easings on a property-indexed keyframe are thrown after reading all properties assert_equals: All properties were read before throwing the easing error expected 2 but got 0
+FAIL Invalid easing values are correctly rejected when set on a property-indexed keyframe assert_throws: TypeError is thrown for easing '' function "() => {
+      new KeyframeEffect(target, { easing: invalidEasing });
+    }" did not throw
+FAIL Invalid easing values are correctly rejected when using a keyframe sequence assert_throws: TypeError is thrown for easing '' function "() => {
+      new KeyframeEffect(target, [{ easing: invalidEasing }]);
+    }" did not throw
+FAIL Errors from invalid easings on a property-indexed keyframe are thrown after reading all properties assert_equals: All properties were read before throwing the easing error expected 2 but got 1
 FAIL Errors from invalid easings on a keyframe sequence are thrown after reading all properties assert_throws: function "() => {
     new KeyframeEffect(target, [ kf1, kf2 ]);
   }" did not throw
 FAIL Errors from invalid easings on a keyframe sequence are thrown after reading all properties assert_throws: function "() => {
     new KeyframeEffect(target, [ kf1, kf2 ]);
   }" did not throw
index 2f0f7f3..1d081ac 100644 (file)
@@ -1,49 +1,53 @@
 
 
-FAIL Keyframes can be replaced with an empty keyframe Type error
-FAIL Keyframes can be replaced with a one property two value property-indexed keyframes specification Type error
-FAIL Keyframes can be replaced with a one shorthand property two value property-indexed keyframes specification Type error
-FAIL Keyframes can be replaced with a two property (one shorthand and one of its longhand components) two value property-indexed keyframes specification Type error
-FAIL Keyframes can be replaced with a two property two value property-indexed keyframes specification Type error
-FAIL Keyframes can be replaced with a two property property-indexed keyframes specification with different numbers of values Type error
-FAIL Keyframes can be replaced with a property-indexed keyframes specification with an invalid value Type error
-FAIL Keyframes can be replaced with a one property two value property-indexed keyframes specification that needs to stringify its values Type error
-FAIL Keyframes can be replaced with a property-indexed keyframes specification with a CSS variable reference Type error
-FAIL Keyframes can be replaced with a property-indexed keyframes specification with a CSS variable reference in a shorthand property Type error
-FAIL Keyframes can be replaced with a one property one value property-indexed keyframes specification Type error
-FAIL Keyframes can be replaced with a one property one non-array value property-indexed keyframes specification Type error
-FAIL Keyframes can be replaced with a one property two value property-indexed keyframes specification where the first value is invalid Type error
-FAIL Keyframes can be replaced with a one property two value property-indexed keyframes specification where the second value is invalid Type error
-FAIL Keyframes can be replaced with a one property one keyframe sequence Type error
-FAIL Keyframes can be replaced with a one property two keyframe sequence Type error
-FAIL Keyframes can be replaced with a two property two keyframe sequence Type error
-FAIL Keyframes can be replaced with a one shorthand property two keyframe sequence Type error
-FAIL Keyframes can be replaced with a two property (a shorthand and one of its component longhands) two keyframe sequence Type error
+FAIL Keyframes can be replaced with an empty keyframe effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property two value property-indexed keyframes specification effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one shorthand property two value property-indexed keyframes specification effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a two property (one shorthand and one of its longhand components) two value property-indexed keyframes specification effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a two property two value property-indexed keyframes specification effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a two property property-indexed keyframes specification with different numbers of values effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a property-indexed keyframes specification with an invalid value effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property two value property-indexed keyframes specification that needs to stringify its values effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a property-indexed keyframes specification with a CSS variable reference effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a property-indexed keyframes specification with a CSS variable reference in a shorthand property effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property one value property-indexed keyframes specification effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property one non-array value property-indexed keyframes specification effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property two value property-indexed keyframes specification where the first value is invalid effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property two value property-indexed keyframes specification where the second value is invalid effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property one keyframe sequence effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property two keyframe sequence effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a two property two keyframe sequence effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one shorthand property two keyframe sequence effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a two property (a shorthand and one of its component longhands) two keyframe sequence effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
 FAIL Keyframes can be replaced with a keyframe sequence with duplicate values for a given interior offset Type error
 FAIL Keyframes can be replaced with a keyframe sequence with duplicate values for offsets 0 and 1 Type error
 FAIL Keyframes can be replaced with a two property four keyframe sequence Type error
 FAIL Keyframes can be replaced with a keyframe sequence with duplicate values for a given interior offset Type error
 FAIL Keyframes can be replaced with a keyframe sequence with duplicate values for offsets 0 and 1 Type error
 FAIL Keyframes can be replaced with a two property four keyframe sequence Type error
-FAIL Keyframes can be replaced with a single keyframe sequence with omitted offset Type error
-FAIL Keyframes can be replaced with a single keyframe sequence with null offset Type error
-FAIL Keyframes can be replaced with a single keyframe sequence with string offset Type error
-FAIL Keyframes can be replaced with a one property keyframe sequence with some omitted offsets Type error
-FAIL Keyframes can be replaced with a one property keyframe sequence with some null offsets Type error
-FAIL Keyframes can be replaced with a two property keyframe sequence with some omitted offsets Type error
-FAIL Keyframes can be replaced with a one property keyframe sequence with all omitted offsets Type error
+FAIL Keyframes can be replaced with a single keyframe sequence with omitted offset effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a single keyframe sequence with null offset effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a single keyframe sequence with string offset effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property keyframe sequence with some omitted offsets effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property keyframe sequence with some null offsets effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a two property keyframe sequence with some omitted offsets effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a one property keyframe sequence with all omitted offsets effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
 FAIL Keyframes can be replaced with a keyframe sequence with different easing values, but the same easing value for a given offset Type error
 FAIL Keyframes can be replaced with a keyframe sequence with different composite values, but the same composite value for a given offset Type error
 FAIL Keyframes can be replaced with a keyframe sequence with different easing values, but the same easing value for a given offset Type error
 FAIL Keyframes can be replaced with a keyframe sequence with different composite values, but the same composite value for a given offset Type error
-FAIL Keyframes can be replaced with a one property two keyframe sequence that needs to stringify its values Type error
-FAIL Keyframes can be replaced with a keyframe sequence with a CSS variable reference Type error
-FAIL Keyframes can be replaced with a keyframe sequence with a CSS variable reference in a shorthand property Type error
-FAIL Keyframes can be replaced with a keyframe sequence where shorthand precedes longhand Type error
-FAIL Keyframes can be replaced with a keyframe sequence where longhand precedes shorthand Type error
-FAIL Keyframes can be replaced with a keyframe sequence where lesser shorthand precedes greater shorthand Type error
-FAIL Keyframes can be replaced with a keyframe sequence where greater shorthand precedes lesser shorthand Type error
-FAIL Keyframes can be replaced with a two property keyframe sequence where one property is missing from the first keyframe Type error
-FAIL Keyframes can be replaced with a two property keyframe sequence where one property is missing from the last keyframe Type error
+FAIL Keyframes can be replaced with a one property two keyframe sequence that needs to stringify its values effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a keyframe sequence with a CSS variable reference effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a keyframe sequence with a CSS variable reference in a shorthand property effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a keyframe sequence where shorthand precedes longhand effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a keyframe sequence where longhand precedes shorthand effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a keyframe sequence where lesser shorthand precedes greater shorthand effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a keyframe sequence where greater shorthand precedes lesser shorthand effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a two property keyframe sequence where one property is missing from the first keyframe effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
+FAIL Keyframes can be replaced with a two property keyframe sequence where one property is missing from the last keyframe effect.getKeyframes is not a function. (In 'effect.getKeyframes()', 'effect.getKeyframes' is undefined)
 FAIL Keyframes can be replaced with a keyframe sequence with repeated values at offset 1 with different easings Type error
 FAIL Keyframes can be replaced with a keyframe sequence with repeated values at offset 1 with different easings Type error
-FAIL KeyframeEffect constructor throws with keyframes with an out-of-bounded positive offset Type error
-FAIL KeyframeEffect constructor throws with keyframes with an out-of-bounded negative offset Type error
-FAIL KeyframeEffect constructor throws with keyframes not loosely sorted by offset Type error
-FAIL KeyframeEffect constructor throws with property-indexed keyframes with an invalid easing value Type error
-FAIL KeyframeEffect constructor throws with a keyframe sequence with an invalid easing value Type error
-FAIL KeyframeEffect constructor throws with keyframes with an invalid composite value Type error
+PASS KeyframeEffect constructor throws with keyframes with an out-of-bounded positive offset 
+PASS KeyframeEffect constructor throws with keyframes with an out-of-bounded negative offset 
+PASS KeyframeEffect constructor throws with keyframes not loosely sorted by offset 
+FAIL KeyframeEffect constructor throws with property-indexed keyframes with an invalid easing value assert_throws: function "function () {
+      effect.setKeyframes(subtest.input);
+    }" did not throw
+FAIL KeyframeEffect constructor throws with a keyframe sequence with an invalid easing value assert_throws: function "function () {
+      effect.setKeyframes(subtest.input);
+    }" did not throw
+PASS KeyframeEffect constructor throws with keyframes with an invalid composite value 
 
 
index 1523777..37bc26e 100644 (file)
@@ -1,3 +1,44 @@
+2017-12-21  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Complete support for keyframe animations
+        https://bugs.webkit.org/show_bug.cgi?id=179708
+
+        Reviewed by Dean Jackson.
+
+        We implement section 5.10.3 "Processing a keyframes argument" of the Web Animations spec to handle
+        multiple keyframes, rather than only two, provided either in iterable (ie. array) or property-index
+        (ie. dictionary) form. Although we don't currently support timing function or composite operations,
+        we parse them as well.
+
+        While there are minimal test changes, there will be a host of changes in the next patch in which
+        we turn Element.animate() on.
+
+        * animation/KeyframeEffect.cpp:
+        (WebCore::IDLAttributeNameToAnimationPropertyName): New utility to convert an IDL-parsed JS property
+        into a WebCore CSSPropertyID.
+        (WebCore::computeMissingKeyframeOffsets): Converts "null" offsets into computed offset values as specified
+        in section 4.4.2. "Calculating computed keyframes".
+        (WebCore::processIterableKeyframes): Process the iterable form of the keyframes argument.
+        (WebCore::processKeyframeLikeObject): Process a keyframe-like object found in the property-indexed form.
+        (WebCore::processPropertyIndexedKeyframes): Process the property-indexed form of the keyframes argument.
+        (WebCore::KeyframeEffect::setKeyframes): Since this method and processKeyframes() share the same signature,
+        we can just return the value from processKeyframes() directly.
+        (WebCore::KeyframeEffect::processKeyframes): Process the keyframes argument as specified.
+        (WebCore::KeyframeEffect::applyAtLocalTime): Delegate blending to the new setAnimatedPropertiesInStyle()
+        since this task is now more complex due to handling of multiple keyframes.
+        (WebCore::KeyframeEffect::getAnimatedStyle): Delegate blending to the new setAnimatedPropertiesInStyle()
+        since this task is now more complex due to handling of multiple keyframes.
+        (WebCore::KeyframeEffect::setAnimatedPropertiesInStyle): Handle multiple and implicit start and end keyframes.
+        * animation/KeyframeEffect.h: Add some new structures used for parsing purposes.
+        * animation/KeyframeEffect.idl: Expose the CompositeOperation enum and the BasePropertyIndexedKeyframe dictionary
+        used in processKeyframeLikeObject().
+        * css/CSSStyleDeclaration.cpp:
+        (WebCore::CSSStyleDeclaration::getCSSPropertyIDFromJavaScriptPropertyName):
+        * css/CSSStyleDeclaration.h:
+        * page/animation/CSSPropertyAnimation.cpp:
+        (WebCore::CSSPropertyAnimation::isPropertyAnimatable):
+        * page/animation/CSSPropertyAnimation.h:
+
 2017-12-21  Jer Noble  <jer.noble@apple.com>
 
         Add initial DOM support for Media Capabilities
 2017-12-21  Jer Noble  <jer.noble@apple.com>
 
         Add initial DOM support for Media Capabilities
index 0e55b69..fc3c48b 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "Animation.h"
 #include "CSSPropertyAnimation.h"
 
 #include "Animation.h"
 #include "CSSPropertyAnimation.h"
+#include "CSSStyleDeclaration.h"
 #include "Element.h"
 #include "RenderStyle.h"
 #include "StyleProperties.h"
 #include "Element.h"
 #include "RenderStyle.h"
 #include "StyleProperties.h"
 namespace WebCore {
 using namespace JSC;
 
 namespace WebCore {
 using namespace JSC;
 
+static inline CSSPropertyID IDLAttributeNameToAnimationPropertyName(String idlAttributeName)
+{
+    // https://drafts.csswg.org/web-animations-1/#idl-attribute-name-to-animation-property-name
+    // 1. If attribute conforms to the <custom-property-name> production, return attribute.
+    // 2. If attribute is the string "cssFloat", then return an animation property representing the CSS float property.
+    if (idlAttributeName == "cssFloat")
+        return CSSPropertyFloat;
+    // 3. If attribute is the string "cssOffset", then return an animation property representing the CSS offset property.
+    // FIXME: we don't support the CSS "offset" property
+    // 4. Otherwise, return the result of applying the IDL attribute to CSS property algorithm [CSSOM] to attribute.
+    return CSSStyleDeclaration::getCSSPropertyIDFromJavaScriptPropertyName(idlAttributeName);
+}
+
+static inline void computeMissingKeyframeOffsets(Vector<KeyframeEffect::ProcessedKeyframe>& keyframes)
+{
+    // https://drafts.csswg.org/web-animations-1/#compute-missing-keyframe-offsets
+
+    if (keyframes.isEmpty())
+        return;
+
+    // 1. For each keyframe, in keyframes, let the computed keyframe offset of the keyframe be equal to its keyframe offset value.
+
+    // 2. If keyframes contains more than one keyframe and the computed keyframe offset of the first keyframe in keyframes is null,
+    //    set the computed keyframe offset of the first keyframe to 0.
+    if (keyframes.size() > 1 && !keyframes[0].offset)
+        keyframes[0].offset = 0;
+
+    // 3. If the computed keyframe offset of the last keyframe in keyframes is null, set its computed keyframe offset to 1.
+    if (!keyframes.last().offset)
+        keyframes.last().offset = 1;
+
+    // 4. For each pair of keyframes A and B where:
+    //    - A appears before B in keyframes, and
+    //    - A and B have a computed keyframe offset that is not null, and
+    //    - all keyframes between A and B have a null computed keyframe offset,
+    //    calculate the computed keyframe offset of each keyframe between A and B as follows:
+    //    1. Let offsetk be the computed keyframe offset of a keyframe k.
+    //    2. Let n be the number of keyframes between and including A and B minus 1.
+    //    3. Let index refer to the position of keyframe in the sequence of keyframes between A and B such that the first keyframe after A has an index of 1.
+    //    4. Set the computed keyframe offset of keyframe to offsetA + (offsetB − offsetA) × index / n.
+    size_t indexOfLastKeyframeWithNonNullOffset = 0;
+    for (size_t i = 1; i < keyframes.size(); ++i) {
+        auto keyframe = keyframes[i];
+        if (!keyframe.offset)
+            continue;
+        if (indexOfLastKeyframeWithNonNullOffset == i - 1)
+            continue;
+
+        double lastNonNullOffset = keyframes[indexOfLastKeyframeWithNonNullOffset].offset.value();
+        double offsetDelta = keyframe.offset.value() - lastNonNullOffset;
+        double offsetIncrement = offsetDelta / (i - indexOfLastKeyframeWithNonNullOffset);
+        size_t indexOfFirstKeyframeWithNullOffset = indexOfLastKeyframeWithNonNullOffset + 1;
+        for (size_t j = indexOfFirstKeyframeWithNullOffset; j < i; ++j)
+            keyframes[j].offset = lastNonNullOffset + (j - indexOfLastKeyframeWithNonNullOffset) * offsetIncrement;
+
+        indexOfLastKeyframeWithNonNullOffset = i;
+    }
+}
+
+static inline ExceptionOr<void> processIterableKeyframes(ExecState& state, Strong<JSObject>&& keyframesInput, JSValue method, Vector<KeyframeEffect::ProcessedKeyframe>& processedKeyframes)
+{
+    VM& vm = state.vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    // 1. Let iter be GetIterator(object, method).
+    forEachInIterable(state, keyframesInput.get(), method, [&processedKeyframes](VM& vm, ExecState& state, JSValue nextValue) -> ExceptionOr<void> {
+        if (!nextValue || !nextValue.isObject())
+            return Exception { TypeError };
+
+        auto scope = DECLARE_THROW_SCOPE(vm);
+
+        JSObject* keyframe = nextValue.toObject(&state);
+        PropertyNameArray ownPropertyNames(&vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
+        JSObject::getOwnPropertyNames(keyframe, &state, ownPropertyNames, EnumerationMode());
+        size_t numberOfProperties = ownPropertyNames.size();
+
+        KeyframeEffect::ProcessedKeyframe keyframeOutput;
+
+        String easing = "linear";
+        std::optional<double> offset;
+        std::optional<KeyframeEffect::CompositeOperation> composite;
+
+        for (size_t j = 0; j < numberOfProperties; ++j) {
+            auto ownPropertyName = ownPropertyNames[j];
+            auto ownPropertyRawValue = keyframe->get(&state, ownPropertyName);
+            if (ownPropertyName == "easing")
+                easing = convert<IDLDOMString>(state, ownPropertyRawValue);
+            else if (ownPropertyName == "offset")
+                offset = convert<IDLNullable<IDLDouble>>(state, ownPropertyRawValue);
+            else if (ownPropertyName == "composite")
+                composite = convert<IDLEnumeration<KeyframeEffect::CompositeOperation>>(state, ownPropertyRawValue);
+            else {
+                auto cssPropertyId = IDLAttributeNameToAnimationPropertyName(ownPropertyName.string());
+                if (CSSPropertyAnimation::isPropertyAnimatable(cssPropertyId))
+                    keyframeOutput.cssPropertiesAndValues.set(cssPropertyId, convert<IDLDOMString>(state, ownPropertyRawValue));
+            }
+            RETURN_IF_EXCEPTION(scope, Exception { TypeError });
+        }
+
+        keyframeOutput.easing = easing;
+        keyframeOutput.offset = offset;
+        keyframeOutput.composite = composite;
+
+        processedKeyframes.append(WTFMove(keyframeOutput));
+
+        return { };
+    });
+    RETURN_IF_EXCEPTION(scope, Exception { TypeError });
+
+    return { };
+}
+
+static inline ExceptionOr<KeyframeEffect::KeyframeLikeObject> processKeyframeLikeObject(ExecState& state, Strong<JSObject>&& keyframesInput)
+{
+    // https://drafts.csswg.org/web-animations-1/#process-a-keyframe-like-object
+
+    VM& vm = state.vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    // 1. Run the procedure to convert an ECMAScript value to a dictionary type [WEBIDL] with keyframe input as the ECMAScript value as follows:
+    // 
+    //    dictionary BasePropertyIndexedKeyframe {
+    //        (double? or sequence<double?>)                       offset = [];
+    //        (DOMString or sequence<DOMString>)                   easing = [];
+    //        (CompositeOperation or sequence<CompositeOperation>) composite = [];
+    //    };
+    //
+    //    Store the result of this procedure as keyframe output.
+    auto baseProperties = convert<IDLDictionary<KeyframeEffect::BasePropertyIndexedKeyframe>>(state, keyframesInput.get());
+    RETURN_IF_EXCEPTION(scope, Exception { TypeError });
+
+    KeyframeEffect::KeyframeLikeObject keyframeOuput;
+    keyframeOuput.baseProperties = baseProperties;
+
+    // 2. Build up a list of animatable properties as follows:
+    //
+    //    1. Let animatable properties be a list of property names (including shorthand properties that have longhand sub-properties
+    //       that are animatable) that can be animated by the implementation.
+    //    2. Convert each property name in animatable properties to the equivalent IDL attribute by applying the animation property
+    //       name to IDL attribute name algorithm.
+
+    // 3. Let input properties be the result of calling the EnumerableOwnNames operation with keyframe input as the object.
+    PropertyNameArray inputProperties(&vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
+    JSObject::getOwnPropertyNames(keyframesInput.get(), &state, inputProperties, EnumerationMode());
+
+    // 4. Make up a new list animation properties that consists of all of the properties that are in both input properties and animatable
+    //    properties, or which are in input properties and conform to the <custom-property-name> production.
+
+    // 5. Sort animation properties in ascending order by the Unicode codepoints that define each property name.
+    //    We only actually perform this after step 6.
+
+    // 6. For each property name in animation properties,
+    size_t numberOfProperties = inputProperties.size();
+    for (size_t i = 0; i < numberOfProperties; ++i) {
+        auto cssPropertyID = IDLAttributeNameToAnimationPropertyName(inputProperties[i].string());
+        if (!CSSPropertyAnimation::isPropertyAnimatable(cssPropertyID))
+            continue;
+
+        // 1. Let raw value be the result of calling the [[Get]] internal method on keyframe input, with property name as the property
+        //    key and keyframe input as the receiver.
+        auto rawValue = keyframesInput->get(&state, inputProperties[i]);
+
+        // 2. Check the completion record of raw value.
+        RETURN_IF_EXCEPTION(scope, Exception { TypeError });
+
+        // 3. Convert raw value to a DOMString or sequence of DOMStrings property values as follows:
+        Vector<String> propertyValues;
+        // Let property values be the result of converting raw value to IDL type (DOMString or sequence<DOMString>)
+        // using the procedures defined for converting an ECMAScript value to an IDL value [WEBIDL].
+        // If property values is a single DOMString, replace property values with a sequence of DOMStrings with the original value of property
+        // Values as the only element.
+        if (rawValue.isString())
+            propertyValues = { rawValue.toWTFString(&state) };
+        else
+            propertyValues = convert<IDLSequence<IDLDOMString>>(state, rawValue);
+        RETURN_IF_EXCEPTION(scope, Exception { TypeError });
+
+        // 4. Calculate the normalized property name as the result of applying the IDL attribute name to animation property name algorithm to property name.
+        // 5. Add a property to to keyframe output with normalized property name as the property name, and property values as the property value.
+        keyframeOuput.propertiesAndValues.append({ cssPropertyID, propertyValues });
+    }
+
+    // Now we can perform step 5.
+    std::sort(keyframeOuput.propertiesAndValues.begin(), keyframeOuput.propertiesAndValues.end(), [](auto& lhs, auto& rhs) {
+        return getPropertyNameString(lhs.property).utf8() < getPropertyNameString(rhs.property).utf8();
+    });
+
+    // 7. Return keyframe output.
+    return { WTFMove(keyframeOuput) };
+}
+
+static inline ExceptionOr<void> processPropertyIndexedKeyframes(ExecState& state, Strong<JSObject>&& keyframesInput, Vector<KeyframeEffect::ProcessedKeyframe>& processedKeyframes)
+{
+    // 1. Let property-indexed keyframe be the result of running the procedure to process a keyframe-like object passing object as the keyframe input.
+    auto processKeyframeLikeObjectResult = processKeyframeLikeObject(state, WTFMove(keyframesInput));
+    if (processKeyframeLikeObjectResult.hasException())
+        return processKeyframeLikeObjectResult.releaseException();
+    auto propertyIndexedKeyframe = processKeyframeLikeObjectResult.returnValue();
+
+    // 2. For each member, m, in property-indexed keyframe, perform the following steps:
+    for (auto& m : propertyIndexedKeyframe.propertiesAndValues) {
+        // 1. Let property name be the key for m.
+        auto propertyName = m.property;
+        // 2. If property name is “composite”, or “easing”, or “offset”, skip the remaining steps in this loop and continue from the next member in property-indexed
+        //    keyframe after m.
+        //    We skip this test since we split those properties and the actual CSS properties that we're currently iterating over.
+        // 3. Let property values be the value for m.
+        auto propertyValues = m.values;
+        // 4. Let property keyframes be an empty sequence of keyframes.
+        Vector<KeyframeEffect::ProcessedKeyframe> propertyKeyframes;
+        // 5. For each value, v, in property values perform the following steps:
+        for (auto& v : propertyValues) {
+            // 1. Let k be a new keyframe with a null keyframe offset.
+            KeyframeEffect::ProcessedKeyframe k;
+            // 2. Add the property-value pair, property name → v, to k.
+            k.cssPropertiesAndValues.set(propertyName, v);
+            // 3. Append k to property keyframes.
+            propertyKeyframes.append(k);
+        }
+        // 6. Apply the procedure to compute missing keyframe offsets to property keyframes.
+        computeMissingKeyframeOffsets(propertyKeyframes);
+
+        // 7. Add keyframes in property keyframes to processed keyframes.
+        for (auto& keyframe : propertyKeyframes)
+            processedKeyframes.append(keyframe);
+    }
+
+    // 3. Sort processed keyframes by the computed keyframe offset of each keyframe in increasing order.
+    std::sort(processedKeyframes.begin(), processedKeyframes.end(), [](auto& lhs, auto& rhs) {
+        return lhs.offset.value() < rhs.offset.value();
+    });
+
+    // 4. Merge adjacent keyframes in processed keyframes when they have equal computed keyframe offsets.
+    size_t i = 1;
+    while (i < processedKeyframes.size()) {
+        auto& keyframe = processedKeyframes[i];
+        auto& previousKeyframe = processedKeyframes[i - 1];
+        // If the offsets of this keyframe and the previous keyframe are different,
+        // this means that the two keyframes should not be merged and we can move
+        // on to the next keyframe.
+        if (keyframe.offset.value() != previousKeyframe.offset.value()) {
+            i++;
+            continue;
+        }
+        // Otherwise, both this keyframe and the previous keyframe should be merged.
+        // Unprocessed keyframes in processedKeyframes at this stage have a single
+        // property in cssPropertiesAndValues, so just set this on the previous keyframe.
+        auto singleValueInKeyframe = keyframe.cssPropertiesAndValues.begin();
+        previousKeyframe.cssPropertiesAndValues.set(singleValueInKeyframe->key, singleValueInKeyframe->value);
+        // Since we've processed this keyframe, we can remove it and keep i the same
+        // so that we process the next keyframe in the next loop iteration.
+        processedKeyframes.remove(i);
+    }
+
+    // 5. Let offsets be a sequence of nullable double values assigned based on the type of the “offset” member of the property-indexed keyframe as follows:
+    //    - sequence<double?>, the value of “offset” as-is.
+    //    - double?, a sequence of length one with the value of “offset” as its single item, i.e. « offset »,
+    // FIXME: update this when we figure out how to deal with nullable doubles here.
+    Vector<std::optional<double>> offsets;
+    if (WTF::holds_alternative<Vector<std::optional<double>>>(propertyIndexedKeyframe.baseProperties.offset))
+        offsets = WTF::get<Vector<std::optional<double>>>(propertyIndexedKeyframe.baseProperties.offset);
+    else if (WTF::holds_alternative<double>(propertyIndexedKeyframe.baseProperties.offset))
+        offsets.append(WTF::get<double>(propertyIndexedKeyframe.baseProperties.offset));
+    else if (WTF::holds_alternative<std::nullptr_t>(propertyIndexedKeyframe.baseProperties.offset))
+        offsets.append(std::nullopt);
+
+    // 6. Assign each value in offsets to the keyframe offset of the keyframe with corresponding position in property keyframes until the end of either sequence is reached.
+    for (size_t i = 0; i < offsets.size() && i < processedKeyframes.size(); ++i)
+        processedKeyframes[i].offset = offsets[i];
+
+    // 7. Let easings be a sequence of DOMString values assigned based on the type of the “easing” member of the property-indexed keyframe as follows:
+    //    - sequence<DOMString>, the value of “easing” as-is.
+    //    - DOMString, a sequence of length one with the value of “easing” as its single item, i.e. « easing »,
+    Vector<String> easings;
+    if (WTF::holds_alternative<Vector<String>>(propertyIndexedKeyframe.baseProperties.easing))
+        easings = WTF::get<Vector<String>>(propertyIndexedKeyframe.baseProperties.easing);
+    else if (WTF::holds_alternative<String>(propertyIndexedKeyframe.baseProperties.easing))
+        easings.append(WTF::get<String>(propertyIndexedKeyframe.baseProperties.easing));
+
+    // 8. If easings is an empty sequence, let it be a sequence of length one containing the single value “linear”, i.e. « "linear" ».
+    if (easings.isEmpty())
+        easings.append("linear");
+
+    // 9. If easings has fewer items than property keyframes, repeat the elements in easings successively starting from the beginning of the list until easings has as many
+    //    items as property keyframes.
+    if (easings.size() < processedKeyframes.size()) {
+        size_t initialNumberOfEasings = easings.size();
+        for (i = initialNumberOfEasings + 1; i < processedKeyframes.size() - 1; ++i)
+            easings.append(easings[i % initialNumberOfEasings]);
+    }
+
+    // 10. If easings has more items than property keyframes, store the excess items as unused easings.
+    // FIXME: We don't deal with this yet.
+
+    // 11. Assign each value in easings to a property named “easing” on the keyframe with the corresponding position in property keyframes until the end of property keyframes
+    //     is reached.
+    for (size_t i = 0; i < easings.size() && i < processedKeyframes.size(); ++i)
+        processedKeyframes[i].easing = easings[i];
+
+    // 12. If the “composite” member of the property-indexed keyframe is not an empty sequence:
+    Vector<KeyframeEffect::CompositeOperation> compositeModes;
+    if (WTF::holds_alternative<Vector<KeyframeEffect::CompositeOperation>>(propertyIndexedKeyframe.baseProperties.composite))
+        compositeModes = WTF::get<Vector<KeyframeEffect::CompositeOperation>>(propertyIndexedKeyframe.baseProperties.composite);
+    else if (WTF::holds_alternative<KeyframeEffect::CompositeOperation>(propertyIndexedKeyframe.baseProperties.composite))
+        compositeModes.append(WTF::get<KeyframeEffect::CompositeOperation>(propertyIndexedKeyframe.baseProperties.composite));
+    if (!compositeModes.isEmpty()) {
+        // 1. Let composite modes be a sequence of composite operations assigned from the “composite” member of property-indexed keyframe. If that member is a single composite
+        //    operation, let composite modes be a sequence of length one, with the value of the “composite” as its single item.
+        // 2. As with easings, if composite modes has fewer items than property keyframes, repeat the elements in composite modes successively starting from the beginning of
+        //    the list until composite modes has as many items as property keyframes.
+        if (compositeModes.size() < processedKeyframes.size()) {
+            size_t initialNumberOfCompositeModes = compositeModes.size();
+            for (i = initialNumberOfCompositeModes + 1; i < processedKeyframes.size() - 1; ++i)
+                compositeModes.append(compositeModes[i % initialNumberOfCompositeModes]);
+        }
+        // 3. Assign each value in composite modes to the keyframe-specific composite operation on the keyframe with the corresponding position in property keyframes until
+        //    the end of property keyframes is reached.
+        for (size_t i = 0; i < compositeModes.size() && i < processedKeyframes.size(); ++i)
+            processedKeyframes[i].composite = compositeModes[i];
+    }
+
+    return { };
+}
+
 ExceptionOr<Ref<KeyframeEffect>> KeyframeEffect::create(ExecState& state, Element* target, Strong<JSObject>&& keyframes)
 {
     auto keyframeEffect = adoptRef(*new KeyframeEffect(target));
 ExceptionOr<Ref<KeyframeEffect>> KeyframeEffect::create(ExecState& state, Element* target, Strong<JSObject>&& keyframes)
 {
     auto keyframeEffect = adoptRef(*new KeyframeEffect(target));
@@ -56,75 +381,103 @@ KeyframeEffect::KeyframeEffect(Element* target)
 {
 }
 
 {
 }
 
-ExceptionOr<void> KeyframeEffect::setKeyframes(ExecState& state, Strong<JSObject>&& keyframes)
+ExceptionOr<void> KeyframeEffect::setKeyframes(ExecState& state, Strong<JSObject>&& keyframesInput)
 {
 {
-    auto processKeyframesResult = processKeyframes(state, WTFMove(keyframes));
-    if (processKeyframesResult.hasException())
-        return processKeyframesResult.releaseException();
-    return { };
+    return processKeyframes(state, WTFMove(keyframesInput));
 }
 
 }
 
-ExceptionOr<void> KeyframeEffect::processKeyframes(ExecState& state, Strong<JSObject>&& keyframes)
+ExceptionOr<void> KeyframeEffect::processKeyframes(ExecState& state, Strong<JSObject>&& keyframesInput)
 {
 {
-    // FIXME: We only have primitive to-from parsing, for full support see webkit.org/b/179708.
-    // Full specification is at https://w3c.github.io/web-animations/#processing-a-keyframes-argument.
-
-    if (!m_target || !keyframes)
+    // 1. If object is null, return an empty sequence of keyframes.
+    if (!m_target || !keyframesInput.get())
         return { };
 
         return { };
 
-    if (!isJSArray(keyframes.get()))
-        return Exception { TypeError };
-
-    KeyframeList newKeyframes("keyframe-effect-" + createCanonicalUUIDString());
-
     VM& vm = state.vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
 
     VM& vm = state.vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
 
-    StyleResolver& styleResolver = m_target->styleResolver();
-    auto parserContext = CSSParserContext(HTMLStandardMode);
+    // 2. Let processed keyframes be an empty sequence of keyframes.
+    Vector<ProcessedKeyframe> processedKeyframes;
 
 
-    auto* array = jsCast<const JSArray*>(keyframes.get());
-    auto length = array->length();
-    if (length != 2)
-        return Exception { TypeError };
+    // 3. Let method be the result of GetMethod(object, @@iterator).
+    auto method = keyframesInput.get()->get(&state, vm.propertyNames->iteratorSymbol);
 
 
-    for (unsigned i = 0; i < length; ++i) {
-        const JSValue value = array->getIndex(&state, i);
-        if (scope.exception() || !value || !value.isObject())
+    // 4. Check the completion record of method.
+    RETURN_IF_EXCEPTION(scope, Exception { TypeError });
+
+    // 5. Perform the steps corresponding to the first matching condition from below,
+    if (!method.isUndefined())
+        processIterableKeyframes(state, WTFMove(keyframesInput), WTFMove(method), processedKeyframes);
+    else
+        processPropertyIndexedKeyframes(state, WTFMove(keyframesInput), processedKeyframes);
+
+    if (!processedKeyframes.size())
+        return { };
+
+    // 6. If processed keyframes is not loosely sorted by offset, throw a TypeError and abort these steps.
+    // 7. If there exist any keyframe in processed keyframes whose keyframe offset is non-null and less than
+    //    zero or greater than one, throw a TypeError and abort these steps.
+    double lastNonNullOffset = -1;
+    for (auto& keyframe : processedKeyframes) {
+        if (!keyframe.offset)
+            continue;
+        auto offset = keyframe.offset.value();
+        if (offset <= lastNonNullOffset || offset < 0 || offset > 1)
             return Exception { TypeError };
             return Exception { TypeError };
-        JSObject* keyframe = value.toObject(&state);
-        PropertyNameArray ownPropertyNames(&vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
-        JSObject::getOwnPropertyNames(keyframe, &state, ownPropertyNames, EnumerationMode());
-        size_t numberOfProperties = ownPropertyNames.size();
+        lastNonNullOffset = offset;
+    }
+
+    // We take a slight detour from the spec text and compute the missing keyframe offsets right away
+    // since they can be computed up-front.
+    computeMissingKeyframeOffsets(processedKeyframes);
+
+    KeyframeList keyframeList("keyframe-effect-" + createCanonicalUUIDString());
+    StyleResolver& styleResolver = m_target->styleResolver();
+    auto parserContext = CSSParserContext(HTMLStandardMode);
+
+    // 8. For each frame in processed keyframes, perform the following steps:
+    for (auto& keyframe : processedKeyframes) {
+        // 1. For each property-value pair in frame, parse the property value using the syntax specified for that property.
+        //    If the property value is invalid according to the syntax for the property, discard the property-value pair.
+        //    User agents that provide support for diagnosing errors in content SHOULD produce an appropriate warning
+        //    highlighting the invalid property value.
 
         StringBuilder cssText;
 
         StringBuilder cssText;
-        for (size_t j = 0; j < numberOfProperties; ++j) {
-            cssText.append(ownPropertyNames[j].string());
+        for (auto it = keyframe.cssPropertiesAndValues.begin(), end = keyframe.cssPropertiesAndValues.end(); it != end; ++it) {
+            cssText.append(getPropertyNameString(it->key));
             cssText.appendLiteral(": ");
             cssText.appendLiteral(": ");
-            cssText.append(keyframe->get(&state, ownPropertyNames[j]).toWTFString(&state));
+            cssText.append(it->value);
             cssText.appendLiteral("; ");
         }
 
             cssText.appendLiteral("; ");
         }
 
+        KeyframeValue keyframeValue(keyframe.offset.value(), nullptr);
         auto renderStyle = RenderStyle::createPtr();
         auto styleProperties = MutableStyleProperties::create();
         styleProperties->parseDeclaration(cssText.toString(), parserContext);
         unsigned numberOfCSSProperties = styleProperties->propertyCount();
 
         auto renderStyle = RenderStyle::createPtr();
         auto styleProperties = MutableStyleProperties::create();
         styleProperties->parseDeclaration(cssText.toString(), parserContext);
         unsigned numberOfCSSProperties = styleProperties->propertyCount();
 
-        KeyframeValue keyframeValue(0, nullptr);
-        for (unsigned k = 0; k < numberOfCSSProperties; ++k) {
-            auto cssPropertyId = styleProperties->propertyAt(k).id();
+        for (unsigned i = 0; i < numberOfCSSProperties; ++i) {
+            auto cssPropertyId = styleProperties->propertyAt(i).id();
             keyframeValue.addProperty(cssPropertyId);
             keyframeValue.addProperty(cssPropertyId);
-            newKeyframes.addProperty(cssPropertyId);
-            styleResolver.applyPropertyToStyle(cssPropertyId, styleProperties->propertyAt(k).value(), WTFMove(renderStyle));
+            keyframeList.addProperty(cssPropertyId);
+            styleResolver.applyPropertyToStyle(cssPropertyId, styleProperties->propertyAt(i).value(), WTFMove(renderStyle));
             renderStyle = styleResolver.state().takeStyle();
         }
 
             renderStyle = styleResolver.state().takeStyle();
         }
 
-        keyframeValue.setKey(i);
         keyframeValue.setStyle(RenderStyle::clonePtr(*renderStyle));
         keyframeValue.setStyle(RenderStyle::clonePtr(*renderStyle));
-        newKeyframes.insert(WTFMove(keyframeValue));
+        keyframeList.insert(WTFMove(keyframeValue));
+
+        // 2. Let the timing function of frame be the result of parsing the “easing” property on frame using the CSS syntax
+        //    defined for the easing property of the AnimationEffectTimingReadOnly interface.
+        //    If parsing the “easing” property fails, throw a TypeError and abort this procedure.
+        // FIXME: implement when we support easing.
     }
 
     }
 
-    m_keyframes = WTFMove(newKeyframes);
+    // 9. Parse each of the values in unused easings using the CSS syntax defined for easing property of the
+    //    AnimationEffectTimingReadOnly interface, and if any of the values fail to parse, throw a TypeError
+    //    and abort this procedure.
+    // FIXME: implement when we support easing.
+
+    m_keyframes = WTFMove(keyframeList);
 
     computeStackingContextImpact();
 
 
     computeStackingContextImpact();
 
@@ -170,11 +523,8 @@ void KeyframeEffect::applyAtLocalTime(Seconds localTime, RenderStyle& targetStyl
     }
     m_started = true;
 
     }
     m_started = true;
 
-    if (!needsToStartAccelerated && !m_startedAccelerated) {
-        float progress = localTime / timing()->duration();
-        for (auto cssPropertyId : m_keyframes.properties())
-            CSSPropertyAnimation::blendProperties(this, cssPropertyId, &targetStyle, m_keyframes[0].style(), m_keyframes[1].style(), progress);
-    }
+    if (!needsToStartAccelerated && !m_startedAccelerated)
+        setAnimatedPropertiesInStyle(targetStyle, localTime / timing()->duration());
 
     // 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
 
     // 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
@@ -210,9 +560,53 @@ void KeyframeEffect::getAnimatedStyle(std::unique_ptr<RenderStyle>& animatedStyl
     if (!animatedStyle)
         animatedStyle = RenderStyle::clonePtr(renderer()->style());
 
     if (!animatedStyle)
         animatedStyle = RenderStyle::clonePtr(renderer()->style());
 
+    setAnimatedPropertiesInStyle(*animatedStyle.get(), localTime.value() / timing()->duration());
+}
+
+void KeyframeEffect::setAnimatedPropertiesInStyle(RenderStyle& targetStyle, double progress)
+{
+    size_t numberOfKeyframes = m_keyframes.size();
     for (auto cssPropertyId : m_keyframes.properties()) {
     for (auto cssPropertyId : m_keyframes.properties()) {
-        float progress = localTime.value() / timing()->duration();
-        CSSPropertyAnimation::blendProperties(this, cssPropertyId, animatedStyle.get(), m_keyframes[0].style(), m_keyframes[1].style(), progress);
+        int startKeyframeIndex = -1;
+        int endKeyframeIndex = -1;
+
+        for (size_t i = 0; i < numberOfKeyframes; ++i) {
+            auto& keyframe = m_keyframes[i];
+            if (!keyframe.containsProperty(cssPropertyId))
+                continue;
+
+            if (keyframe.key() > progress) {
+                endKeyframeIndex = i;
+                break;
+            }
+
+            startKeyframeIndex = i;
+        }
+
+        // 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();
+        }
+
+        // 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;
+        } else {
+            endOffset = m_keyframes[endKeyframeIndex].key();
+            endStyle = m_keyframes[endKeyframeIndex].style();
+        }
+
+        auto progressInRange = (progress - startOffset) / (endOffset - startOffset);
+        CSSPropertyAnimation::blendProperties(this, cssPropertyId, &targetStyle, startStyle, endStyle, progressInRange);
     }
 }
 
     }
 }
 
index 7b49c19..cc2b989 100644 (file)
@@ -41,6 +41,31 @@ public:
     static ExceptionOr<Ref<KeyframeEffect>> create(JSC::ExecState&, Element*, JSC::Strong<JSC::JSObject>&&);
     ~KeyframeEffect() { }
 
     static ExceptionOr<Ref<KeyframeEffect>> create(JSC::ExecState&, Element*, JSC::Strong<JSC::JSObject>&&);
     ~KeyframeEffect() { }
 
+    enum class CompositeOperation { Replace, Add, Accumulate };
+
+    struct BasePropertyIndexedKeyframe {
+        Variant<std::nullptr_t, double, Vector<std::optional<double>>> offset;
+        Variant<String, Vector<String>> easing;
+        Variant<CompositeOperation, Vector<CompositeOperation>> composite;
+    };
+
+    struct PropertyAndValues {
+        CSSPropertyID property;
+        Vector<String> values;
+    };
+
+    struct KeyframeLikeObject {
+        BasePropertyIndexedKeyframe baseProperties;
+        Vector<PropertyAndValues> propertiesAndValues;
+    };
+
+    struct ProcessedKeyframe {
+        String easing;
+        std::optional<double> offset;
+        std::optional<CompositeOperation> composite;
+        HashMap<CSSPropertyID, String> cssPropertiesAndValues;
+    };
+
     Element* target() const { return m_target.get(); }
     ExceptionOr<void> setKeyframes(JSC::ExecState&, JSC::Strong<JSC::JSObject>&&);
     void getAnimatedStyle(std::unique_ptr<RenderStyle>& animatedStyle);
     Element* target() const { return m_target.get(); }
     ExceptionOr<void> setKeyframes(JSC::ExecState&, JSC::Strong<JSC::JSObject>&&);
     void getAnimatedStyle(std::unique_ptr<RenderStyle>& animatedStyle);
@@ -60,6 +85,8 @@ public:
 private:
     KeyframeEffect(Element*);
     ExceptionOr<void> processKeyframes(JSC::ExecState&, JSC::Strong<JSC::JSObject>&&);
 private:
     KeyframeEffect(Element*);
     ExceptionOr<void> processKeyframes(JSC::ExecState&, JSC::Strong<JSC::JSObject>&&);
+
+    void setAnimatedPropertiesInStyle(RenderStyle&, double);
     void computeStackingContextImpact();
     bool shouldRunAccelerated();
 
     void computeStackingContextImpact();
     bool shouldRunAccelerated();
 
index 519a0b6..b8e415e 100644 (file)
     readonly attribute Element? target;
     [MayThrowException, CallWith=ScriptState] void setKeyframes(object? keyframes);
 };
     readonly attribute Element? target;
     [MayThrowException, CallWith=ScriptState] void setKeyframes(object? keyframes);
 };
+
+enum CompositeOperation { "replace", "add", "accumulate" };
+
+dictionary BasePropertyIndexedKeyframe {
+    (double? or sequence<double?>) offset = [];
+    (DOMString or sequence<DOMString>) easing = [];
+    (CompositeOperation or sequence<CompositeOperation>) composite = [];
+};
index 2f68862..b513705 100644 (file)
@@ -250,6 +250,11 @@ static CSSPropertyInfo parseJavaScriptCSSPropertyName(const AtomicString& proper
 
 }
 
 
 }
 
+CSSPropertyID CSSStyleDeclaration::getCSSPropertyIDFromJavaScriptPropertyName(const AtomicString& propertyName)
+{
+    return parseJavaScriptCSSPropertyName(propertyName).propertyID;
+}
+
 std::optional<Variant<String, double>> CSSStyleDeclaration::namedItem(const AtomicString& propertyName)
 {
     auto propertyInfo = parseJavaScriptCSSPropertyName(propertyName);
 std::optional<Variant<String, double>> CSSStyleDeclaration::namedItem(const AtomicString& propertyName)
 {
     auto propertyInfo = parseJavaScriptCSSPropertyName(propertyName);
index dbb991a..b18c9ce 100644 (file)
@@ -79,6 +79,7 @@ public:
     ExceptionOr<void> setNamedItem(const AtomicString& name, String value, bool& propertySupported);
     Vector<AtomicString> supportedPropertyNames() const;
 
     ExceptionOr<void> setNamedItem(const AtomicString& name, String value, bool& propertySupported);
     Vector<AtomicString> supportedPropertyNames() const;
 
+    static CSSPropertyID getCSSPropertyIDFromJavaScriptPropertyName(const AtomicString&);
 protected:
     CSSStyleDeclaration() = default;
 };
 protected:
     CSSStyleDeclaration() = default;
 };
index 9e5dc2c..ea12189 100644 (file)
@@ -1716,6 +1716,11 @@ bool CSSPropertyAnimation::blendProperties(const CSSPropertyBlendingClient* anim
     return false;
 }
 
     return false;
 }
 
+bool CSSPropertyAnimation::isPropertyAnimatable(CSSPropertyID prop)
+{
+    return CSSPropertyAnimationWrapperMap::singleton().wrapperForProperty(prop);
+}
+
 bool CSSPropertyAnimation::animationOfPropertyIsAccelerated(CSSPropertyID prop)
 {
     AnimationPropertyWrapperBase* wrapper = CSSPropertyAnimationWrapperMap::singleton().wrapperForProperty(prop);
 bool CSSPropertyAnimation::animationOfPropertyIsAccelerated(CSSPropertyID prop)
 {
     AnimationPropertyWrapperBase* wrapper = CSSPropertyAnimationWrapperMap::singleton().wrapperForProperty(prop);
index fd94dd1..e9fc7ff 100644 (file)
@@ -38,6 +38,7 @@ class RenderStyle;
 
 class CSSPropertyAnimation {
 public:
 
 class CSSPropertyAnimation {
 public:
+    static bool isPropertyAnimatable(CSSPropertyID);
     static bool animationOfPropertyIsAccelerated(CSSPropertyID);
     static bool propertiesEqual(CSSPropertyID, const RenderStyle* a, const RenderStyle* b);
     static CSSPropertyID getPropertyAtIndex(int, bool& isShorthand);
     static bool animationOfPropertyIsAccelerated(CSSPropertyID);
     static bool propertiesEqual(CSSPropertyID, const RenderStyle* a, const RenderStyle* b);
     static CSSPropertyID getPropertyAtIndex(int, bool& isShorthand);