summaryrefslogtreecommitdiffstats
path: root/dom/animation/test/css-transitions/file_animation-starttime.html
blob: a156ba0a08984ca47ead4783819b86666ab3a623 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
<!doctype html>
<html>
  <head>
    <meta charset=utf-8>
    <title>Tests for the effect of setting a CSS transition's
           Animation.startTime</title>
    <style>

.animated-div {
  margin-left: 100px;
  transition: margin-left 1000s linear 1000s;
}

    </style>
    <script src="../testcommon.js"></script>
  </head>
  <body>
    <script type="text/javascript">

'use strict';

// TODO: Once the computedTiming property is implemented, add checks to the
// checker helpers to ensure that computedTiming's properties are updated as
// expected.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055


const ANIM_DELAY_MS = 1000000; // 1000s
const ANIM_DUR_MS = 1000000; // 1000s

/**
 * These helpers get the value that the startTime needs to be set to, to put an
 * animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into the
 * middle of various phases or points through the active duration.
 */
function startTimeForBeforePhase(timeline) {
  return timeline.currentTime - ANIM_DELAY_MS / 2;
}
function startTimeForActivePhase(timeline) {
  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS / 2;
}
function startTimeForAfterPhase(timeline) {
  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS - ANIM_DELAY_MS / 2;
}
function startTimeForStartOfActiveInterval(timeline) {
  return timeline.currentTime - ANIM_DELAY_MS;
}
function startTimeForFiftyPercentThroughActiveInterval(timeline) {
  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS * 0.5;
}
function startTimeForEndOfActiveInterval(timeline) {
  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS;
}


// Expected computed 'margin-left' values at points during the active interval:
// When we assert_between_inclusive using these values we could in theory cause
// intermittent failure due to very long delays between paints, but since the
// active duration is 1000s long, a delay would need to be around 100s to cause
// that. If that's happening then there are likely other issues that should be
// fixed, so a failure to make us look into that seems like a good thing.
const INITIAL_POSITION = 100;
const TEN_PCT_POSITION = 110;
const FIFTY_PCT_POSITION = 150;
const END_POSITION = 200;

// The terms used for the naming of the following helper functions refer to
// terms used in the Web Animations specification for specific phases of an
// animation. The terms can be found here:
//
//   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
//
// Note the distinction between the "animation start time" which occurs before
// the start delay and the start of the active interval which occurs after it.

// Called when the ready Promise's callbacks should happen
function checkStateOnReadyPromiseResolved(animation)
{
  assert_less_than_equal(animation.startTime, animation.timeline.currentTime,
    'Animation.startTime should be less than the timeline\'s ' +
    'currentTime on the first paint tick after animation creation');

  assert_equals(animation.playState, 'running',
    'Animation.playState should be "running" on the first paint ' +
    'tick after animation creation');

  var div = animation.effect.target;
  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
  assert_equals(marginLeft, INITIAL_POSITION,
                'the computed value of margin-left should be unaffected ' +
                'by an animation with a delay on ready Promise resolve');
}

// Called when startTime is set to the time the active interval starts.
function checkStateAtActiveIntervalStartTime(animation)
{
  // We don't test animation.startTime since our caller just set it.

  assert_equals(animation.playState, 'running',
    'Animation.playState should be "running" at the start of ' +
    'the active interval');

  var div = animation.effect.target;
  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
    'the computed value of margin-left should be close to the value at the ' +
    'beginning of the animation');
}

function checkStateAtFiftyPctOfActiveInterval(animation)
{
  // We don't test animation.startTime since our caller just set it.

  var div = animation.effect.target;
  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
  assert_equals(marginLeft, FIFTY_PCT_POSITION,
    'the computed value of margin-left should be half way through the ' +
    'animation at the midpoint of the active interval');
}

// Called when startTime is set to the time the active interval ends.
function checkStateAtActiveIntervalEndTime(animation)
{
  // We don't test animation.startTime since our caller just set it.

  assert_equals(animation.playState, 'finished',
    'Animation.playState should be "finished" at the end of ' +
    'the active interval');

  var div = animation.effect.target;
  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
  assert_equals(marginLeft, END_POSITION,
    'the computed value of margin-left should be the final transitioned-to ' +
    'value at the end of the active duration');
}

