OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
46 * results. This function may not be used in a ref test. | 46 * results. This function may not be used in a ref test. |
47 * * convertToReference - This is intended to be used interactively to | 47 * * convertToReference - This is intended to be used interactively to |
48 * construct a reference given the results of a test. To build a | 48 * construct a reference given the results of a test. To build a |
49 * reference, run the test, open the inspector and trigger this | 49 * reference, run the test, open the inspector and trigger this |
50 * function, then copy/paste the results. | 50 * function, then copy/paste the results. |
51 */ | 51 */ |
52 'use strict'; | 52 'use strict'; |
53 (function() { | 53 (function() { |
54 var webkitPrefix = 'webkitAnimation' in document.documentElement.style ? '-web
kit-' : ''; | 54 var webkitPrefix = 'webkitAnimation' in document.documentElement.style ? '-web
kit-' : ''; |
55 var isRefTest = false; | 55 var isRefTest = false; |
| 56 var webAnimationsTest = typeof Element.prototype.animate === 'function'; |
56 var startEvent = webkitPrefix ? 'webkitAnimationStart' : 'animationstart'; | 57 var startEvent = webkitPrefix ? 'webkitAnimationStart' : 'animationstart'; |
57 var endEvent = webkitPrefix ? 'webkitAnimationEnd' : 'animationend'; | 58 var endEvent = webkitPrefix ? 'webkitAnimationEnd' : 'animationend'; |
58 var testCount = 0; | 59 var testCount = 0; |
59 var animationEventCount = 0; | 60 var animationEventCount = 0; |
60 // FIXME: This should be 0, but 0 duration animations are broken in at least | 61 // FIXME: This should be 0, but 0 duration animations are broken in at least |
61 // pre-Web-Animations Blink, WebKit and Gecko. | 62 // pre-Web-Animations Blink, WebKit and Gecko. |
62 var durationSeconds = 0.001; | 63 var durationSeconds = 0.001; |
63 var iterationCount = 0.5; | 64 var iterationCount = 0.5; |
64 var delaySeconds = 0; | 65 var delaySeconds = 0; |
65 var cssText = '.test:hover:before {\n' + | 66 var cssText = '.test:hover:before {\n' + |
66 ' content: attr(description);\n' + | 67 ' content: attr(description);\n' + |
67 ' position: absolute;\n' + | 68 ' position: absolute;\n' + |
68 ' z-index: 1000;\n' + | 69 ' z-index: 1000;\n' + |
69 ' background: gold;\n' + | 70 ' background: gold;\n' + |
70 '}\n'; | 71 '}\n'; |
71 var fragment = document.createDocumentFragment(); | 72 var fragment = document.createDocumentFragment(); |
| 73 var fragmentAttachedListeners = []; |
72 var style = document.createElement('style'); | 74 var style = document.createElement('style'); |
| 75 var cssTests = document.createElement('div'); |
| 76 cssTests.id = 'css-tests'; |
| 77 cssTests.textContent = 'CSS Animations:'; |
73 var afterTestCallback = null; | 78 var afterTestCallback = null; |
74 fragment.appendChild(style); | 79 fragment.appendChild(style); |
| 80 fragment.appendChild(cssTests); |
| 81 |
| 82 if (webAnimationsTest) { |
| 83 var waTests = document.createElement('div'); |
| 84 waTests.id = 'web-animations-tests'; |
| 85 waTests.textContent = 'Web Animations API:'; |
| 86 fragment.appendChild(waTests); |
| 87 } |
75 | 88 |
76 var updateScheduled = false; | 89 var updateScheduled = false; |
77 function maybeScheduleUpdate() { | 90 function maybeScheduleUpdate() { |
78 if (updateScheduled) { | 91 if (updateScheduled) { |
79 return; | 92 return; |
80 } | 93 } |
81 updateScheduled = true; | 94 updateScheduled = true; |
82 setTimeout(function() { | 95 setTimeout(function() { |
83 updateScheduled = false; | 96 updateScheduled = false; |
84 style.innerHTML = cssText; | 97 style.innerHTML = cssText; |
85 document.body.appendChild(fragment); | 98 document.body.appendChild(fragment); |
| 99 fragmentAttachedListeners.forEach(function(listener) {listener();}); |
86 }, 0); | 100 }, 0); |
87 } | 101 } |
88 | 102 |
89 function dumpResults() { | 103 function dumpResults() { |
90 var targets = document.querySelectorAll('.target.active'); | 104 var targets = document.querySelectorAll('.target.active'); |
91 if (isRefTest) { | 105 if (isRefTest) { |
92 // Convert back to reference to avoid cases where the computed style is | 106 // Convert back to reference to avoid cases where the computed style is |
93 // out of sync with the compositor. | 107 // out of sync with the compositor. |
94 for (var i = 0; i < targets.length; i++) { | 108 for (var i = 0; i < targets.length; i++) { |
95 targets[i].convertToReference(); | 109 targets[i].convertToReference(); |
96 } | 110 } |
97 style.parentNode.removeChild(style); | 111 style.parentNode.removeChild(style); |
98 } else { | 112 } else { |
99 var resultString = ''; | 113 var cssResultString = 'CSS Animations:\n'; |
| 114 var waResultString = 'Web Animations API:\n'; |
100 for (var i = 0; i < targets.length; i++) { | 115 for (var i = 0; i < targets.length; i++) { |
101 resultString += targets[i].getResultString() + '\n'; | 116 if (targets[i].testType === 'css') { |
| 117 cssResultString += targets[i].getResultString() + '\n'; |
| 118 } else { |
| 119 waResultString += targets[i].getResultString() + '\n'; |
| 120 } |
102 } | 121 } |
103 var results = document.createElement('div'); | 122 var results = document.createElement('pre'); |
104 results.style.whiteSpace = 'pre'; | 123 results.textContent = cssResultString + (webAnimationsTest ? '\n' + waResu
ltString : ''); |
105 results.textContent = resultString; | |
106 results.id = 'results'; | 124 results.id = 'results'; |
107 document.body.appendChild(results); | 125 document.body.appendChild(results); |
108 } | 126 } |
109 } | 127 } |
110 | 128 |
111 function convertToReference() { | 129 function convertToReference() { |
112 console.assert(isRefTest); | 130 console.assert(isRefTest); |
113 var scripts = document.querySelectorAll('script'); | 131 var scripts = document.querySelectorAll('script'); |
114 for (var i = 0; i < scripts.length; i++) { | 132 for (var i = 0; i < scripts.length; i++) { |
115 scripts[i].parentNode.removeChild(scripts[i]); | 133 scripts[i].parentNode.removeChild(scripts[i]); |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 | 167 |
150 function testInterpolationAt(fractions, params) { | 168 function testInterpolationAt(fractions, params) { |
151 if (!Array.isArray(fractions)) { | 169 if (!Array.isArray(fractions)) { |
152 fractions = [fractions]; | 170 fractions = [fractions]; |
153 } | 171 } |
154 assertInterpolation(params, fractions.map(function(fraction) { | 172 assertInterpolation(params, fractions.map(function(fraction) { |
155 return {at: fraction}; | 173 return {at: fraction}; |
156 })); | 174 })); |
157 } | 175 } |
158 | 176 |
159 function describeTest(params) { | 177 function createTestContainer(description, className) { |
160 return params.property + ': from [' + params.from + '] to [' + params.to + '
]'; | 178 var testContainer = document.createElement('div'); |
| 179 testContainer.setAttribute('description', description); |
| 180 testContainer.classList.add('test'); |
| 181 if (className) { |
| 182 testContainer.classList.add(className); |
| 183 } |
| 184 return testContainer; |
| 185 } |
| 186 |
| 187 function convertPropertyToCamelCase(property) { |
| 188 return property.replace(/^-/, '').replace(/-\w/g, function(m) {return m[1].t
oUpperCase();}); |
| 189 } |
| 190 |
| 191 function describeCSSTest(params) { |
| 192 return 'CSS ' + params.property + ': from [' + params.from + '] to [' + para
ms.to + ']'; |
| 193 } |
| 194 |
| 195 function describeWATest(params) { |
| 196 return 'element.animate() ' + convertPropertyToCamelCase(params.property) +
': from [' + params.from + '] to [' + params.to + ']'; |
161 } | 197 } |
162 | 198 |
163 function assertInterpolation(params, expectations) { | 199 function assertInterpolation(params, expectations) { |
164 // If the prefixed property is not supported, try to unprefix it. | 200 // If the prefixed property is not supported, try to unprefix it. |
165 if (/^-[^-]+-/.test(params.property) && !CSS.supports(params.property, 'init
ial')) { | 201 if (/^-[^-]+-/.test(params.property) && !CSS.supports(params.property, 'init
ial')) { |
166 var unprefixed = params.property.replace(/^-[^-]+-/, ''); | 202 var unprefixed = params.property.replace(/^-[^-]+-/, ''); |
167 if (CSS.supports(unprefixed, 'initial')) { | 203 if (CSS.supports(unprefixed, 'initial')) { |
168 params.property = unprefixed; | 204 params.property = unprefixed; |
169 } | 205 } |
170 } | 206 } |
171 var testId = defineKeyframes(params); | 207 var testId = defineKeyframes(params); |
172 var nextCaseId = 0; | 208 var nextCaseId = 0; |
173 var testContainer = document.createElement('div'); | 209 var cssTestContainer = createTestContainer(describeCSSTest(params), testId); |
174 testContainer.setAttribute('description', describeTest(params)); | 210 cssTests.appendChild(cssTestContainer); |
175 testContainer.classList.add('test'); | 211 if (webAnimationsTest) { |
176 testContainer.classList.add(testId); | 212 var waTestContainer = createTestContainer(describeWATest(params), testId); |
177 fragment.appendChild(testContainer); | 213 waTests.appendChild(waTestContainer); |
| 214 } |
178 expectations.forEach(function(expectation) { | 215 expectations.forEach(function(expectation) { |
179 testContainer.appendChild(makeInterpolationTest( | 216 cssTestContainer.appendChild(makeInterpolationTest( |
180 expectation.at, testId, 'case-' + ++nextCaseId, params, expectation.is
)); | 217 'css', expectation.at, testId, 'case-' + ++nextCaseId, params, expecta
tion.is)); |
181 }); | 218 }); |
| 219 if (webAnimationsTest) { |
| 220 expectations.forEach(function(expectation) { |
| 221 waTestContainer.appendChild(makeInterpolationTest( |
| 222 'web-animations', expectation.at, testId, 'case-' + ++nextCaseId, pa
rams, expectation.is)); |
| 223 }); |
| 224 } |
182 maybeScheduleUpdate(); | 225 maybeScheduleUpdate(); |
183 } | 226 } |
184 | 227 |
185 var nextKeyframeId = 0; | 228 var nextKeyframeId = 0; |
186 function defineKeyframes(params) { | 229 function defineKeyframes(params) { |
187 var testId = 'test-' + ++nextKeyframeId; | 230 var testId = 'test-' + ++nextKeyframeId; |
188 cssText += '@' + webkitPrefix + 'keyframes ' + testId + ' { \n' + | 231 cssText += '@' + webkitPrefix + 'keyframes ' + testId + ' { \n' + |
189 ' 0% { ' + params.property + ': ' + params.from + '; }\n' + | 232 ' 0% { ' + params.property + ': ' + params.from + '; }\n' + |
190 ' 100% { ' + params.property + ': ' + params.to + '; }\n' + | 233 ' 100% { ' + params.property + ': ' + params.to + '; }\n' + |
191 '}\n'; | 234 '}\n'; |
192 return testId; | 235 return testId; |
193 } | 236 } |
194 | 237 |
195 function normalizeValue(value) { | 238 function roundNumbers(value) { |
196 return value. | 239 return value. |
197 // Round numbers to two decimal places. | 240 // Round numbers to two decimal places. |
198 replace(/-?\d*\.\d+/g, function(n) { | 241 replace(/-?\d*\.\d+/g, function(n) { |
199 return (parseFloat(n).toFixed(2)). | 242 return (parseFloat(n).toFixed(2)). |
200 replace(/\.0*$/, ''). | 243 replace(/\.\d+/, function(m) { |
| 244 return m.replace(/0+$/, ''); |
| 245 }). |
| 246 replace(/\.$/, ''). |
201 replace(/^-0$/, '0'); | 247 replace(/^-0$/, '0'); |
202 }). | 248 }); |
| 249 } |
| 250 |
| 251 function normalizeValue(value) { |
| 252 return roundNumbers(value). |
203 // Place whitespace between tokens. | 253 // Place whitespace between tokens. |
204 replace(/([\w\d.]+|[^\s])/g, '$1 '). | 254 replace(/([\w\d.]+|[^\s])/g, '$1 '). |
205 replace(/\s+/g, ' '); | 255 replace(/\s+/g, ' '); |
206 } | 256 } |
207 | 257 |
208 function createTargetContainer(id) { | 258 function createTargetContainer(id) { |
209 var targetContainer = document.createElement('div'); | 259 var targetContainer = document.createElement('div'); |
210 var template = document.querySelector('#target-template'); | 260 var template = document.querySelector('#target-template'); |
211 if (template) { | 261 if (template) { |
212 targetContainer.appendChild(template.content.cloneNode(true)); | 262 targetContainer.appendChild(template.content.cloneNode(true)); |
(...skipping 23 matching lines...) Expand all Loading... |
236 var url = /url\(([^\)]*)\)/g.exec(matches[i])[1]; | 286 var url = /url\(([^\)]*)\)/g.exec(matches[i])[1]; |
237 var anchor = document.createElement('a'); | 287 var anchor = document.createElement('a'); |
238 anchor.href = url; | 288 anchor.href = url; |
239 anchor.pathname = '...' + anchor.pathname.substring(anchor.pathname.last
IndexOf('/')); | 289 anchor.pathname = '...' + anchor.pathname.substring(anchor.pathname.last
IndexOf('/')); |
240 value = value.replace(matches[i], 'url(' + anchor.href + ')'); | 290 value = value.replace(matches[i], 'url(' + anchor.href + ')'); |
241 } | 291 } |
242 } | 292 } |
243 return value; | 293 return value; |
244 } | 294 } |
245 | 295 |
246 function makeInterpolationTest(fraction, testId, caseId, params, expectation)
{ | 296 function makeInterpolationTest(testType, fraction, testId, caseId, params, exp
ectation) { |
247 console.assert(expectation === undefined || !isRefTest); | 297 console.assert(expectation === undefined || !isRefTest); |
248 var targetContainer = createTargetContainer(caseId); | 298 var targetContainer = createTargetContainer(caseId); |
249 var target = targetContainer.querySelector('.target') || targetContainer; | 299 var target = targetContainer.querySelector('.target') || targetContainer; |
250 target.classList.add('active'); | 300 target.classList.add('active'); |
251 var replicaContainer, replica; | 301 var replicaContainer, replica; |
252 if (expectation !== undefined) { | 302 if (expectation !== undefined) { |
253 replicaContainer = createTargetContainer(caseId); | 303 replicaContainer = createTargetContainer(caseId); |
254 replica = replicaContainer.querySelector('.target') || replicaContainer; | 304 replica = replicaContainer.querySelector('.target') || replicaContainer; |
255 replica.classList.add('replica'); | 305 replica.classList.add('replica'); |
256 replica.style.setProperty(params.property, expectation); | 306 replica.style.setProperty(params.property, expectation); |
257 } | 307 } |
| 308 target.testType = testType; |
258 target.getResultString = function() { | 309 target.getResultString = function() { |
259 if (!CSS.supports(params.property, expectation)) { | 310 if (!CSS.supports(params.property, expectation)) { |
260 return 'FAIL: [' + params.property + ': ' + expectation + '] is not supp
orted'; | 311 return 'FAIL: [' + params.property + ': ' + expectation + '] is not supp
orted'; |
261 } | 312 } |
262 var value = getComputedStyle(this).getPropertyValue(params.property); | 313 var value = getComputedStyle(this).getPropertyValue(params.property); |
263 var result = ''; | 314 var result = ''; |
264 var reason = ''; | 315 var reason = ''; |
| 316 var property = testType === 'css' ? params.property : convertPropertyToCam
elCase(params.property); |
265 if (expectation !== undefined) { | 317 if (expectation !== undefined) { |
266 var parsedExpectation = getComputedStyle(replica).getPropertyValue(param
s.property); | 318 var parsedExpectation = getComputedStyle(replica).getPropertyValue(param
s.property); |
267 var pass = normalizeValue(value) === normalizeValue(parsedExpectation); | 319 var pass = normalizeValue(value) === normalizeValue(parsedExpectation); |
268 result = pass ? 'PASS: ' : 'FAIL: '; | 320 result = pass ? 'PASS: ' : 'FAIL: '; |
269 reason = pass ? '' : ', expected [' + expectation + ']' + | 321 reason = pass ? '' : ', expected [' + expectation + ']' + |
270 (expectation === parsedExpectation ? '' : ' (parsed as [' + sanitize
Urls(parsedExpectation) + '])'); | 322 (expectation === parsedExpectation ? '' : ' (parsed as [' + sanitize
Urls(roundNumbers(parsedExpectation)) + '])'); |
271 value = pass ? expectation : sanitizeUrls(value); | 323 value = pass ? expectation : sanitizeUrls(value); |
272 } | 324 } |
273 return result + params.property + ' from [' + params.from + '] to ' + | 325 return result + property + ' from [' + params.from + '] to ' + |
274 '[' + params.to + '] was [' + value + ']' + | 326 '[' + params.to + '] was [' + value + ']' + |
275 ' at ' + fraction + reason; | 327 ' at ' + fraction + reason; |
276 }; | 328 }; |
277 target.convertToReference = function() { | 329 target.convertToReference = function() { |
278 this.style[params.property] = getComputedStyle(this).getPropertyValue(para
ms.property); | 330 this.style[params.property] = getComputedStyle(this).getPropertyValue(para
ms.property); |
279 }; | 331 }; |
280 var easing = createEasing(fraction); | 332 var easing = createEasing(fraction); |
281 cssText += '.' + testId + ' .' + caseId + '.active {\n' + | |
282 ' ' + webkitPrefix + 'animation: ' + testId + ' ' + durationSeconds + '
s forwards;\n' + | |
283 ' ' + webkitPrefix + 'animation-timing-function: ' + easing + ';\n' + | |
284 ' ' + webkitPrefix + 'animation-iteration-count: ' + iterationCount + '
;\n' + | |
285 ' ' + webkitPrefix + 'animation-delay: ' + delaySeconds + 's;\n' + | |
286 '}\n'; | |
287 testCount++; | 333 testCount++; |
| 334 if (testType === 'css') { |
| 335 cssText += '.' + testId + ' .' + caseId + '.active {\n' + |
| 336 ' ' + webkitPrefix + 'animation: ' + testId + ' ' + durationSeconds +
's forwards;\n' + |
| 337 ' ' + webkitPrefix + 'animation-timing-function: ' + easing + ';\n' + |
| 338 ' ' + webkitPrefix + 'animation-iteration-count: ' + iterationCount +
';\n' + |
| 339 ' ' + webkitPrefix + 'animation-delay: ' + delaySeconds + 's;\n' + |
| 340 '}\n'; |
| 341 } else { |
| 342 var keyframes = [{}, {}]; |
| 343 keyframes[0][convertPropertyToCamelCase(params.property)] = params.from; |
| 344 keyframes[1][convertPropertyToCamelCase(params.property)] = params.to; |
| 345 fragmentAttachedListeners.push(function() { |
| 346 target.animate(keyframes, { |
| 347 fill: 'forwards', |
| 348 duration: 1, |
| 349 easing: easing, |
| 350 delay: -0.5, |
| 351 iterations: 0.5, |
| 352 }); |
| 353 animationEnded(); |
| 354 }); |
| 355 } |
288 var testFragment = document.createDocumentFragment(); | 356 var testFragment = document.createDocumentFragment(); |
289 testFragment.appendChild(targetContainer); | 357 testFragment.appendChild(targetContainer); |
290 replica && testFragment.appendChild(replicaContainer); | 358 replica && testFragment.appendChild(replicaContainer); |
291 testFragment.appendChild(document.createTextNode('\n')); | 359 testFragment.appendChild(document.createTextNode('\n')); |
292 return testFragment; | 360 return testFragment; |
293 } | 361 } |
294 | 362 |
295 var finished = false; | 363 var finished = false; |
296 function finishTest() { | 364 function finishTest() { |
297 finished = true; | 365 finished = true; |
(...skipping 13 matching lines...) Expand all Loading... |
311 } | 379 } |
312 | 380 |
313 if (window.testRunner) { | 381 if (window.testRunner) { |
314 testRunner.waitUntilDone(); | 382 testRunner.waitUntilDone(); |
315 } | 383 } |
316 | 384 |
317 function isLastAnimationEvent() { | 385 function isLastAnimationEvent() { |
318 return !finished && animationEventCount === testCount; | 386 return !finished && animationEventCount === testCount; |
319 } | 387 } |
320 | 388 |
321 function endEventListener() { | 389 function animationEnded() { |
322 animationEventCount++; | 390 animationEventCount++; |
323 if (!isLastAnimationEvent()) { | 391 if (!isLastAnimationEvent()) { |
324 return; | 392 return; |
325 } | 393 } |
326 finishTest(); | 394 finishTest(); |
327 } | 395 } |
328 | 396 |
329 if (window.internals) { | 397 if (window.internals) { |
330 durationSeconds = 0; | 398 durationSeconds = 0; |
331 document.documentElement.addEventListener(endEvent, endEventListener); | 399 document.documentElement.addEventListener(endEvent, animationEnded); |
332 } else if (webkitPrefix) { | 400 } else if (webkitPrefix) { |
333 durationSeconds = 1e9; | 401 durationSeconds = 1e9; |
334 iterationCount = 1; | 402 iterationCount = 1; |
335 delaySeconds = -durationSeconds / 2; | 403 delaySeconds = -durationSeconds / 2; |
336 document.documentElement.addEventListener(startEvent, function() { | 404 document.documentElement.addEventListener(startEvent, function() { |
337 animationEventCount++; | 405 animationEventCount++; |
338 if (!isLastAnimationEvent()) { | 406 if (!isLastAnimationEvent()) { |
339 return; | 407 return; |
340 } | 408 } |
341 setTimeout(finishTest, 0); | 409 setTimeout(finishTest, 0); |
342 }); | 410 }); |
343 } else { | 411 } else { |
344 document.documentElement.addEventListener(endEvent, endEventListener); | 412 document.documentElement.addEventListener(endEvent, animationEnded); |
345 } | 413 } |
346 | 414 |
347 if (!window.testRunner) { | 415 if (!window.testRunner) { |
348 setTimeout(function() { | 416 setTimeout(function() { |
349 if (finished) { | 417 if (finished) { |
350 return; | 418 return; |
351 } | 419 } |
352 finishTest(); | 420 finishTest(); |
353 }, 10000); | 421 }, 10000); |
354 } | 422 } |
355 | 423 |
356 window.runAsRefTest = runAsRefTest; | 424 window.runAsRefTest = runAsRefTest; |
357 window.testInterpolationAt = testInterpolationAt; | 425 window.testInterpolationAt = testInterpolationAt; |
358 window.assertInterpolation = assertInterpolation; | 426 window.assertInterpolation = assertInterpolation; |
359 window.convertToReference = convertToReference; | 427 window.convertToReference = convertToReference; |
360 window.afterTest = afterTest; | 428 window.afterTest = afterTest; |
361 })(); | 429 })(); |
OLD | NEW |