Modified: trunk/LayoutTests/http/wpt/web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt (226233 => 226234)
--- trunk/LayoutTests/http/wpt/web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt 2017-12-21 19:38:18 UTC (rev 226233)
+++ trunk/LayoutTests/http/wpt/web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt 2017-12-21 19:51:35 UTC (rev 226234)
@@ -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 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 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 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
Modified: trunk/Source/WebCore/animation/KeyframeEffect.cpp (226233 => 226234)
--- trunk/Source/WebCore/animation/KeyframeEffect.cpp 2017-12-21 19:38:18 UTC (rev 226233)
+++ trunk/Source/WebCore/animation/KeyframeEffect.cpp 2017-12-21 19:51:35 UTC (rev 226234)
@@ -28,6 +28,7 @@
#include "Animation.h"
#include "CSSPropertyAnimation.h"
+#include "CSSStyleDeclaration.h"
#include "Element.h"
#include "RenderStyle.h"
#include "StyleProperties.h"
@@ -38,6 +39,330 @@
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));
@@ -56,76 +381,104 @@
{
}
-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.
+ // 1. If object is null, return an empty sequence of keyframes.
+ if (!m_target || !keyframesInput.get())
+ return { };
- if (!m_target || !keyframes)
+ VM& vm = state.vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ // 2. Let processed keyframes be an empty sequence of keyframes.
+ Vector<ProcessedKeyframe> processedKeyframes;
+
+ // 3. Let method be the result of GetMethod(object, @@iterator).
+ auto method = keyframesInput.get()->get(&state, vm.propertyNames->iteratorSymbol);
+
+ // 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 { };
- if (!isJSArray(keyframes.get()))
- return Exception { TypeError };
+ // 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 };
+ lastNonNullOffset = offset;
+ }
- KeyframeList newKeyframes("keyframe-effect-" + createCanonicalUUIDString());
+ // 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);
- VM& vm = state.vm();
- auto scope = DECLARE_THROW_SCOPE(vm);
-
+ KeyframeList keyframeList("keyframe-effect-" + createCanonicalUUIDString());
StyleResolver& styleResolver = m_target->styleResolver();
auto parserContext = CSSParserContext(HTMLStandardMode);
- auto* array = jsCast<const JSArray*>(keyframes.get());
- auto length = array->length();
- if (length != 2)
- return Exception { TypeError };
+ // 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.
- for (unsigned i = 0; i < length; ++i) {
- const JSValue value = array->getIndex(&state, i);
- if (scope.exception() || !value || !value.isObject())
- 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();
-
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.append(keyframe->get(&state, ownPropertyNames[j]).toWTFString(&state));
+ cssText.append(it->value);
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();
- 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);
- 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();
}
- keyframeValue.setKey(i);
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();
return { };
@@ -170,11 +523,8 @@
}
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
@@ -210,9 +560,53 @@
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()) {
- 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);
}
}