summaryrefslogtreecommitdiffstats
path: root/dom/animation/test/mozilla/file_deferred_start.html
blob: 863fc80fec2633773aad9b7aa15684f81d533b95 (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
<!doctype html>
<meta charset=utf-8>
<script src="../testcommon.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<style>
@keyframes empty { }
.target {
  /* Element needs geometry to be eligible for layerization */
  width: 100px;
  height: 100px;
  background-color: white;
}
</style>
<body>
<script>
'use strict';

function waitForDocLoad() {
  return new Promise((resolve, reject) => {
    if (document.readyState === 'complete') {
      resolve();
    } else {
      window.addEventListener('load', resolve);
    }
  });
}

function waitForPaints() {
  return new Promise((resolve, reject) => {
    waitForAllPaintsFlushed(resolve);
  });
}

promise_test(async t => {
  // Test that empty animations actually start.
  //
  // Normally we tie the start of animations to when their first frame of
  // the animation is rendered. However, for animations that don't actually
  // trigger a paint (e.g. because they are empty, or are animating something
  // that doesn't render or is offscreen) we want to make sure they still
  // start.
  //
  // Before we start, wait for the document to finish loading, then create
  // div element, and wait for painting. This is because during loading we will
  // have other paint events taking place which might, by luck, happen to
  // trigger animations that otherwise would not have been triggered, leading to
  // false positives.
  //
  // As a result, it's better to wait until we have a more stable state before
  // continuing.
  await waitForDocLoad();

  const div = addDiv(t);

  await waitForPaints();

  div.style.animation = 'empty 1000s';
  const animation = div.getAnimations()[0];

  let promiseCallbackDone = false;
  animation.ready.then(() => {
    promiseCallbackDone = true;
  }).catch(() => {
    assert_unreached('ready promise was rejected');
  });

  // We need to wait for up to three frames. This is because in some
  // cases it can take up to two frames for the initial layout
  // to take place. Even after that happens we don't actually resolve the
  // ready promise until the following tick.
  await waitForAnimationFrames(3);

  assert_true(promiseCallbackDone,
              'ready promise for an empty animation was resolved'
              + ' within three animation frames');
}, 'Animation.ready is resolved for an empty animation');

// Test that compositor animations with delays get synced correctly
//
// NOTE: It is important that we DON'T use
// SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh here since that takes
// us through a different code path.
promise_test(async t => {
  assert_false(SpecialPowers.DOMWindowUtils.isTestControllingRefreshes,
               'Test should run without the refresh driver being under'
               + ' test control');

  // This test only applies to compositor animations
  if (!isOMTAEnabled()) {
    return;
  }

  const div = addDiv(t, { class: 'target' });

  // As with the above test, any stray paints can cause this test to produce
  // a false negative (that is, pass when it should fail). To avoid this we
  // wait for paints and only then do we commence the test.
  await waitForPaints();

  const animation =
    div.animate({ transform: [ 'translate(0px)', 'translate(100px)' ] },
                { duration: 400 * MS_PER_SEC,
                  delay: -200 * MS_PER_SEC });

  await waitForAnimationReadyToRestyle(animation);

  await waitForPaints();

  const transformStr =
    SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
  const translateX = getTranslateXFromTransform(transformStr);

  // If the delay has been applied we should be about half-way through
  // the animation. However, if we applied it twice we will be at the
  // end of the animation already so check that we are roughly half way
  // through.
  assert_between_inclusive(translateX, 40, 75,
      'Animation is about half-way through on the compositor');
}, 'Starting an animation with a delay starts from the correct point');

// Test that compositor animations with a playback rate start at the
// appropriate point.
//
// NOTE: As with the previous test, it is important that we DON'T use
// SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh here since that takes
// us through a different code path.
promise_test(async t => {
  assert_false(SpecialPowers.DOMWindowUtils.isTestControllingRefreshes,
               'Test should run without the refresh driver being under'
               + ' test control');

  // This test only applies to compositor animations
  if (!isOMTAEnabled()) {
    return;
  }

  const div = addDiv(t, { class: 'target' });

  // Wait for the document to load and painting (see notes in previous test).
  await waitForPaints();

  const animation =
    div.animate({ transform: [ 'translate(0px)', 'translate(100px)' ] },
                200 * MS_PER_SEC);
  animation.currentTime = 100 * MS_PER_SEC;
  animation.playbackRate = 0.1;

  await waitForPaints();

  const transformStr =
    SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
  const translateX = getTranslateXFromTransform(transformStr);

  // We pass the playback rate to the compositor independently and we have
  // tests to ensure that it is correctly applied there. However, if, when
  // we resolve the start time of the pending animation, we fail to
  // incorporate the playback rate, we will end up starting from the wrong
  // point and the current time calculated on the compositor will be wrong.
  assert_between_inclusive(translateX, 25, 75,
      'Animation is about half-way through on the compositor');
}, 'Starting an animation with a playbackRate starts from the correct point');

function getTranslateXFromTransform(transformStr) {
  const matrixComponents =
    transformStr.startsWith('matrix(')
    ? transformStr.substring('matrix('.length, transformStr.length-1)
                  .split(',')
                  .map(component => Number(component))
    : [];
  assert_equals(matrixComponents.length, 6,
                'Got a valid transform matrix on the compositor'
                + ' (got: "' + transformStr + '")');

  return matrixComponents[4];
}

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