blob: fa53c6f5a700cfb76f1a7fff69a3e12d57342a4e [file] [log] [blame]
'use strict';
class CompositedAnimationTestCommon {
constructor(composited) {
this.composited = composited;
this.tests = [];
this.nextInstanceId = 1;
this.errorCount = 0;
this.createStyles();
this.createStaticElements();
}
createStyles() {
var styleSheet = document.createElement('style');
styleSheet.textContent = `
.item {
width: 20px;
height: 20px;
position: relative;
background: black;
float: left;
}
.marker {
width: 5px;
height: 5px;
display: inline-block;
background: orange;
margin: 15px;
}`;
document.head.appendChild(styleSheet);
}
createStaticElements() {
this.error = document.createElement('span');
this.error.style = 'color: red; font-family: monospace; font-size: 12px';
// The error element must have some painted content in order to be
// composited when animated in SPv2.
this.error.innerText = '(no errors)';
document.body.appendChild(this.error);
this.wrapper = document.createElement('div');
document.body.appendChild(this.wrapper);
}
createTestElements() {
this.tests.forEach(test => {
test.testWrapper = document.createElement('div');
this.wrapper.appendChild(test.testWrapper);
test.data.samples.forEach(sample => {
var element = document.createElement('div');
// Add marker custom style as inline style.
// Do not create marker if empty string specified.
if (test.data.markerStyle == null || test.data.markerStyle != '') {
var content = document.createElement('div');
content.classList.add('marker');
content.style.cssText = test.data.markerStyle;
element.appendChild(content);
}
element.classList.add('item');
// Add custom style as inline style.
var elementStyle = '';
if (this.suiteStyle)
elementStyle = this.suiteStyle;
if (test.data.style)
elementStyle += test.data.style;
if (elementStyle)
element.style.cssText = elementStyle;
// New line.
if (!test.testWrapper.hasChildNodes())
element.style.clear = 'left';
test.testWrapper.appendChild(element);
test.instances.push({
element: element,
animation: null,
id: this.nextInstanceId++
});
});
});
// Update all lifecycle phases to propagate all the objects to
// the compositor and to clear all the dirty flags.
if (window.internals)
internals.forceCompositingUpdate(document);
}
startAnimations() {
// We want to achieve desired accuracy for splines using a specific duration.
// TODO(loyso): Duration mustn't affect cc/blink consistency.
// Taken from cubic_bezier.cc:
var kBezierEpsilon = 1e-7;
// Reverse the blink::accuracyForDuration function to calculate duration
// from epsilon:
var duration = 1000 * 1.0 / (kBezierEpsilon * 200.0);
this.tests.forEach(test => {
if (test.instances.length != test.data.samples.length)
this.reportError(test, `instances.length=${test.instances.length} != samples.length=${test.data.samples.length}`);
for (var i = 0; i < test.instances.length; i++) {
var sample = test.data.samples[i];
var instance = test.instances[i];
// Use negative animation delays to specify sampled time for each animation.
instance.animation = instance.element.animate(test.data.keyframes, {
duration: duration,
iterations: Infinity,
delay: -duration * sample.at,
easing: test.data.easing
});
if (window.internals && !this.composited)
internals.disableCompositedAnimation(instance.animation);
}
});
if (window.internals)
internals.pauseAnimations(0);
}
assertAnimationCompositedState() {
this.tests.forEach(test => {
test.instances.forEach(instance => {
var composited = internals.isCompositedAnimation(instance.animation);
if (composited != this.composited)
this.reportError(test, `Animation ${composited ? 'is' : 'is not'} running on the compositor [id=${instance.id}].`);
});
});
}
reportError(test, message) {
if (this.errorCount == 0)
this.error.innerHTML = `${this.composited ? 'Tests:' : 'TestExpectations:'}<br>`;
if (this.errorCount > 0)
this.error.innerHTML += '<br>';
this.error.innerHTML += `${test.name}: ${message} `;
this.errorCount++;
}
waitForCompositor() {
return this.error.animate({opacity: ['1', '1']}, 1).finished;
}
layoutAndPaint() {
if (window.testRunner)
testRunner.waitUntilDone();
this.waitForCompositor().then(() => {
requestAnimationFrame(() => {
if (window.internals)
this.assertAnimationCompositedState();
if (window.testRunner)
testRunner.notifyDone();
});
});
}
registerTestsData(testSuiteData) {
this.suiteStyle = testSuiteData.style;
for (var testName in testSuiteData.tests) {
var testData = testSuiteData.tests[testName];
this.tests.push({
name: testName,
data: testData,
instances: []
});
}
}
run() {
this.createTestElements();
this.startAnimations();
this.layoutAndPaint();
}
}
class CompositedAnimationTest extends CompositedAnimationTestCommon {
constructor() {
var composited = true;
super(composited)
}
}
class CompositedAnimationTestExpected extends CompositedAnimationTestCommon {
constructor() {
var composited = false;
super(composited)
}
}
var runCompositedAnimationTests = function(testSuiteData) {
var test = new CompositedAnimationTest();
test.registerTestsData(testSuiteData);
test.run();
}
var runCompositedAnimationTestExpectations = function(testSuiteData) {
var test = new CompositedAnimationTestExpected();
test.registerTestsData(testSuiteData);
test.run();
}
var getLinearSamples = function(n, start, end) {
var arr = [];
var spread = end - start;
for (var i = 0; i <= n; i++)
arr.push(i * spread / n + start);
return arr.map(t => { return {at: t} });
}