Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / LayoutTests / animations / interpolation / resources / interpolation-test.js
1 /*
2  * Copyright (C) 2013 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /*
32  * This script is intended to be used for constructing layout tests which
33  * exercise the interpolation functionaltiy of the animation system.
34  * Tests which run using this script should be portable across browsers.
35  *
36  * The following functions are exported:
37  *  * runAsRefTest - indicates that the test is a ref test and disables
38  *    dumping of textual output.
39  *  * testInterpolationAt([timeFractions], {property: x, from: y, to: z})
40  *    Constructs a test case for the interpolation of property x from
41  *    value y to value z at each of the times in timeFractions.
42  *  * assertInterpolation({property: x, from: y, to: z}, [{at: fraction, is: value}])
43  *    Constructs a test case which for each fraction will output a PASS
44  *    or FAIL depending on whether the interpolated result matches
45  *    'value'. Replica elements are constructed to aid eyeballing test
46  *    results. This function may not be used in a ref test.
47  *  * convertToReference - This is intended to be used interactively to
48  *    construct a reference given the results of a test. To build a
49  *    reference, run the test, open the inspector and trigger this
50  *    function, then copy/paste the results.
51  */
52 'use strict';
53 (function() {
54   var webkitPrefix = 'webkitAnimation' in document.documentElement.style ? '-webkit-' : '';
55   var isRefTest = false;
56   var webAnimationsTest = typeof Element.prototype.animate === 'function';
57   var startEvent = webkitPrefix ? 'webkitAnimationStart' : 'animationstart';
58   var endEvent = webkitPrefix ? 'webkitAnimationEnd' : 'animationend';
59   var testCount = 0;
60   var animationEventCount = 0;
61   // FIXME: This should be 0, but 0 duration animations are broken in at least
62   // pre-Web-Animations Blink, WebKit and Gecko.
63   var durationSeconds = 0.001;
64   var iterationCount = 0.5;
65   var delaySeconds = 0;
66   var cssText = '.test:hover:before {\n' +
67       '  content: attr(description);\n' +
68       '  position: absolute;\n' +
69       '  z-index: 1000;\n' +
70       '  background: gold;\n' +
71       '}\n';
72   var fragment = document.createDocumentFragment();
73   var fragmentAttachedListeners = [];
74   var style = document.createElement('style');
75   var cssTests = document.createElement('div');
76   cssTests.id = 'css-tests';
77   cssTests.textContent = 'CSS Animations:';
78   var afterTestCallback = null;
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   }
88
89   var updateScheduled = false;
90   function maybeScheduleUpdate() {
91     if (updateScheduled) {
92       return;
93     }
94     updateScheduled = true;
95     setTimeout(function() {
96       updateScheduled = false;
97       style.innerHTML = cssText;
98       document.body.appendChild(fragment);
99       fragmentAttachedListeners.forEach(function(listener) {listener();});
100     }, 0);
101   }
102
103   function dumpResults() {
104     var targets = document.querySelectorAll('.target.active');
105     if (isRefTest) {
106       // Convert back to reference to avoid cases where the computed style is
107       // out of sync with the compositor.
108       for (var i = 0; i < targets.length; i++) {
109         targets[i].convertToReference();
110       }
111       style.parentNode.removeChild(style);
112     } else {
113       var cssResultString = 'CSS Animations:\n';
114       var waResultString = 'Web Animations API:\n';
115       for (var i = 0; i < targets.length; i++) {
116         if (targets[i].testType === 'css') {
117           cssResultString += targets[i].getResultString() + '\n';
118         } else {
119           waResultString += targets[i].getResultString() + '\n';
120         }
121       }
122       var results = document.createElement('pre');
123       results.textContent = cssResultString + (webAnimationsTest ? '\n' + waResultString : '');
124       results.id = 'results';
125       document.body.appendChild(results);
126     }
127   }
128
129   function convertToReference() {
130     console.assert(isRefTest);
131     var scripts = document.querySelectorAll('script');
132     for (var i = 0; i < scripts.length; i++) {
133       scripts[i].parentNode.removeChild(scripts[i]);
134     }
135     style.parentNode.removeChild(style);
136     var html = document.documentElement.outerHTML;
137     document.documentElement.style.whiteSpace = 'pre';
138     document.documentElement.textContent = html;
139   }
140
141   function afterTest(callback) {
142     afterTestCallback = callback;
143   }
144
145   function runAsRefTest() {
146     console.assert(!isRefTest);
147     isRefTest = true;
148   }
149
150   // Constructs a timing function which produces 'y' at x = 0.5
151   function createEasing(y) {
152     // FIXME: if 'y' is > 0 and < 1 use a linear timing function and allow
153     // 'x' to vary. Use a bezier only for values < 0 or > 1.
154     if (y == 0) {
155       return 'steps(1, end)';
156     }
157     if (y == 1) {
158       return 'steps(1, start)';
159     }
160     if (y == 0.5) {
161       return 'steps(2, end)';
162     }
163     // Approximate using a bezier.
164     var b = (8 * y - 1) / 6;
165     return 'cubic-bezier(0, ' + b + ', 1, ' + b + ')';
166   }
167
168   function testInterpolationAt(fractions, params) {
169     if (!Array.isArray(fractions)) {
170       fractions = [fractions];
171     }
172     assertInterpolation(params, fractions.map(function(fraction) {
173       return {at: fraction};
174     }));
175   }
176
177   function createTestContainer(description, className) {
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].toUpperCase();});
189   }
190
191   function describeCSSTest(params) {
192     return 'CSS ' + params.property + ': from [' + params.from + '] to [' + params.to + ']';
193   }
194
195   function describeWATest(params) {
196     return 'element.animate() ' + convertPropertyToCamelCase(params.property) + ': from [' + params.from + '] to [' + params.to + ']';
197   }
198
199   function assertInterpolation(params, expectations) {
200     var testId = defineKeyframes(params);
201     var nextCaseId = 0;
202     var cssTestContainer = createTestContainer(describeCSSTest(params), testId);
203     cssTests.appendChild(cssTestContainer);
204     if (webAnimationsTest) {
205       var waTestContainer = createTestContainer(describeWATest(params), testId);
206       waTests.appendChild(waTestContainer);
207     }
208     expectations.forEach(function(expectation) {
209       cssTestContainer.appendChild(makeInterpolationTest(
210           'css', expectation.at, testId, 'case-' + ++nextCaseId, params, expectation.is));
211     });
212     if (webAnimationsTest) {
213       expectations.forEach(function(expectation) {
214         waTestContainer.appendChild(makeInterpolationTest(
215             'web-animations', expectation.at, testId, 'case-' + ++nextCaseId, params, expectation.is));
216       });
217     }
218     maybeScheduleUpdate();
219   }
220
221   var nextKeyframeId = 0;
222   function defineKeyframes(params) {
223     var testId = 'test-' + ++nextKeyframeId;
224     cssText += '@' + webkitPrefix + 'keyframes ' + testId + ' { \n' +
225         '  0% { ' + params.property + ': ' + params.from + '; }\n' +
226         '  100% { ' + params.property + ': ' + params.to + '; }\n' +
227         '}\n';
228     return testId;
229   }
230
231   function roundNumbers(value) {
232     return value.
233         // Round numbers to two decimal places.
234         replace(/-?\d*\.\d+/g, function(n) {
235           return (parseFloat(n).toFixed(2)).
236               replace(/\.\d+/, function(m) {
237                 return m.replace(/0+$/, '');
238               }).
239               replace(/\.$/, '').
240               replace(/^-0$/, '0');
241         });
242   }
243
244   function normalizeValue(value) {
245     return roundNumbers(value).
246         // Place whitespace between tokens.
247         replace(/([\w\d.]+|[^\s])/g, '$1 ').
248         replace(/\s+/g, ' ');
249   }
250
251   function createTargetContainer(id) {
252     var targetContainer = document.createElement('div');
253     var template = document.querySelector('#target-template');
254     if (template) {
255       targetContainer.appendChild(template.content.cloneNode(true));
256       // Remove whitespace text nodes at start / end.
257       while (targetContainer.firstChild.nodeType != Node.ELEMENT_NODE && !/\S/.test(targetContainer.firstChild.nodeValue)) {
258         targetContainer.removeChild(targetContainer.firstChild);
259       }
260       while (targetContainer.lastChild.nodeType != Node.ELEMENT_NODE && !/\S/.test(targetContainer.lastChild.nodeValue)) {
261         targetContainer.removeChild(targetContainer.lastChild);
262       }
263       // If the template contains just one element, use that rather than a wrapper div.
264       if (targetContainer.children.length == 1 && targetContainer.childNodes.length == 1) {
265         targetContainer = targetContainer.firstChild;
266         targetContainer.remove();
267       }
268     }
269     var target = targetContainer.querySelector('.target') || targetContainer;
270     target.classList.add('target');
271     target.classList.add(id);
272     return targetContainer;
273   }
274
275   function sanitizeUrls(value) {
276     var matches = value.match(/url\([^\)]*\)/g);
277     if (matches !== null) {
278       for (var i = 0; i < matches.length; ++i) {
279         var url = /url\(([^\)]*)\)/g.exec(matches[i])[1];
280         var anchor = document.createElement('a');
281         anchor.href = url;
282         anchor.pathname = '...' + anchor.pathname.substring(anchor.pathname.lastIndexOf('/'));
283         value = value.replace(matches[i], 'url(' + anchor.href + ')');
284       }
285     }
286     return value;
287   }
288
289   function makeInterpolationTest(testType, fraction, testId, caseId, params, expectation) {
290     console.assert(expectation === undefined || !isRefTest);
291     var targetContainer = createTargetContainer(caseId);
292     var target = targetContainer.querySelector('.target') || targetContainer;
293     target.classList.add('active');
294     var replicaContainer, replica;
295     if (expectation !== undefined) {
296       replicaContainer = createTargetContainer(caseId);
297       replica = replicaContainer.querySelector('.target') || replicaContainer;
298       replica.classList.add('replica');
299       replica.style.setProperty(params.property, expectation);
300     }
301     target.testType = testType;
302     target.getResultString = function() {
303       if (!CSS.supports(params.property, expectation)) {
304         return 'FAIL: [' + params.property + ': ' + expectation + '] is not supported';
305       }
306       var value = getComputedStyle(this).getPropertyValue(params.property);
307       var result = '';
308       var reason = '';
309       var property = testType === 'css' ? params.property : convertPropertyToCamelCase(params.property);
310       if (expectation !== undefined) {
311         var parsedExpectation = getComputedStyle(replica).getPropertyValue(params.property);
312         var pass = normalizeValue(value) === normalizeValue(parsedExpectation);
313         result = pass ? 'PASS: ' : 'FAIL: ';
314         reason = pass ? '' : ', expected [' + expectation + ']' +
315             (expectation === parsedExpectation ? '' : ' (parsed as [' + sanitizeUrls(roundNumbers(parsedExpectation)) + '])');
316         value = pass ? expectation : sanitizeUrls(value);
317       }
318       return result + property + ' from [' + params.from + '] to ' +
319           '[' + params.to + '] was [' + value + ']' +
320           ' at ' + fraction + reason;
321     };
322     target.convertToReference = function() {
323       this.style[params.property] = getComputedStyle(this).getPropertyValue(params.property);
324     };
325     var easing = createEasing(fraction);
326     testCount++;
327     if (testType === 'css') {
328       cssText += '.' + testId + ' .' + caseId + '.active {\n' +
329           '  ' + webkitPrefix + 'animation: ' + testId + ' ' + durationSeconds + 's forwards;\n' +
330           '  ' + webkitPrefix + 'animation-timing-function: ' + easing + ';\n' +
331           '  ' + webkitPrefix + 'animation-iteration-count: ' + iterationCount + ';\n' +
332           '  ' + webkitPrefix + 'animation-delay: ' + delaySeconds + 's;\n' +
333           '}\n';
334     } else {
335       var keyframes = [{}, {}];
336       keyframes[0][convertPropertyToCamelCase(params.property)] = params.from;
337       keyframes[1][convertPropertyToCamelCase(params.property)] = params.to;
338       fragmentAttachedListeners.push(function() {
339         target.animate(keyframes, {
340             fill: 'forwards',
341             duration: 1,
342             easing: easing,
343             delay: -0.5,
344             iterations: 0.5,
345           });
346         animationEnded();
347       });
348     }
349     var testFragment = document.createDocumentFragment();
350     testFragment.appendChild(targetContainer);
351     replica && testFragment.appendChild(replicaContainer);
352     testFragment.appendChild(document.createTextNode('\n'));
353     return testFragment;
354   }
355
356   var finished = false;
357   function finishTest() {
358     finished = true;
359     dumpResults();
360     if (afterTestCallback) {
361       afterTestCallback();
362     }
363     if (window.testRunner) {
364       if (!isRefTest) {
365         var results = document.querySelector('#results');
366         document.documentElement.textContent = '';
367         document.documentElement.appendChild(results);
368         testRunner.dumpAsText();
369       }
370       testRunner.notifyDone();
371     }
372   }
373
374   if (window.testRunner) {
375     testRunner.waitUntilDone();
376   }
377
378   function isLastAnimationEvent() {
379     return !finished && animationEventCount === testCount;
380   }
381
382   function animationEnded() {
383     animationEventCount++;
384     if (!isLastAnimationEvent()) {
385       return;
386     }
387     finishTest();
388   }
389
390   if (window.internals) {
391     durationSeconds = 0;
392     document.documentElement.addEventListener(endEvent, animationEnded);
393   } else if (webkitPrefix) {
394     durationSeconds = 1e9;
395     iterationCount = 1;
396     delaySeconds = -durationSeconds / 2;
397     document.documentElement.addEventListener(startEvent, function() {
398       animationEventCount++;
399       if (!isLastAnimationEvent()) {
400         return;
401       }
402       setTimeout(finishTest, 0);
403     });
404   } else {
405     document.documentElement.addEventListener(endEvent, animationEnded);
406   }
407
408   if (!window.testRunner) {
409     setTimeout(function() {
410       if (finished) {
411         return;
412       }
413       finishTest();
414     }, 10000);
415   }
416
417   function disableWebAnimationsTest() {
418     if (webAnimationsTest) {
419       fragment.querySelector('#web-animations-tests').remove();
420       webAnimationsTest = false;
421     }
422   }
423
424   window.runAsRefTest = runAsRefTest;
425   window.testInterpolationAt = testInterpolationAt;
426   window.assertInterpolation = assertInterpolation;
427   window.convertToReference = convertToReference;
428   window.afterTest = afterTest;
429   window.disableWebAnimationsTest = disableWebAnimationsTest;
430 })();