1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "config.h"
6#include "core/animation/Animation.h"
7
8#include "bindings/core/v8/Dictionary.h"
9#include "bindings/core/v8/Nullable.h"
10#include "core/animation/AnimationClock.h"
11#include "core/animation/AnimationNodeTiming.h"
12#include "core/animation/AnimationTestHelper.h"
13#include "core/animation/AnimationTimeline.h"
14#include "core/animation/KeyframeEffectModel.h"
15#include "core/animation/Timing.h"
16#include "core/dom/Document.h"
17#include "core/testing/DummyPageHolder.h"
18#include <gtest/gtest.h>
19#include <v8.h>
20
21namespace blink {
22
23class AnimationAnimationTest : public ::testing::Test {
24protected:
25    AnimationAnimationTest()
26        : pageHolder(DummyPageHolder::create())
27        , document(pageHolder->document())
28        , element(document.createElement("foo", ASSERT_NO_EXCEPTION))
29    {
30        document.animationClock().resetTimeForTesting(document.timeline().zeroTime());
31        EXPECT_EQ(0, document.timeline().currentTime());
32    }
33
34    OwnPtr<DummyPageHolder> pageHolder;
35    Document& document;
36    RefPtrWillBePersistent<Element> element;
37    TrackExceptionState exceptionState;
38};
39
40class AnimationAnimationV8Test : public AnimationAnimationTest {
41protected:
42    AnimationAnimationV8Test()
43        : m_isolate(v8::Isolate::GetCurrent())
44        , m_scope(m_isolate)
45    {
46    }
47
48    template<typename T>
49    static PassRefPtrWillBeRawPtr<Animation> createAnimation(Element* element, Vector<Dictionary> keyframeDictionaryVector, T timingInput, ExceptionState& exceptionState)
50    {
51        return Animation::create(element, EffectInput::convert(element, keyframeDictionaryVector, exceptionState), timingInput);
52    }
53    static PassRefPtrWillBeRawPtr<Animation> createAnimation(Element* element, Vector<Dictionary> keyframeDictionaryVector, ExceptionState& exceptionState)
54    {
55        return Animation::create(element, EffectInput::convert(element, keyframeDictionaryVector, exceptionState));
56    }
57
58    v8::Isolate* m_isolate;
59
60private:
61    V8TestingScope m_scope;
62};
63
64TEST_F(AnimationAnimationV8Test, CanCreateAnAnimation)
65{
66    Vector<Dictionary> jsKeyframes;
67    v8::Handle<v8::Object> keyframe1 = v8::Object::New(m_isolate);
68    v8::Handle<v8::Object> keyframe2 = v8::Object::New(m_isolate);
69
70    setV8ObjectPropertyAsString(keyframe1, "width", "100px");
71    setV8ObjectPropertyAsString(keyframe1, "offset", "0");
72    setV8ObjectPropertyAsString(keyframe1, "easing", "ease-in-out");
73    setV8ObjectPropertyAsString(keyframe2, "width", "0px");
74    setV8ObjectPropertyAsString(keyframe2, "offset", "1");
75    setV8ObjectPropertyAsString(keyframe2, "easing", "cubic-bezier(1, 1, 0.3, 0.3)");
76
77    jsKeyframes.append(Dictionary(keyframe1, m_isolate));
78    jsKeyframes.append(Dictionary(keyframe2, m_isolate));
79
80    String value1;
81    ASSERT_TRUE(DictionaryHelper::get(jsKeyframes[0], "width", value1));
82    ASSERT_EQ("100px", value1);
83
84    String value2;
85    ASSERT_TRUE(DictionaryHelper::get(jsKeyframes[1], "width", value2));
86    ASSERT_EQ("0px", value2);
87
88    RefPtrWillBeRawPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, 0, exceptionState);
89
90    Element* target = animation->target();
91    EXPECT_EQ(*element.get(), *target);
92
93    const KeyframeVector keyframes = toKeyframeEffectModelBase(animation->effect())->getFrames();
94
95    EXPECT_EQ(0, keyframes[0]->offset());
96    EXPECT_EQ(1, keyframes[1]->offset());
97
98    const CSSValue* keyframe1Width = toStringKeyframe(keyframes[0].get())->propertyValue(CSSPropertyWidth);
99    const CSSValue* keyframe2Width = toStringKeyframe(keyframes[1].get())->propertyValue(CSSPropertyWidth);
100    ASSERT(keyframe1Width);
101    ASSERT(keyframe2Width);
102
103    EXPECT_EQ("100px", keyframe1Width->cssText());
104    EXPECT_EQ("0px", keyframe2Width->cssText());
105
106    EXPECT_EQ(*(CubicBezierTimingFunction::preset(CubicBezierTimingFunction::EaseInOut)), keyframes[0]->easing());
107    EXPECT_EQ(*(CubicBezierTimingFunction::create(1, 1, 0.3, 0.3).get()), keyframes[1]->easing());
108}
109
110TEST_F(AnimationAnimationV8Test, CanSetDuration)
111{
112    Vector<Dictionary, 0> jsKeyframes;
113    double duration = 2000;
114
115    RefPtrWillBeRawPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, duration, exceptionState);
116
117    EXPECT_EQ(duration / 1000, animation->specifiedTiming().iterationDuration);
118}
119
120TEST_F(AnimationAnimationV8Test, CanOmitSpecifiedDuration)
121{
122    Vector<Dictionary, 0> jsKeyframes;
123    RefPtrWillBeRawPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, exceptionState);
124    EXPECT_TRUE(std::isnan(animation->specifiedTiming().iterationDuration));
125}
126
127TEST_F(AnimationAnimationV8Test, NegativeDurationIsAuto)
128{
129    Vector<Dictionary, 0> jsKeyframes;
130    RefPtrWillBeRawPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, -2, exceptionState);
131    EXPECT_TRUE(std::isnan(animation->specifiedTiming().iterationDuration));
132}
133
134TEST_F(AnimationAnimationV8Test, MismatchedKeyframePropertyRaisesException)
135{
136    Vector<Dictionary> jsKeyframes;
137    v8::Handle<v8::Object> keyframe1 = v8::Object::New(m_isolate);
138    v8::Handle<v8::Object> keyframe2 = v8::Object::New(m_isolate);
139
140    setV8ObjectPropertyAsString(keyframe1, "width", "100px");
141    setV8ObjectPropertyAsString(keyframe1, "offset", "0");
142
143    // Height property appears only in keyframe2
144    setV8ObjectPropertyAsString(keyframe2, "height", "100px");
145    setV8ObjectPropertyAsString(keyframe2, "width", "0px");
146    setV8ObjectPropertyAsString(keyframe2, "offset", "1");
147
148    jsKeyframes.append(Dictionary(keyframe1, m_isolate));
149    jsKeyframes.append(Dictionary(keyframe2, m_isolate));
150
151    createAnimation(element.get(), jsKeyframes, 0, exceptionState);
152
153    EXPECT_TRUE(exceptionState.hadException());
154    EXPECT_EQ(NotSupportedError, exceptionState.code());
155}
156
157TEST_F(AnimationAnimationV8Test, MissingOffsetZeroRaisesException)
158{
159    Vector<Dictionary> jsKeyframes;
160    v8::Handle<v8::Object> keyframe1 = v8::Object::New(m_isolate);
161    v8::Handle<v8::Object> keyframe2 = v8::Object::New(m_isolate);
162
163    setV8ObjectPropertyAsString(keyframe1, "width", "100px");
164    setV8ObjectPropertyAsString(keyframe1, "offset", "0.1");
165    setV8ObjectPropertyAsString(keyframe2, "width", "0px");
166    setV8ObjectPropertyAsString(keyframe2, "offset", "1");
167
168    jsKeyframes.append(Dictionary(keyframe1, m_isolate));
169    jsKeyframes.append(Dictionary(keyframe2, m_isolate));
170
171    createAnimation(element.get(), jsKeyframes, 0, exceptionState);
172
173    EXPECT_TRUE(exceptionState.hadException());
174    EXPECT_EQ(NotSupportedError, exceptionState.code());
175}
176
177TEST_F(AnimationAnimationV8Test, MissingOffsetOneRaisesException)
178{
179    Vector<Dictionary> jsKeyframes;
180    v8::Handle<v8::Object> keyframe1 = v8::Object::New(m_isolate);
181    v8::Handle<v8::Object> keyframe2 = v8::Object::New(m_isolate);
182
183    setV8ObjectPropertyAsString(keyframe1, "width", "100px");
184    setV8ObjectPropertyAsString(keyframe1, "offset", "0");
185    setV8ObjectPropertyAsString(keyframe2, "width", "0px");
186    setV8ObjectPropertyAsString(keyframe2, "offset", "0.1");
187
188    jsKeyframes.append(Dictionary(keyframe1, m_isolate));
189    jsKeyframes.append(Dictionary(keyframe2, m_isolate));
190
191    createAnimation(element.get(), jsKeyframes, 0, exceptionState);
192
193    EXPECT_TRUE(exceptionState.hadException());
194    EXPECT_EQ(NotSupportedError, exceptionState.code());
195}
196
197TEST_F(AnimationAnimationV8Test, MissingOffsetZeroAndOneRaisesException)
198{
199    Vector<Dictionary> jsKeyframes;
200    v8::Handle<v8::Object> keyframe1 = v8::Object::New(m_isolate);
201    v8::Handle<v8::Object> keyframe2 = v8::Object::New(m_isolate);
202
203    setV8ObjectPropertyAsString(keyframe1, "width", "100px");
204    setV8ObjectPropertyAsString(keyframe1, "offset", "0.1");
205    setV8ObjectPropertyAsString(keyframe2, "width", "0px");
206    setV8ObjectPropertyAsString(keyframe2, "offset", "0.2");
207
208    jsKeyframes.append(Dictionary(keyframe1, m_isolate));
209    jsKeyframes.append(Dictionary(keyframe2, m_isolate));
210
211    createAnimation(element.get(), jsKeyframes, 0, exceptionState);
212
213    EXPECT_TRUE(exceptionState.hadException());
214    EXPECT_EQ(NotSupportedError, exceptionState.code());
215}
216
217TEST_F(AnimationAnimationV8Test, SpecifiedGetters)
218{
219    Vector<Dictionary, 0> jsKeyframes;
220
221    v8::Handle<v8::Object> timingInput = v8::Object::New(m_isolate);
222    setV8ObjectPropertyAsNumber(timingInput, "delay", 2);
223    setV8ObjectPropertyAsNumber(timingInput, "endDelay", 0.5);
224    setV8ObjectPropertyAsString(timingInput, "fill", "backwards");
225    setV8ObjectPropertyAsNumber(timingInput, "iterationStart", 2);
226    setV8ObjectPropertyAsNumber(timingInput, "iterations", 10);
227    setV8ObjectPropertyAsNumber(timingInput, "playbackRate", 2);
228    setV8ObjectPropertyAsString(timingInput, "direction", "reverse");
229    setV8ObjectPropertyAsString(timingInput, "easing", "step-start");
230    Dictionary timingInputDictionary = Dictionary(v8::Handle<v8::Value>::Cast(timingInput), m_isolate);
231
232    RefPtrWillBeRawPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, timingInputDictionary, exceptionState);
233
234    RefPtrWillBeRawPtr<AnimationNodeTiming> specified = animation->timing();
235    EXPECT_EQ(2, specified->delay());
236    EXPECT_EQ(0.5, specified->endDelay());
237    EXPECT_EQ("backwards", specified->fill());
238    EXPECT_EQ(2, specified->iterationStart());
239    EXPECT_EQ(10, specified->iterations());
240    EXPECT_EQ(2, specified->playbackRate());
241    EXPECT_EQ("reverse", specified->direction());
242    EXPECT_EQ("step-start", specified->easing());
243}
244
245TEST_F(AnimationAnimationV8Test, SpecifiedDurationGetter)
246{
247    Vector<Dictionary, 0> jsKeyframes;
248
249    v8::Handle<v8::Object> timingInputWithDuration = v8::Object::New(m_isolate);
250    setV8ObjectPropertyAsNumber(timingInputWithDuration, "duration", 2.5);
251    Dictionary timingInputDictionaryWithDuration = Dictionary(v8::Handle<v8::Value>::Cast(timingInputWithDuration), m_isolate);
252
253    RefPtrWillBeRawPtr<Animation> animationWithDuration = createAnimation(element.get(), jsKeyframes, timingInputDictionaryWithDuration, exceptionState);
254
255    RefPtrWillBeRawPtr<AnimationNodeTiming> specifiedWithDuration = animationWithDuration->timing();
256    Nullable<double> numberDuration;
257    String stringDuration;
258    specifiedWithDuration->getDuration("duration", numberDuration, stringDuration);
259    EXPECT_FALSE(numberDuration.isNull());
260    EXPECT_EQ(2.5, numberDuration.get());
261    EXPECT_TRUE(stringDuration.isNull());
262
263
264    v8::Handle<v8::Object> timingInputNoDuration = v8::Object::New(m_isolate);
265    Dictionary timingInputDictionaryNoDuration = Dictionary(v8::Handle<v8::Value>::Cast(timingInputNoDuration), m_isolate);
266
267    RefPtrWillBeRawPtr<Animation> animationNoDuration = createAnimation(element.get(), jsKeyframes, timingInputDictionaryNoDuration, exceptionState);
268
269    RefPtrWillBeRawPtr<AnimationNodeTiming> specifiedNoDuration = animationNoDuration->timing();
270    Nullable<double> numberDuration2;
271    String stringDuration2;
272    specifiedNoDuration->getDuration("duration", numberDuration2, stringDuration2);
273    EXPECT_TRUE(numberDuration2.isNull());
274    EXPECT_FALSE(stringDuration2.isNull());
275    EXPECT_EQ("auto", stringDuration2);
276}
277
278TEST_F(AnimationAnimationV8Test, SpecifiedSetters)
279{
280    Vector<Dictionary, 0> jsKeyframes;
281    v8::Handle<v8::Object> timingInput = v8::Object::New(m_isolate);
282    Dictionary timingInputDictionary = Dictionary(v8::Handle<v8::Value>::Cast(timingInput), m_isolate);
283    RefPtrWillBeRawPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, timingInputDictionary, exceptionState);
284
285    RefPtrWillBeRawPtr<AnimationNodeTiming> specified = animation->timing();
286
287    EXPECT_EQ(0, specified->delay());
288    specified->setDelay(2);
289    EXPECT_EQ(2, specified->delay());
290
291    EXPECT_EQ(0, specified->endDelay());
292    specified->setEndDelay(0.5);
293    EXPECT_EQ(0.5, specified->endDelay());
294
295    EXPECT_EQ("auto", specified->fill());
296    specified->setFill("backwards");
297    EXPECT_EQ("backwards", specified->fill());
298
299    EXPECT_EQ(0, specified->iterationStart());
300    specified->setIterationStart(2);
301    EXPECT_EQ(2, specified->iterationStart());
302
303    EXPECT_EQ(1, specified->iterations());
304    specified->setIterations(10);
305    EXPECT_EQ(10, specified->iterations());
306
307    EXPECT_EQ(1, specified->playbackRate());
308    specified->setPlaybackRate(2);
309    EXPECT_EQ(2, specified->playbackRate());
310
311    EXPECT_EQ("normal", specified->direction());
312    specified->setDirection("reverse");
313    EXPECT_EQ("reverse", specified->direction());
314
315    EXPECT_EQ("linear", specified->easing());
316    specified->setEasing("step-start");
317    EXPECT_EQ("step-start", specified->easing());
318}
319
320TEST_F(AnimationAnimationV8Test, SetSpecifiedDuration)
321{
322    Vector<Dictionary, 0> jsKeyframes;
323    v8::Handle<v8::Object> timingInput = v8::Object::New(m_isolate);
324    Dictionary timingInputDictionary = Dictionary(v8::Handle<v8::Value>::Cast(timingInput), m_isolate);
325    RefPtrWillBeRawPtr<Animation> animation = createAnimation(element.get(), jsKeyframes, timingInputDictionary, exceptionState);
326
327    RefPtrWillBeRawPtr<AnimationNodeTiming> specified = animation->timing();
328
329    Nullable<double> numberDuration;
330    String stringDuration;
331    specified->getDuration("duration", numberDuration, stringDuration);
332    EXPECT_TRUE(numberDuration.isNull());
333    EXPECT_FALSE(stringDuration.isNull());
334    EXPECT_EQ("auto", stringDuration);
335
336    specified->setDuration("duration", 2.5);
337    Nullable<double> numberDuration2;
338    String stringDuration2;
339    specified->getDuration("duration", numberDuration2, stringDuration2);
340    EXPECT_FALSE(numberDuration2.isNull());
341    EXPECT_EQ(2.5, numberDuration2.get());
342    EXPECT_TRUE(stringDuration2.isNull());
343}
344
345TEST_F(AnimationAnimationTest, TimeToEffectChange)
346{
347    Timing timing;
348    timing.iterationDuration = 100;
349    timing.startDelay = 100;
350    timing.endDelay = 100;
351    timing.fillMode = Timing::FillModeNone;
352    RefPtrWillBeRawPtr<Animation> animation = Animation::create(0, nullptr, timing);
353    RefPtrWillBeRawPtr<AnimationPlayer> player = document.timeline().play(animation.get());
354    double inf = std::numeric_limits<double>::infinity();
355
356    EXPECT_EQ(100, animation->timeToForwardsEffectChange());
357    EXPECT_EQ(inf, animation->timeToReverseEffectChange());
358
359    player->setCurrentTimeInternal(100);
360    EXPECT_EQ(0, animation->timeToForwardsEffectChange());
361    EXPECT_EQ(0, animation->timeToReverseEffectChange());
362
363    player->setCurrentTimeInternal(199);
364    EXPECT_EQ(0, animation->timeToForwardsEffectChange());
365    EXPECT_EQ(0, animation->timeToReverseEffectChange());
366
367    player->setCurrentTimeInternal(200);
368    // End-exclusive.
369    EXPECT_EQ(inf, animation->timeToForwardsEffectChange());
370    EXPECT_EQ(0, animation->timeToReverseEffectChange());
371
372    player->setCurrentTimeInternal(300);
373    EXPECT_EQ(inf, animation->timeToForwardsEffectChange());
374    EXPECT_EQ(100, animation->timeToReverseEffectChange());
375}
376
377TEST_F(AnimationAnimationTest, TimeToEffectChangeWithPlaybackRate)
378{
379    Timing timing;
380    timing.iterationDuration = 100;
381    timing.startDelay = 100;
382    timing.endDelay = 100;
383    timing.playbackRate = 2;
384    timing.fillMode = Timing::FillModeNone;
385    RefPtrWillBeRawPtr<Animation> animation = Animation::create(0, nullptr, timing);
386    RefPtrWillBeRawPtr<AnimationPlayer> player = document.timeline().play(animation.get());
387    double inf = std::numeric_limits<double>::infinity();
388
389    EXPECT_EQ(100, animation->timeToForwardsEffectChange());
390    EXPECT_EQ(inf, animation->timeToReverseEffectChange());
391
392    player->setCurrentTimeInternal(100);
393    EXPECT_EQ(0, animation->timeToForwardsEffectChange());
394    EXPECT_EQ(0, animation->timeToReverseEffectChange());
395
396    player->setCurrentTimeInternal(149);
397    EXPECT_EQ(0, animation->timeToForwardsEffectChange());
398    EXPECT_EQ(0, animation->timeToReverseEffectChange());
399
400    player->setCurrentTimeInternal(150);
401    // End-exclusive.
402    EXPECT_EQ(inf, animation->timeToForwardsEffectChange());
403    EXPECT_EQ(0, animation->timeToReverseEffectChange());
404
405    player->setCurrentTimeInternal(200);
406    EXPECT_EQ(inf, animation->timeToForwardsEffectChange());
407    EXPECT_EQ(50, animation->timeToReverseEffectChange());
408}
409
410TEST_F(AnimationAnimationTest, TimeToEffectChangeWithNegativePlaybackRate)
411{
412    Timing timing;
413    timing.iterationDuration = 100;
414    timing.startDelay = 100;
415    timing.endDelay = 100;
416    timing.playbackRate = -2;
417    timing.fillMode = Timing::FillModeNone;
418    RefPtrWillBeRawPtr<Animation> animation = Animation::create(0, nullptr, timing);
419    RefPtrWillBeRawPtr<AnimationPlayer> player = document.timeline().play(animation.get());
420    double inf = std::numeric_limits<double>::infinity();
421
422    EXPECT_EQ(100, animation->timeToForwardsEffectChange());
423    EXPECT_EQ(inf, animation->timeToReverseEffectChange());
424
425    player->setCurrentTimeInternal(100);
426    EXPECT_EQ(0, animation->timeToForwardsEffectChange());
427    EXPECT_EQ(0, animation->timeToReverseEffectChange());
428
429    player->setCurrentTimeInternal(149);
430    EXPECT_EQ(0, animation->timeToForwardsEffectChange());
431    EXPECT_EQ(0, animation->timeToReverseEffectChange());
432
433    player->setCurrentTimeInternal(150);
434    EXPECT_EQ(inf, animation->timeToForwardsEffectChange());
435    EXPECT_EQ(0, animation->timeToReverseEffectChange());
436
437    player->setCurrentTimeInternal(200);
438    EXPECT_EQ(inf, animation->timeToForwardsEffectChange());
439    EXPECT_EQ(50, animation->timeToReverseEffectChange());
440}
441
442TEST_F(AnimationAnimationTest, ElementDestructorClearsAnimationTarget)
443{
444    // This test expects incorrect behaviour should be removed once Element
445    // and Animation are moved to Oilpan. See crbug.com/362404 for context.
446    Timing timing;
447    timing.iterationDuration = 5;
448    RefPtrWillBeRawPtr<Animation> animation = Animation::create(element.get(), nullptr, timing);
449    EXPECT_EQ(element.get(), animation->target());
450    document.timeline().play(animation.get());
451    pageHolder.clear();
452    element.clear();
453#if !ENABLE(OILPAN)
454    EXPECT_EQ(0, animation->target());
455#endif
456}
457
458} // namespace blink
459