Upstream version 5.34.104.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 startEvent = webkitPrefix ? 'webkitAnimationStart' : 'animationstart';
57   var endEvent = webkitPrefix ? 'webkitAnimationEnd' : 'animationend';
58   var testCount = 0;
59   var animationEventCount = 0;
60   // FIXME: This should be 0, but 0 duration animations are broken in at least
61   // pre-Web-Animations Blink, WebKit and Gecko.
62   var durationSeconds = 0.001;
63   var iterationCount = 0.5;
64   var delaySeconds = 0;
65   var cssText = '.test:hover:before {\n' +
66       '  content: attr(description);\n' +
67       '  position: absolute;\n' +
68       '  z-index: 1000;\n' +
69       '  background: gold;\n' +
70       '}\n';
71   var fragment = document.createDocumentFragment();
72   var style = document.createElement('style');
73   var afterTestCallback = null;
74   fragment.appendChild(style);
75
76   var updateScheduled = false;
77   function maybeScheduleUpdate() {
78     if (updateScheduled) {
79       return;
80     }
81     updateScheduled = true;
82     setTimeout(function() {
83       updateScheduled = false;
84       style.innerHTML = cssText;
85       document.body.appendChild(fragment);
86     }, 0);
87   }
88
89   function dumpResults() {
90     var targets = document.querySelectorAll('.target.active');
91     if (isRefTest) {
92       // Convert back to reference to avoid cases where the computed style is
93       // out of sync with the compositor.
94       for (var i = 0; i < targets.length; i++) {
95         targets[i].convertToReference();
96       }
97       style.parentNode.removeChild(style);
98     } else {
99       var resultString = '';
100       for (var i = 0; i < targets.length; i++) {
101         resultString += targets[i].getResultString() + '\n';
102       }
103       var results = document.createElement('div');
104       results.style.whiteSpace = 'pre';
105       results.textContent = resultString;
106       results.id = 'results';
107       document.body.appendChild(results);
108     }
109   }
110
111   function convertToReference() {
112     console.assert(isRefTest);
113     var scripts = document.querySelectorAll('script');
114     for (var i = 0; i < scripts.length; i++) {
115       scripts[i].parentNode.removeChild(scripts[i]);
116     }
117     style.parentNode.removeChild(style);
118     var html = document.documentElement.outerHTML;
119     document.documentElement.style.whiteSpace = 'pre';
120     document.documentElement.textContent = html;
121   }
122
123   function afterTest(callback) {
124     afterTestCallback = callback;
125   }
126
127   function runAsRefTest() {
128     console.assert(!isRefTest);
129     isRefTest = true;
130   }
131
132   // Constructs a timing function which produces 'y' at x = 0.5
133   function createEasing(y) {
134     // FIXME: if 'y' is > 0 and < 1 use a linear timing function and allow
135     // 'x' to vary. Use a bezier only for values < 0 or > 1.
136     if (y == 0) {
137       return 'steps(1, end)';
138     }
139     if (y == 1) {
140       return 'steps(1, start)';
141     }
142     if (y == 0.5) {
143       return 'steps(2, end)';
144     }
145     // Approximate using a bezier.
146     var b = (8 * y - 1) / 6;
147     return 'cubic-bezier(0, ' + b + ', 1, ' + b + ')';
148   }
149
150   function testInterpolationAt(fractions, params) {
151     if (!Array.isArray(fractions)) {
152       fractions = [fractions];
153     }
154     assertInterpolation(params, fractions.map(function(fraction) {
155       return {at: fraction};
156     }));
157   }
158
159   function describeTest(params) {
160     return params.property + ': from [' + params.from + '] to [' + params.to + ']';
161   }
162
163   function assertInterpolation(params, expectations) {
164     // If the prefixed property is not supported, try to unprefix it.
165     if (/^-[^-]+-/.test(params.property) && !CSS.supports(params.property, 'initial')) {
166       var unprefixed = params.property.replace(/^-[^-]+-/, '');
167       if (CSS.supports(unprefixed, 'initial')) {
168         params.property = unprefixed;
169       }
170     }
171     var testId = defineKeyframes(params);
172     var nextCaseId = 0;
173     var testContainer = document.createElement('div');
174     testContainer.setAttribute('description', describeTest(params));
175     testContainer.classList.add('test');
176     testContainer.classList.add(testId);
177     fragment.appendChild(testContainer);
178     expectations.forEach(function(expectation) {
179       testContainer.appendChild(makeInterpolationTest(
180           expectation.at, testId, 'case-' + ++nextCaseId, params, expectation.is));
181     });
182     maybeScheduleUpdate();
183   }
184
185   var nextKeyframeId = 0;
186   function defineKeyframes(params) {
187     var testId = 'test-' + ++nextKeyframeId;
188     cssText += '@' + webkitPrefix + 'keyframes ' + testId + ' { \n' +
189         '  0% { ' + params.property + ': ' + params.from + '; }\n' +
190         '  100% { ' + params.property + ': ' + params.to + '; }\n' +
191         '}\n';
192     return testId;
193   }
194
195   function normalizeValue(value) {
196     return value.
197         // Round numbers to two decimal places.
198         replace(/-?\d*\.\d+/g, function(n) {
199           return (parseFloat(n).toFixed(2)).
200               replace(/\.0*$/, '').
201               replace(/^-0$/, '0');
202         }).
203         // Place whitespace between tokens.
204         replace(/([\w\d.]+|[^\s])/g, '$1 ').
205         replace(/\s+/g, ' ');
206   }
207
208   function createTargetContainer(id) {
209     var targetContainer = document.createElement('div');
210     var template = document.querySelector('#target-template');
211     if (template) {
212       targetContainer.appendChild(template.content.cloneNode(true));
213       // Remove whitespace text nodes at start / end.
214       while (targetContainer.firstChild.nodeType != Node.ELEMENT_NODE && !/\S/.test(targetContainer.firstChild.nodeValue)) {
215         targetContainer.removeChild(targetContainer.firstChild);
216       }
217       while (targetContainer.lastChild.nodeType != Node.ELEMENT_NODE && !/\S/.test(targetContainer.lastChild.nodeValue)) {
218         targetContainer.removeChild(targetContainer.lastChild);
219       }
220       // If the template contains just one element, use that rather than a wrapper div.
221       if (targetContainer.children.length == 1 && targetContainer.childNodes.length == 1) {
222         targetContainer = targetContainer.firstChild;
223         targetContainer.remove();
224       }
225     }
226     var target = targetContainer.querySelector('.target') || targetContainer;
227     target.classList.add('target');
228     target.classList.add(id);
229     return targetContainer;
230   }
231
232   function sanitizeUrls(value) {
233     var matches = value.match(/url\([^\)]*\)/g);
234     if (matches !== null) {
235       for (var i = 0; i < matches.length; ++i) {
236         var url = /url\(([^\)]*)\)/g.exec(matches[i])[1];
237         var anchor = document.createElement('a');
238         anchor.href = url;
239         anchor.pathname = '...' + anchor.pathname.substring(anchor.pathname.lastIndexOf('/'));
240         value = value.replace(matches[i], 'url(' + anchor.href + ')');
241       }
242     }
243     return value;
244   }
245
246   function makeInterpolationTest(fraction, testId, caseId, params, expectation) {
247     console.assert(expectation === undefined || !isRefTest);
248     var targetContainer = createTargetContainer(caseId);
249     var target = targetContainer.querySelector('.target') || targetContainer;
250     target.classList.add('active');
251     var replicaContainer, replica;
252     if (expectation !== undefined) {
253       replicaContainer = createTargetContainer(caseId);
254       replica = replicaContainer.querySelector('.target') || replicaContainer;
255       replica.classList.add('replica');
256       replica.style.setProperty(params.property, expectation);
257     }
258     target.getResultString = function() {
259       if (!CSS.supports(params.property, expectation)) {
260         return 'FAIL: [' + params.property + ': ' + expectation + '] is not supported';
261       }
262       var value = getComputedStyle(this).getPropertyValue(params.property);
263       var result = '';
264       var reason = '';
265       if (expectation !== undefined) {
266         var parsedExpectation = getComputedStyle(replica).getPropertyValue(params.property);
267         var pass = normalizeValue(value) === normalizeValue(parsedExpectation);
268         result = pass ? 'PASS: ' : 'FAIL: ';
269         reason = pass ? '' : ', expected [' + expectation + ']' +
270             (expectation === parsedExpectation ? '' : ' (parsed as [' + sanitizeUrls(parsedExpectation) + '])');
271         value = pass ? expectation : sanitizeUrls(value);
272       }
273       return result + params.property + ' from [' + params.from + '] to ' +
274           '[' + params.to + '] was [' + value + ']' +
275           ' at ' + fraction + reason;
276     };
277     target.convertToReference = function() {
278       this.style[params.property] = getComputedStyle(this).getPropertyValue(params.property);
279     };
280     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++;
288     var testFragment = document.createDocumentFragment();
289     testFragment.appendChild(targetContainer);
290     replica && testFragment.appendChild(replicaContainer);
291     testFragment.appendChild(document.createTextNode('\n'));
292     return testFragment;
293   }
294
295   var finished = false;
296   function finishTest() {
297     finished = true;
298     dumpResults();
299     if (afterTestCallback) {
300       afterTestCallback();
301     }
302     if (window.testRunner) {
303       if (!isRefTest) {
304         var results = document.querySelector('#results');
305         document.documentElement.textContent = '';
306         document.documentElement.appendChild(results);
307         testRunner.dumpAsText();
308       }
309       testRunner.notifyDone();
310     }
311   }
312
313   if (window.testRunner) {
314     testRunner.waitUntilDone();
315   }
316
317   function isLastAnimationEvent() {
318     return !finished && animationEventCount === testCount;
319   }
320
321   function endEventListener() {
322     animationEventCount++;
323     if (!isLastAnimationEvent()) {
324       return;
325     }
326     finishTest();
327   }
328
329   if (window.internals) {
330     durationSeconds = 0;
331     document.documentElement.addEventListener(endEvent, endEventListener);
332   } else if (webkitPrefix) {
333     durationSeconds = 1e9;
334     iterationCount = 1;
335     delaySeconds = -durationSeconds / 2;
336     document.documentElement.addEventListener(startEvent, function() {
337       animationEventCount++;
338       if (!isLastAnimationEvent()) {
339         return;
340       }
341       setTimeout(finishTest, 0);
342     });
343   } else {
344     document.documentElement.addEventListener(endEvent, endEventListener);
345   }
346
347   if (!window.testRunner) {
348     setTimeout(function() {
349       if (finished) {
350         return;
351       }
352       finishTest();
353     }, 10000);
354   }
355
356   window.runAsRefTest = runAsRefTest;
357   window.testInterpolationAt = testInterpolationAt;
358   window.assertInterpolation = assertInterpolation;
359   window.convertToReference = convertToReference;
360   window.afterTest = afterTest;
361 })();