test(function(t)
{
  var div = addDiv(t, {'class': 'animated-div'});
  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];
  assert_equals(animation.startTime, null, 'startTime is unresolved');
}, 'startTime of a newly created transition is unresolved');


test(function(t)
{
  var div = addDiv(t, {'class': 'animated-div'});
  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];
  var currentTime = animation.timeline.currentTime;
  animation.startTime = currentTime;
  assert_approx_equals(animation.startTime, currentTime, 0.0001, // rounding error
    'Check setting of startTime actually works');
}, 'Sanity test to check round-tripping assigning to new animation\'s ' +
   'startTime');


async_test(function(t) {
  var div = addDiv(t, {'class': 'animated-div'});
  var eventWatcher = new EventWatcher(t, div, 'transitionend');

  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];

  animation.ready.then(t.step_func(function() {
    checkStateOnReadyPromiseResolved(animation);

    animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
    checkStateAtActiveIntervalStartTime(animation);

    animation.startTime =
      startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
    checkStateAtFiftyPctOfActiveInterval(animation);

    animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
    return eventWatcher.wait_for('transitionend');
  })).then(t.step_func(function() {
    checkStateAtActiveIntervalEndTime(animation);
  })).catch(t.step_func(function(reason) {
    assert_unreached(reason);
  })).then(function() {
    t.done();
  });
}, 'Skipping forward through animation');


test(function(t) {
  var div = addDiv(t, {'class': 'animated-div'});
  var eventWatcher = new EventWatcher(t, div, 'transitionend');

  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];

  // Unlike in the case of CSS animations, we cannot skip to the end and skip
  // backwards since when we reach the end the transition effect is removed and
  // changes to the Animation object no longer affect the element. For
  // this reason we only skip forwards as far as the 90% through point.

  animation.startTime =
    startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
  checkStateAtFiftyPctOfActiveInterval(animation);

  animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);

  // Despite going backwards from being in the active interval to being before
  // it, we now expect an 'animationend' event because the animation should go
  // from being active to inactive.
  //
  // Calling checkStateAtActiveIntervalStartTime will check computed style,
  // causing computed style to be updated and the 'transitionend' event to
  // be dispatched synchronously. We need to call waitForEvent first
  // otherwise eventWatcher will assert that the event was unexpected.
  eventWatcher.wait_for('transitionend').then(function() {
    t.done();
  });
  checkStateAtActiveIntervalStartTime(animation);
}, 'Skipping backwards through transition');


async_test(function(t) {
  var div = addDiv(t, {'class': 'animated-div'});

  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];

  var storedCurrentTime;

  animation.ready.then(t.step_func(function() {
    storedCurrentTime = animation.currentTime;
    animation.startTime = null;
    return animation.ready;
  })).catch(t.step_func(function(reason) {
    assert_unreached(reason);
  })).then(t.step_func(function() {
    assert_equals(animation.currentTime, storedCurrentTime,
      'Test that hold time is correct');
    t.done();
  }));
}, 'Setting startTime to null');


async_test(function(t) {
  var div = addDiv(t, {'class': 'animated-div'});

  flushComputedStyle(div);
  div.style.marginLeft = '200px'; // initiate transition

  var animation = div.getAnimations()[0];

  animation.ready.then(t.step_func(function() {
    var savedStartTime = animation.startTime;

    assert_not_equals(animation.startTime, null,
      'Animation.startTime not null on ready Promise resolve');

    animation.pause();
    return animation.ready;
  })).then(t.step_func(function() {
    assert_equals(animation.startTime, null,
      'Animation.startTime is null after paused');
    assert_equals(animation.playState, 'paused',
      'Animation.playState is "paused" after pause() call');
  })).catch(t.step_func(function(reason) {
    assert_unreached(reason);
  })).then(function() {
    t.done();
  });
}, 'Animation.startTime after paused');

done();
    </script>
  </body>
</html>