summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/web-animations/testcommon.js
blob: 31ebdfaf279deca00ef5384fd7d347e1cf00e8e1 (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
/*
Distributed under both the W3C Test Suite License [1] and the W3C
3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
policies and contribution forms [3].

[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
[3] http://www.w3.org/2004/10/27-testcases
 */

'use strict';

var MS_PER_SEC = 1000;

// The recommended minimum precision to use for time values[1].
//
// [1] https://w3c.github.io/web-animations/#precision-of-time-values
var TIME_PRECISION = 0.0005; // ms

// Allow implementations to substitute an alternative method for comparing
// times based on their precision requirements.
if (!window.assert_times_equal) {
  window.assert_times_equal = function(actual, expected, description) {
    assert_approx_equals(actual, expected, TIME_PRECISION, description);
  }
}

// creates div element, appends it to the document body and
// removes the created element during test cleanup
function createDiv(test, doc) {
  return createElement(test, 'div', doc);
}

// creates element of given tagName, appends it to the document body and
// removes the created element during test cleanup
// if tagName is null or undefined, returns div element
function createElement(test, tagName, doc) {
  if (!doc) {
    doc = document;
  }
  var element = doc.createElement(tagName || 'div');
  doc.body.appendChild(element);
  test.add_cleanup(function() {
    element.remove();
  });
  return element;
}

// Creates a style element with the specified rules, appends it to the document
// head and removes the created element during test cleanup.
// |rules| is an object. For example:
// { '@keyframes anim': '' ,
//   '.className': 'animation: anim 100s;' };
// or
// { '.className1::before': 'content: ""; width: 0px; transition: all 10s;',
//   '.className2::before': 'width: 100px;' };
// The object property name could be a keyframes name, or a selector.
// The object property value is declarations which are property:value pairs
// split by a space.
function createStyle(test, rules, doc) {
  if (!doc) {
    doc = document;
  }
  var extraStyle = doc.createElement('style');
  doc.head.appendChild(extraStyle);
  if (rules) {
    var sheet = extraStyle.sheet;
    for (var selector in rules) {
      sheet.insertRule(selector + '{' + rules[selector] + '}',
                       sheet.cssRules.length);
    }
  }
  test.add_cleanup(function() {
    extraStyle.remove();
  });
}

// Create a pseudo element
function createPseudo(test, type) {
  createStyle(test, { '@keyframes anim': '',
                      ['.pseudo::' + type]: 'animation: anim 10s;' });
  var div = createDiv(test);
  div.classList.add('pseudo');
  var anims = document.getAnimations();
  assert_true(anims.length >= 1);
  var anim = anims[anims.length - 1];
  assert_equals(anim.effect.target.parentElement, div);
  assert_equals(anim.effect.target.type, '::' + type);
  anim.cancel();
  return anim.effect.target;
}

// Convert px unit value to a Number
function pxToNum(str) {
  return Number(String(str).match(/^(-?[\d.]+)px$/)[1]);
}

// Cubic bezier with control points (0, 0), (x1, y1), (x2, y2), and (1, 1).
function cubicBezier(x1, y1, x2, y2) {
  function xForT(t) {
    var omt = 1-t;
    return 3 * omt * omt * t * x1 + 3 * omt * t * t * x2 + t * t * t;
  }

  function yForT(t) {
    var omt = 1-t;
    return 3 * omt * omt * t * y1 + 3 * omt * t * t * y2 + t * t * t;
  }

  function tForX(x) {
    // Binary subdivision.
    var mint = 0, maxt = 1;
    for (var i = 0; i < 30; ++i) {
      var guesst = (mint + maxt) / 2;
      var guessx = xForT(guesst);
      if (x < guessx) {
        maxt = guesst;
      } else {
        mint = guesst;
      }
    }
    return (mint + maxt) / 2;
  }

  return function bezierClosure(x) {
    if (x == 0) {
      return 0;
    }
    if (x == 1) {
      return 1;
    }
    return yForT(tForX(x));
  }
}

function stepEnd(nsteps) {
  return function stepEndClosure(x) {
    return Math.floor(x * nsteps) / nsteps;
  }
}

function stepStart(nsteps) {
  return function stepStartClosure(x) {
    var result = Math.floor(x * nsteps + 1.0) / nsteps;
    return (result > 1.0) ? 1.0 : result;
  }
}

function waitForAnimationFrames(frameCount) {
  return new Promise(function(resolve, reject) {
    function handleFrame() {
      if (--frameCount <= 0) {
        resolve();
      } else {
        window.requestAnimationFrame(handleFrame); // wait another frame
      }
    }
    window.requestAnimationFrame(handleFrame);
  });
}

// Continually calls requestAnimationFrame until |minDelay| has elapsed
// as recorded using document.timeline.currentTime (i.e. frame time not
// wall-clock time).
function waitForAnimationFramesWithDelay(minDelay) {
  var startTime = document.timeline.currentTime;
  return new Promise(function(resolve) {
    (function handleFrame() {
      if (document.timeline.currentTime - startTime >= minDelay) {
        resolve();
      } else {
        window.requestAnimationFrame(handleFrame);
      }
    }());
  });
}