Commit 356356fc authored by rjwright@chromium.org's avatar rjwright@chromium.org

Web Animations API: Fix...

Web Animations API: Fix KeyframeEffectModel::PropertySpecificKeyframeGroup::addSyntheticKeyframeIfRequired

Synthetic Keyframes:
For each property declared in a KeyframeEffectModel, addSyntheticKeyframeIfRequired adds
a property specific keyframe at offset: 0 if none exists. Similarly, it adds a keyframe
at offset: 1 if required.

The synthetic keyframes have compositeOperation add, and a neutral value for the property.

Partial Keyframes:
Previously, if an animation was constructed with a partial keyframe (a keyframe list with no 
keyframe at offset zero or no keyframe at offset one for any of the properties it declares) 
the renderer would crash. 

This change detects partial keyframes and throws a NotSupportedError in the JavaScript.

Review URL: https://codereview.chromium.org/203463009

git-svn-id: svn://svn.chromium.org/blink/trunk@169626 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 72eb39de
......@@ -51,10 +51,8 @@ test(function() {
}, 'Calling animate() should start an animation. CamelCase property names should be parsed.');
var keyframesWithInvalid = [
{offset: 0.1},
{width: '0px', backgroundColor: 'octarine', offset: 0.2},
{width: '1000px', foo: 'bar', offset: 1}
];
{width: '0px', backgroundColor: 'octarine', offset: 0},
{width: '1000px', foo: 'bar', offset: 1}];
test(function() {
var player = e3.animate(keyframesWithInvalid, {duration: durationValue, fill: 'forwards'});
......
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<div id='e'></div>
</body>
<script>
var element = document.getElementById('e');
test(function() {
var partialKeyframes1 = [
{opacity: '1', color: 'red'},
{opacity: '0'}
];
var partialKeyframes2 = [
{opacity: '1'},
{opacity: '0', color: 'red'}
];
var partialKeyframes3 = [
{opacity: '1', color: 'red'},
{opacity: '0', color: 'foo'}
];
var partialKeyframes4 = [
{opacity: '1', color: 'foo'},
{opacity: '0', color: 'red'}
];
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes1); });
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes2); });
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes3); });
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes4); });
}, 'Calling new Animation() with a mismatched keyframe property should throw a NotSupportedError.');
test(function() {
var validKeyframes1 = [
{opacity: '1'},
{opacity: '0.3', offset: 0.5},
{opacity: '0', offset: 1}
];
var validKeyframes2 = [
{width: '0px'},
{height: '0px', offset: 0},
{width: '10px', height: '10px', offset: 1}
];
assert_not_equals(new Animation(element, validKeyframes1), undefined);
assert_not_equals(new Animation(element, validKeyframes2), undefined);
}, 'Calling new Animation() with no offset specified for the first keyframe should create and animation.');
test(function() {
var partialKeyframes1 = [
{opacity: '1', offset: 0.1},
{opacity: '0', offset: 1}
];
var partialKeyframes2 = [
{opacity: '1', offset: 0.1},
{opacity: '0'}
];
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes1); });
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes2); });
}, 'Calling new Animation() with the offset of the first keyframe specified but not equal to 0 should throw a NotSupportedError.');
test(function() {
var validKeyframes1 = [
{opacity: '1', offset: 0},
{opacity: '0.3', offset: 0.5},
{opacity: '0'}
];
var validKeyframes2 = [
{width: '0px', height: '0px', offset: 0},
{height: '10px', offset: 1},
{width: '10px'}
];
assert_not_equals(new Animation(element, validKeyframes1), undefined);
assert_not_equals(new Animation(element, validKeyframes2), undefined);
}, 'Calling new Animation() with no offset specified for the last keyframe should create an animation.');
test(function() {
var partialKeyframes1 = [
{opacity: '1', offset: 0},
{opacity: '0', offset: 0.1}
];
var partialKeyframes2 = [
{opacity: '1'},
{opacity: '0', offset: 0.1}
];
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes1); });
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes2); });
}, 'Calling new Animation() with the offset of the last keyframe specified but not equal to 1 should throw a NotSupportedError.');
test(function() {
var partialKeyframes1 = [
{width: '0px', height: '0px', offset: 0},
{height: '10px'},
{width: '10px', offset: 1}
];
var partialKeyframes2 = [
{width: '0px', height: '0px', offset: 0},
{height: '10px'},
{width: '10px'}
];
// Height is missing keyframe at offset: 1
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes1); });
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes2); });
}, 'Calling new Animation() with a keyframe that has no offset specified, is followed by other keyframes,\n' +
'and is the last keyframe for a property, should throw a NotSupportedError.');
test(function() {
var partialKeyframes1 = [
{width: '0px', offset: 0},
{height: '0px'},
{width: '10px', height: '10px', offset: 1}
];
var partialKeyframes2 = [
{width: '0px'},
{height: '0px'},
{width: '10px', height: '10px', offset: 1}
];
// Height is missing keyframe at offset: 0
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes1); });
assert_throws('NOT_SUPPORTED_ERR', function() { new Animation(element, partialKeyframes2); });
}, 'Calling new Animation() with a keyframe that has no offset specified, is preceded by other keyframes,\n' +
'and is the first keyframe for a property, should throw a NotSupportedError.');
</script>
......@@ -32,6 +32,7 @@
#include "core/animation/Animation.h"
#include "bindings/v8/Dictionary.h"
#include "bindings/v8/ExceptionState.h"
#include "core/animation/ActiveAnimations.h"
#include "core/animation/AnimationHelpers.h"
#include "core/animation/AnimationPlayer.h"
......@@ -64,20 +65,20 @@ PassRefPtr<Animation> Animation::create(Element* element, PassRefPtrWillBeRawPtr
ASSERT(RuntimeEnabledFeatures::webAnimationsAPIEnabled());
return create(element, effect, Timing());
}
PassRefPtr<Animation> Animation::create(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, const Dictionary& timingInputDictionary)
PassRefPtr<Animation> Animation::create(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, const Dictionary& timingInputDictionary, ExceptionState& exceptionState)
{
ASSERT(RuntimeEnabledFeatures::webAnimationsAPIEnabled());
return create(element, EffectInput::convert(element, keyframeDictionaryVector), TimingInput::convert(timingInputDictionary));
return create(element, EffectInput::convert(element, keyframeDictionaryVector, exceptionState), TimingInput::convert(timingInputDictionary));
}
PassRefPtr<Animation> Animation::create(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, double duration)
PassRefPtr<Animation> Animation::create(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, double duration, ExceptionState& exceptionState)
{
ASSERT(RuntimeEnabledFeatures::webAnimationsAPIEnabled());
return create(element, EffectInput::convert(element, keyframeDictionaryVector), TimingInput::convert(duration));
return create(element, EffectInput::convert(element, keyframeDictionaryVector, exceptionState), TimingInput::convert(duration));
}
PassRefPtr<Animation> Animation::create(Element* element, const Vector<Dictionary>& keyframeDictionaryVector)
PassRefPtr<Animation> Animation::create(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, ExceptionState& exceptionState)
{
ASSERT(RuntimeEnabledFeatures::webAnimationsAPIEnabled());
return create(element, EffectInput::convert(element, keyframeDictionaryVector), Timing());
return create(element, EffectInput::convert(element, keyframeDictionaryVector, exceptionState), Timing());
}
Animation::Animation(PassRefPtr<Element> target, PassRefPtrWillBeRawPtr<AnimationEffect> effect, const Timing& timing, Priority priority, PassOwnPtr<EventDelegate> eventDelegate)
......
......@@ -40,8 +40,9 @@
namespace WebCore {
class Element;
class Dictionary;
class Element;
class ExceptionState;
class Animation FINAL : public TimedItem {
......@@ -53,9 +54,9 @@ public:
static PassRefPtr<Animation> create(Element*, PassRefPtrWillBeRawPtr<AnimationEffect>, const Dictionary& timingInputDictionary);
static PassRefPtr<Animation> create(Element*, PassRefPtrWillBeRawPtr<AnimationEffect>, double duration);
static PassRefPtr<Animation> create(Element*, PassRefPtrWillBeRawPtr<AnimationEffect>);
static PassRefPtr<Animation> create(Element*, const Vector<Dictionary>& keyframeDictionaryVector, const Dictionary& timingInputDictionary);
static PassRefPtr<Animation> create(Element*, const Vector<Dictionary>& keyframeDictionaryVector, double duration);
static PassRefPtr<Animation> create(Element*, const Vector<Dictionary>& keyframeDictionaryVector);
static PassRefPtr<Animation> create(Element*, const Vector<Dictionary>& keyframeDictionaryVector, const Dictionary& timingInputDictionary, ExceptionState&);
static PassRefPtr<Animation> create(Element*, const Vector<Dictionary>& keyframeDictionaryVector, double duration, ExceptionState&);
static PassRefPtr<Animation> create(Element*, const Vector<Dictionary>& keyframeDictionaryVector, ExceptionState&);
// FIXME: Move all of these setter methods out of Animation,
// possibly into a new class (TimingInput?).
......
......@@ -30,6 +30,7 @@
[
RuntimeEnabled=WebAnimationsAPI,
RaisesException=Constructor,
Constructor(Element target, sequence<Dictionary> keyframes, Dictionary timingInput),
Constructor(Element target, sequence<Dictionary> keyframes, double timingInput),
Constructor(Element target, sequence<Dictionary> keyframes),
......
......@@ -32,6 +32,7 @@ protected:
RefPtr<Document> document;
RefPtr<Element> element;
TrackExceptionState exceptionState;
};
class AnimationAnimationV8Test : public AnimationAnimationTest {
......@@ -43,13 +44,13 @@ protected:
}
template<typename T>
static PassRefPtr<Animation> createAnimation(Element* element, Vector<Dictionary> keyframeDictionaryVector, T timingInput)
static PassRefPtr<Animation> createAnimation(Element* element, Vector<Dictionary> keyframeDictionaryVector, T timingInput, ExceptionState& exceptionState)
{
return Animation::create(element, EffectInput::convert(element, keyframeDictionaryVector, true), timingInput);
return Animation::create(element, EffectInput::convert(element, keyframeDictionaryVector, exceptionState, true), timingInput);
}
static PassRefPtr<Animation> createAnimation(Element* element, Vector<Dictionary> keyframeDictionaryVector)
static PassRefPtr<Animation> createAnimation(Element* element, Vector<Dictionary> keyframeDictionaryVector, ExceptionState& exceptionState)
{
return Animation::create(element, EffectInput::convert(element, keyframeDictionaryVector, true));
return Animation::create(element, EffectInput::convert(element, keyframeDictionaryVector, exceptionState, true));
}
v8::Isolate* m_isolate;
......@@ -82,7 +83,7 @@ TEST_F(AnimationAnimationV8Test, CanCreateAnAnimation)
ASSERT_TRUE(jsKeyframes[1].get("width", value2));
ASSERT_EQ("0px", value2);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, 0);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, 0, exceptionState);
Element* target = animation->target();
EXPECT_EQ(*element.get(), *target);
......@@ -113,7 +114,7 @@ TEST_F(AnimationAnimationV8Test, CanSetDuration)
Vector<Dictionary, 0> jsKeyframes;
double duration = 2;
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, duration);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, duration, exceptionState);
EXPECT_EQ(duration, animation->specifiedTiming().iterationDuration);
}
......@@ -121,17 +122,100 @@ TEST_F(AnimationAnimationV8Test, CanSetDuration)
TEST_F(AnimationAnimationV8Test, CanOmitSpecifiedDuration)
{
Vector<Dictionary, 0> jsKeyframes;
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, exceptionState);
EXPECT_TRUE(std::isnan(animation->specifiedTiming().iterationDuration));
}
TEST_F(AnimationAnimationV8Test, NegativeDurationIsAuto)
{
Vector<Dictionary, 0> jsKeyframes;
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, -2);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, -2, exceptionState);
EXPECT_TRUE(std::isnan(animation->specifiedTiming().iterationDuration));
}
TEST_F(AnimationAnimationV8Test, MismatchedKeyframePropertyRaisesException)
{
Vector<Dictionary> jsKeyframes;
v8::Handle<v8::Object> keyframe1 = v8::Object::New(m_isolate);
v8::Handle<v8::Object> keyframe2 = v8::Object::New(m_isolate);
setV8ObjectPropertyAsString(keyframe1, "width", "100px");
setV8ObjectPropertyAsString(keyframe1, "offset", "0");
// Height property appears only in keyframe2
setV8ObjectPropertyAsString(keyframe2, "height", "100px");
setV8ObjectPropertyAsString(keyframe2, "width", "0px");
setV8ObjectPropertyAsString(keyframe2, "offset", "1");
jsKeyframes.append(Dictionary(keyframe1, m_isolate));
jsKeyframes.append(Dictionary(keyframe2, m_isolate));
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, 0, exceptionState);
EXPECT_TRUE(exceptionState.hadException());
EXPECT_EQ(NotSupportedError, exceptionState.code());
}
TEST_F(AnimationAnimationV8Test, MissingOffsetZeroRaisesException)
{
Vector<Dictionary> jsKeyframes;
v8::Handle<v8::Object> keyframe1 = v8::Object::New(m_isolate);
v8::Handle<v8::Object> keyframe2 = v8::Object::New(m_isolate);
setV8ObjectPropertyAsString(keyframe1, "width", "100px");
setV8ObjectPropertyAsString(keyframe1, "offset", "0.1");
setV8ObjectPropertyAsString(keyframe2, "width", "0px");
setV8ObjectPropertyAsString(keyframe2, "offset", "1");
jsKeyframes.append(Dictionary(keyframe1, m_isolate));
jsKeyframes.append(Dictionary(keyframe2, m_isolate));
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, 0, exceptionState);
EXPECT_TRUE(exceptionState.hadException());
EXPECT_EQ(NotSupportedError, exceptionState.code());
}
TEST_F(AnimationAnimationV8Test, MissingOffsetOneRaisesException)
{
Vector<Dictionary> jsKeyframes;
v8::Handle<v8::Object> keyframe1 = v8::Object::New(m_isolate);
v8::Handle<v8::Object> keyframe2 = v8::Object::New(m_isolate);
setV8ObjectPropertyAsString(keyframe1, "width", "100px");
setV8ObjectPropertyAsString(keyframe1, "offset", "0");
setV8ObjectPropertyAsString(keyframe2, "width", "0px");
setV8ObjectPropertyAsString(keyframe2, "offset", "0.1");
jsKeyframes.append(Dictionary(keyframe1, m_isolate));
jsKeyframes.append(Dictionary(keyframe2, m_isolate));
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, 0, exceptionState);
EXPECT_TRUE(exceptionState.hadException());
EXPECT_EQ(NotSupportedError, exceptionState.code());
}
TEST_F(AnimationAnimationV8Test, MissingOffsetZeroAndOneRaisesException)
{
Vector<Dictionary> jsKeyframes;
v8::Handle<v8::Object> keyframe1 = v8::Object::New(m_isolate);
v8::Handle<v8::Object> keyframe2 = v8::Object::New(m_isolate);
setV8ObjectPropertyAsString(keyframe1, "width", "100px");
setV8ObjectPropertyAsString(keyframe1, "offset", "0.1");
setV8ObjectPropertyAsString(keyframe2, "width", "0px");
setV8ObjectPropertyAsString(keyframe2, "offset", "0.2");
jsKeyframes.append(Dictionary(keyframe1, m_isolate));
jsKeyframes.append(Dictionary(keyframe2, m_isolate));
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, 0, exceptionState);
EXPECT_TRUE(exceptionState.hadException());
EXPECT_EQ(NotSupportedError, exceptionState.code());
}
TEST_F(AnimationAnimationV8Test, SpecifiedGetters)
{
Vector<Dictionary, 0> jsKeyframes;
......@@ -147,7 +231,7 @@ TEST_F(AnimationAnimationV8Test, SpecifiedGetters)
setV8ObjectPropertyAsString(timingInput, "easing", "step-start");
Dictionary timingInputDictionary = Dictionary(v8::Handle<v8::Value>::Cast(timingInput), m_isolate);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, timingInputDictionary);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, timingInputDictionary, exceptionState);
RefPtr<TimedItemTiming> specified = animation->specified();
EXPECT_EQ(2, specified->delay());
......@@ -168,7 +252,7 @@ TEST_F(AnimationAnimationV8Test, SpecifiedDurationGetter)
setV8ObjectPropertyAsNumber(timingInputWithDuration, "duration", 2.5);
Dictionary timingInputDictionaryWithDuration = Dictionary(v8::Handle<v8::Value>::Cast(timingInputWithDuration), m_isolate);
RefPtr<Animation> animationWithDuration = createAnimation(element.get(), jsKeyframes, timingInputDictionaryWithDuration);
RefPtr<Animation> animationWithDuration = createAnimation(element.get(), jsKeyframes, timingInputDictionaryWithDuration, exceptionState);
RefPtr<TimedItemTiming> specifiedWithDuration = animationWithDuration->specified();
bool isNumber = false;
......@@ -185,7 +269,7 @@ TEST_F(AnimationAnimationV8Test, SpecifiedDurationGetter)
v8::Handle<v8::Object> timingInputNoDuration = v8::Object::New(m_isolate);
Dictionary timingInputDictionaryNoDuration = Dictionary(v8::Handle<v8::Value>::Cast(timingInputNoDuration), m_isolate);
RefPtr<Animation> animationNoDuration = createAnimation(element.get(), jsKeyframes, timingInputDictionaryNoDuration);
RefPtr<Animation> animationNoDuration = createAnimation(element.get(), jsKeyframes, timingInputDictionaryNoDuration, exceptionState);
RefPtr<TimedItemTiming> specifiedNoDuration = animationNoDuration->specified();
isNumber = false;
......@@ -204,7 +288,7 @@ TEST_F(AnimationAnimationV8Test, SpecifiedSetters)
Vector<Dictionary, 0> jsKeyframes;
v8::Handle<v8::Object> timingInput = v8::Object::New(m_isolate);
Dictionary timingInputDictionary = Dictionary(v8::Handle<v8::Value>::Cast(timingInput), m_isolate);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, timingInputDictionary);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, timingInputDictionary, exceptionState);
RefPtr<TimedItemTiming> specified = animation->specified();
......@@ -246,7 +330,7 @@ TEST_F(AnimationAnimationV8Test, SetSpecifiedDuration)
Vector<Dictionary, 0> jsKeyframes;
v8::Handle<v8::Object> timingInput = v8::Object::New(m_isolate);
Dictionary timingInputDictionary = Dictionary(v8::Handle<v8::Value>::Cast(timingInput), m_isolate);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, timingInputDictionary);
RefPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, timingInputDictionary, exceptionState);
RefPtr<TimedItemTiming> specified = animation->specified();
......
......@@ -48,7 +48,7 @@ static bool checkDocumentAndRenderer(Element* element)
return element->renderer();
}
PassRefPtrWillBeRawPtr<AnimationEffect> EffectInput::convert(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, bool unsafe)
PassRefPtrWillBeRawPtr<AnimationEffect> EffectInput::convert(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, ExceptionState& exceptionState, bool unsafe)
{
// FIXME: This test will not be neccessary once resolution of keyframe values occurs at
// animation application time.
......@@ -106,7 +106,13 @@ PassRefPtrWillBeRawPtr<AnimationEffect> EffectInput::convert(Element* element, c
}
// FIXME: Replace this with code that just parses, when that code is available.
return StyleResolver::createKeyframeEffectModel(*element, propertySetVector, keyframes);
RefPtrWillBeRawPtr<KeyframeEffectModel> keyframeEffectModel = StyleResolver::createKeyframeEffectModel(*element, propertySetVector, keyframes);
if (!keyframeEffectModel->isReplaceOnly()) {
exceptionState.throwDOMException(NotSupportedError, "Partial keyframes are not supported.");
return nullptr;
}
return keyframeEffectModel;
}
} // namespace WebCore
......@@ -13,10 +13,11 @@ namespace WebCore {
class AnimationEffect;
class Dictionary;
class Element;
class ExceptionState;
class EffectInput {
public:
static PassRefPtrWillBeRawPtr<AnimationEffect> convert(Element*, const Vector<Dictionary>& keyframeDictionaryVector, bool unsafe = false);
static PassRefPtrWillBeRawPtr<AnimationEffect> convert(Element*, const Vector<Dictionary>& keyframeDictionaryVector, ExceptionState&, bool unsafe = false);
};
} // namespace WebCore
......
......@@ -59,19 +59,19 @@ public:
return animateInternal(element, effect, Timing());
}
static AnimationPlayer* animate(Element& element, const Vector<Dictionary>& keyframeDictionaryVector, const Dictionary& timingInputDictionary)
static AnimationPlayer* animate(Element& element, const Vector<Dictionary>& keyframeDictionaryVector, const Dictionary& timingInputDictionary, ExceptionState& exceptionState)
{
return animateInternal(element, EffectInput::convert(&element, keyframeDictionaryVector), TimingInput::convert(timingInputDictionary));
return animateInternal(element, EffectInput::convert(&element, keyframeDictionaryVector, exceptionState), TimingInput::convert(timingInputDictionary));
}
static AnimationPlayer* animate(Element& element, const Vector<Dictionary>& keyframeDictionaryVector, double duration)
static AnimationPlayer* animate(Element& element, const Vector<Dictionary>& keyframeDictionaryVector, double duration, ExceptionState& exceptionState)
{
return animateInternal(element, EffectInput::convert(&element, keyframeDictionaryVector), TimingInput::convert(duration));
return animateInternal(element, EffectInput::convert(&element, keyframeDictionaryVector, exceptionState), TimingInput::convert(duration));
}
static AnimationPlayer* animate(Element& element, const Vector<Dictionary>& keyframeDictionaryVector)
static AnimationPlayer* animate(Element& element, const Vector<Dictionary>& keyframeDictionaryVector, ExceptionState& exceptionState)
{
return animateInternal(element, EffectInput::convert(&element, keyframeDictionaryVector), Timing());
return animateInternal(element, EffectInput::convert(&element, keyframeDictionaryVector, exceptionState), Timing());
}
private:
......
......@@ -31,7 +31,7 @@
[
RuntimeEnabled=WebAnimationsAPI,
] partial interface Element {
[MeasureAs=ElementAnimateKeyframeListEffectObjectTiming] AnimationPlayer animate(sequence<Dictionary> keyframes, Dictionary timingInput);
[MeasureAs=ElementAnimateKeyframeListEffectDoubleTiming] AnimationPlayer animate(sequence<Dictionary> keyframes, double timingInput);
[MeasureAs=ElementAnimateKeyframeListEffectNoTiming] AnimationPlayer animate(sequence<Dictionary> keyframes);
[MeasureAs=ElementAnimateKeyframeListEffectObjectTiming, RaisesException] AnimationPlayer animate(sequence<Dictionary> keyframes, Dictionary timingInput);
[MeasureAs=ElementAnimateKeyframeListEffectDoubleTiming, RaisesException] AnimationPlayer animate(sequence<Dictionary> keyframes, double timingInput);
[MeasureAs=ElementAnimateKeyframeListEffectNoTiming, RaisesException] AnimationPlayer animate(sequence<Dictionary> keyframes);
};
......@@ -205,6 +205,7 @@ void KeyframeEffectModel::ensureKeyframeGroups() const
else
group = groupIter->value.get();
ASSERT(keyframe->composite() == AnimationEffect::CompositeReplace);
group->appendKeyframe(adoptPtr(
new PropertySpecificKeyframe(keyframe->offset(), keyframe->easing(), keyframe->propertyValue(property), keyframe->composite())));
}
......@@ -225,9 +226,11 @@ void KeyframeEffectModel::ensureInterpolationEffect() const
for (KeyframeGroupMap::const_iterator iter = m_keyframeGroups->begin(); iter != m_keyframeGroups->end(); ++iter) {
const PropertySpecificKeyframeVector& keyframes = iter->value->keyframes();
ASSERT(keyframes[0]->composite() == AnimationEffect::CompositeReplace);
const AnimatableValue* start;
const AnimatableValue* end = keyframes[0]->value();
for (size_t i = 0; i < keyframes.size() - 1; i++) {
ASSERT(keyframes[i + 1]->composite() == AnimationEffect::CompositeReplace);
start = end;
end = keyframes[i + 1]->value();
double applyFrom = i ? keyframes[i]->offset() : (-std::numeric_limits<double>::infinity());
......@@ -243,25 +246,39 @@ void KeyframeEffectModel::ensureInterpolationEffect() const
}
}
bool KeyframeEffectModel::isReplaceOnly()
{
ensureKeyframeGroups();
for (KeyframeGroupMap::iterator iter = m_keyframeGroups->begin(); iter != m_keyframeGroups->end(); ++iter) {
const PropertySpecificKeyframeVector& keyframeVector = iter->value->keyframes();
for (size_t i = 0; i < keyframeVector.size(); ++i) {
if (keyframeVector[i]->composite() != AnimationEffect::CompositeReplace)
return false;
}
}
return true;
}
KeyframeEffectModel::PropertySpecificKeyframe::PropertySpecificKeyframe(double offset, PassRefPtr<TimingFunction> easing, const AnimatableValue* value, CompositeOperation composite)
: m_offset(offset)
, m_easing(easing)
, m_composite(composite)
{
ASSERT(composite == AnimationEffect::CompositeReplace);
m_value = AnimatableValue::takeConstRef(value);
}
KeyframeEffectModel::PropertySpecificKeyframe::PropertySpecificKeyframe(double offset, PassRefPtr<TimingFunction> easing, PassRefPtr<AnimatableValue> value)
KeyframeEffectModel::PropertySpecificKeyframe::PropertySpecificKeyframe(double offset, PassRefPtr<TimingFunction> easing, PassRefPtr<AnimatableValue> value, CompositeOperation composite)
: m_offset(offset)
, m_easing(easing)
, m_value(value)
, m_composite(composite)
{
ASSERT(!isNull(m_offset));
}
PassOwnPtr<KeyframeEffectModel::PropertySpecificKeyframe> KeyframeEffectModel::PropertySpecificKeyframe::cloneWithOffset(double offset) const
{
return adoptPtr(new PropertySpecificKeyframe(offset, m_easing, m_value));
return adoptPtr(new PropertySpecificKeyframe(offset, m_easing, m_value, m_composite));
}
......@@ -293,21 +310,10 @@ void KeyframeEffectModel::PropertySpecificKeyframeGroup::removeRedundantKeyframe
void KeyframeEffectModel::PropertySpecificKeyframeGroup::addSyntheticKeyframeIfRequired()
{
ASSERT(!m_keyframes.isEmpty());
double offset = m_keyframes.first()->offset();
bool allOffsetsEqual = true;
for (PropertySpecificKeyframeVector::const_iterator iter = m_keyframes.begin() + 1; iter != m_keyframes.end(); ++iter) {
if ((*iter)->offset() != offset) {
allOffsetsEqual = false;
break;
}
}
if (!allOffsetsEqual)
return;
if (!offset)
appendKeyframe(m_keyframes.first()->cloneWithOffset(1.0));
else
m_keyframes.insert(0, adoptPtr(new PropertySpecificKeyframe(0.0, nullptr, AnimatableValue::neutralValue(), CompositeAdd)));
if (m_keyframes.first()->offset() != 0.0)
m_keyframes.insert(0, adoptPtr(new PropertySpecificKeyframe(0, nullptr, AnimatableValue::neutralValue(), CompositeAdd)));
if (m_keyframes.last()->offset() != 1.0)
appendKeyframe(adoptPtr(new PropertySpecificKeyframe(1, nullptr, AnimatableValue::neutralValue(), CompositeAdd)));
}
void KeyframeEffectModel::trace(Visitor* visitor)
......
......@@ -109,6 +109,8 @@ public:
virtual bool isKeyframeEffectModel() const OVERRIDE { return true; }
bool isReplaceOnly();
PropertySet properties() const;
class PropertySpecificKeyframe {
......@@ -117,13 +119,15 @@ public:
double offset() const { return m_offset; }
TimingFunction* easing() const { return m_easing.get(); }
const AnimatableValue* value() const { return m_value.get(); }
AnimationEffect::CompositeOperation composite() const { return m_composite; }
PassOwnPtr<PropertySpecificKeyframe> cloneWithOffset(double offset) const;
private:
// Used by cloneWithOffset().
PropertySpecificKeyframe(double offset, PassRefPtr<TimingFunction> easing, PassRefPtr<AnimatableValue>);
PropertySpecificKeyframe(double offset, PassRefPtr<TimingFunction> easing, PassRefPtr<AnimatableValue>, CompositeOperation);
double m_offset;
RefPtr<TimingFunction> m_easing;
RefPtr<AnimatableValue> m_value;
AnimationEffect::CompositeOperation m_composite;
};
class PropertySpecificKeyframeGroup {
......
......@@ -125,6 +125,7 @@ TEST(AnimationKeyframeEffectModel, CompositeReplace)
expectDoubleValue(3.0 * 0.4 + 5.0 * 0.6, effect->sample(0, 0.6)->at(0));
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST(AnimationKeyframeEffectModel, DISABLED_CompositeAdd)
{
KeyframeEffectModel::KeyframeVector keyframes = keyframesAtZeroAndOne(pixelAnimatableValue(3.0), pixelAnimatableValue(5.0));
......@@ -174,6 +175,7 @@ TEST(AnimationKeyframeEffectModel, ExtrapolateReplace)
expectDoubleValue(3.0 * -0.6 + 5.0 * 1.6, effect->sample(0, 1.6)->at(0));
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST(AnimationKeyframeEffectModel, DISABLED_ExtrapolateAdd)
{
KeyframeEffectModel::KeyframeVector keyframes = keyframesAtZeroAndOne(pixelAnimatableValue(3.0), pixelAnimatableValue(5.0));
......@@ -189,7 +191,8 @@ TEST(AnimationKeyframeEffectModel, ZeroKeyframes)
EXPECT_TRUE(effect->sample(0, 0.5)->isEmpty());
}
TEST(AnimationKeyframeEffectModel, SingleKeyframeAtOffsetZero)
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST(AnimationKeyframeEffectModel, DISABLED_SingleKeyframeAtOffsetZero)
{
KeyframeEffectModel::KeyframeVector keyframes(1);
keyframes[0] = Keyframe::create();
......@@ -200,6 +203,7 @@ TEST(AnimationKeyframeEffectModel, SingleKeyframeAtOffsetZero)
expectDoubleValue(3.0, effect->sample(0, 0.6)->at(0));
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST(AnimationKeyframeEffectModel, DISABLED_SingleKeyframeAtOffsetOne)
{
KeyframeEffectModel::KeyframeVector keyframes(1);
......@@ -267,39 +271,46 @@ TEST(AnimationKeyframeEffectModel, SampleOnKeyframe)
TEST(AnimationKeyframeEffectModel, MultipleKeyframesWithSameOffset)
{
KeyframeEffectModel::KeyframeVector keyframes(7);
KeyframeEffectModel::KeyframeVector keyframes(9);
keyframes[0] = Keyframe::create();
keyframes[0]->setOffset(0.1);
keyframes[0]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(1.0).get());
keyframes[0]->setOffset(0.0);
keyframes[0]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(0.0).get());
keyframes[1] = Keyframe::create();
keyframes[1]->setOffset(0.1);
keyframes[1]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(2.0).get());
keyframes[1]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(1.0).get());
keyframes[2] = Keyframe::create();
keyframes[2]->setOffset(0.5);
keyframes[2]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(3.0).get());
keyframes[2]->setOffset(0.1);
keyframes[2]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(2.0).get());
keyframes[3] = Keyframe::create();
keyframes[3]->setOffset(0.5);
keyframes[3]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(4.0).get());
keyframes[3]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(3.0).get());
keyframes[4] = Keyframe::create();
keyframes[4]->setOffset(0.5);
keyframes[4]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(5.0).get());
keyframes[4]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(4.0).get());
keyframes[5] = Keyframe::create();
keyframes[5]->setOffset(0.9);
keyframes[5]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(6.0).get());
keyframes[5]->setOffset(0.5);
keyframes[5]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(5.0).get());
keyframes[6] = Keyframe::create();
keyframes[6]->setOffset(0.9);
keyframes[6]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(7.0).get());
keyframes[6]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(6.0).get());
keyframes[7] = Keyframe::create();
keyframes[7]->setOffset(0.9);
keyframes[7]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(7.0).get());
keyframes[8] = Keyframe::create();
keyframes[8]->setOffset(1.0);
keyframes[8]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(7.0).get());
RefPtrWillBeRawPtr<KeyframeEffectModel> effect = KeyframeEffectModel::create(keyframes);
expectDoubleValue(2.0, effect->sample(0, 0.0)->at(0));
expectDoubleValue(0.0, effect->sample(0, 0.0)->at(0));
expectDoubleValue(2.0, effect->sample(0, 0.2)->at(0));
expectDoubleValue(3.0, effect->sample(0, 0.4)->at(0));
expectDoubleValue(5.0, effect->sample(0, 0.5)->at(0));
expectDoubleValue(5.0, effect->sample(0, 0.6)->at(0));
expectDoubleValue(6.0, effect->sample(0, 0.8)->at(0));
expectDoubleValue(6.0, effect->sample(0, 1.0)->at(0));
expectDoubleValue(7.0, effect->sample(0, 1.0)->at(0));
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST(AnimationKeyframeEffectModel, DISABLED_PerKeyframeComposite)
{
KeyframeEffectModel::KeyframeVector keyframes(2);
......@@ -338,6 +349,7 @@ TEST(AnimationKeyframeEffectModel, MultipleProperties)
expectDoubleValue(6.0, rightValue);
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST(AnimationKeyframeEffectModel, DISABLED_RecompositeCompositableValue)
{
KeyframeEffectModel::KeyframeVector keyframes = keyframesAtZeroAndOne(pixelAnimatableValue(3.0), pixelAnimatableValue(5.0));
......@@ -358,6 +370,7 @@ TEST(AnimationKeyframeEffectModel, MultipleIterations)
expectDoubleValue(2.0, effect->sample(2, 0.5)->at(0));
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST(AnimationKeyframeEffectModel, DISABLED_DependsOnUnderlyingValue)
{
KeyframeEffectModel::KeyframeVector keyframes(3);
......@@ -384,6 +397,21 @@ TEST(AnimationKeyframeEffectModel, DISABLED_DependsOnUnderlyingValue)
EXPECT_FALSE(effect->sample(0, 1)->at(0));
}
TEST(AnimationKeyframeEffectModel, AddSyntheticKeyframes)
{
KeyframeEffectModel::KeyframeVector keyframes(1);
keyframes[0] = Keyframe::create();
keyframes[0]->setOffset(0.5);
keyframes[0]->setPropertyValue(CSSPropertyLeft, unknownAnimatableValue(4.0).get());
RefPtrWillBeRawPtr<KeyframeEffectModel> effect = KeyframeEffectModel::create(keyframes);
const KeyframeEffectModel::PropertySpecificKeyframeVector& propertySpecificKeyframes = effect->getPropertySpecificKeyframes(CSSPropertyLeft);
EXPECT_EQ(3U, propertySpecificKeyframes.size());
EXPECT_DOUBLE_EQ(0.0, propertySpecificKeyframes[0]->offset());
EXPECT_DOUBLE_EQ(0.5, propertySpecificKeyframes[1]->offset());
EXPECT_DOUBLE_EQ(1.0, propertySpecificKeyframes[2]->offset());
}
TEST(AnimationKeyframeEffectModel, ToKeyframeEffectModel)
{
KeyframeEffectModel::KeyframeVector keyframes(0);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment