--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -1,15 +1,14 @@
[DEFAULT]
support-files =
testcommon.js
../../imptests/testharness.js
../../imptests/testharnessreport.js
!/dom/animation/test/chrome/file_animate_xrays.html
- chrome/file_animation_performance_warning.html
[chrome/test_animate_xrays.html]
# file_animate_xrays.html needs to go in mochitest.ini since it is served
# over HTTP
[chrome/test_animation_observers_async.html]
[chrome/test_animation_observers_sync.html]
[chrome/test_animation_performance_warning.html]
[chrome/test_animation_properties.html]
deleted file mode 100644
--- a/dom/animation/test/chrome/file_animation_performance_warning.html
+++ /dev/null
@@ -1,1265 +0,0 @@
-<!doctype html>
-<head>
-<meta charset=utf-8>
-<title>Bug 1196114 - Test metadata related to which animation properties
- are running on the compositor</title>
-<script type="application/javascript" src="../testcommon.js"></script>
-<style>
-.compositable {
- /* Element needs geometry to be eligible for layerization */
- width: 100px;
- height: 100px;
- background-color: white;
-}
-@keyframes fade {
- from { opacity: 1 }
- to { opacity: 0 }
-}
-</style>
-</head>
-<body>
-<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1196114"
- target="_blank">Mozilla Bug 1196114</a>
-<div id="log"></div>
-<script>
-'use strict';
-
-// This is used for obtaining localized strings.
-var gStringBundle;
-
-W3CTest.runner.requestLongerTimeout(2);
-
-/**
- * Promise wrapper for requestIdleCallback.
- * NOTE: DON'T move this function into testcommon.js until requestIdleCallback
- * is enabled by default on all channels.
- */
-function waitForIdleCallback() {
- return new Promise(function(resolve, reject) {
- window.requestIdleCallback(resolve);
- });
-}
-
-function compare_property_state(a, b) {
- if (a.property > b.property) {
- return -1;
- } else if (a.property < b.property) {
- return 1;
- }
- if (a.runningOnCompositor != b.runningOnCompositor) {
- return a.runningOnCompositor ? 1 : -1;
- }
- return a.warning > b.warning ? -1 : 1;
-}
-
-function assert_animation_property_state_equals(actual, expected) {
- assert_equals(actual.length, expected.length, 'Number of properties');
-
- var sortedActual = actual.sort(compare_property_state);
- var sortedExpected = expected.sort(compare_property_state);
-
- for (var i = 0; i < sortedActual.length; i++) {
- assert_equals(sortedActual[i].property,
- sortedExpected[i].property,
- 'CSS property name should match');
- assert_equals(sortedActual[i].runningOnCompositor,
- sortedExpected[i].runningOnCompositor,
- 'runningOnCompositor property should match');
- if (sortedExpected[i].warning instanceof RegExp) {
- assert_regexp_match(sortedActual[i].warning,
- sortedExpected[i].warning,
- 'warning message should match');
- } else if (sortedExpected[i].warning) {
- assert_equals(sortedActual[i].warning,
- gStringBundle.GetStringFromName(sortedExpected[i].warning),
- 'warning message should match');
- }
- }
-}
-
-// Check that the animation is running on compositor and
-// warning property is not set for the CSS property regardless
-// expected values.
-function assert_all_properties_running_on_compositor(actual, expected) {
- assert_equals(actual.length, expected.length);
-
- var sortedActual = actual.sort(compare_property_state);
- var sortedExpected = expected.sort(compare_property_state);
-
- for (var i = 0; i < sortedActual.length; i++) {
- assert_equals(sortedActual[i].property,
- sortedExpected[i].property,
- 'CSS property name should match');
- assert_true(sortedActual[i].runningOnCompositor,
- 'runningOnCompositor property should be true on ' +
- sortedActual[i].property);
- assert_not_exists(sortedActual[i], 'warning',
- 'warning property should not be set');
- }
-}
-
-function testBasicOperation() {
- [
- {
- desc: 'animations on compositor',
- frames: {
- opacity: [0, 1]
- },
- expected: [
- {
- property: 'opacity',
- runningOnCompositor: true
- }
- ]
- },
- {
- desc: 'animations on main thread',
- frames: {
- backgroundColor: ['white', 'red']
- },
- expected: [
- {
- property: 'background-color',
- runningOnCompositor: false
- }
- ]
- },
- {
- desc: 'animations on both threads',
- frames: {
- backgroundColor: ['white', 'red'],
- transform: ['translate(0px)', 'translate(100px)']
- },
- expected: [
- {
- property: 'background-color',
- runningOnCompositor: false
- },
- {
- property: 'transform',
- runningOnCompositor: true
- }
- ]
- },
- {
- desc: 'two animation properties on compositor thread',
- frames: {
- opacity: [0, 1],
- transform: ['translate(0px)', 'translate(100px)']
- },
- expected: [
- {
- property: 'opacity',
- runningOnCompositor: true
- },
- {
- property: 'transform',
- runningOnCompositor: true
- }
- ]
- },
- {
- desc: 'opacity on compositor with animation of geometric properties',
- frames: {
- width: ['100px', '200px'],
- opacity: [0, 1]
- },
- expected: [
- {
- property: 'width',
- runningOnCompositor: false
- },
- {
- property: 'opacity',
- runningOnCompositor: true
- }
- ]
- },
- ].forEach(subtest => {
- promise_test(function(t) {
- var animation = addDivAndAnimate(t, { class: 'compositable' },
- subtest.frames, 100 * MS_PER_SEC);
- return animation.ready.then(function() {
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- subtest.expected);
- });
- }, subtest.desc);
- });
-}
-
-// Test adding/removing a 'width' property on the same animation object.
-function testKeyframesWithGeometricProperties() {
- [
- {
- desc: 'transform',
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- expected: {
- withoutGeometric: [
- {
- property: 'transform',
- runningOnCompositor: true
- }
- ],
- withGeometric: [
- {
- property: 'width',
- runningOnCompositor: false
- },
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
- }
- ]
- }
- },
- {
- desc: 'opacity and transform',
- frames: {
- opacity: [0, 1],
- transform: ['translate(0px)', 'translate(100px)']
- },
- expected: {
- withoutGeometric: [
- {
- property: 'opacity',
- runningOnCompositor: true
- },
- {
- property: 'transform',
- runningOnCompositor: true
- }
- ],
- withGeometric: [
- {
- property: 'width',
- runningOnCompositor: false
- },
- {
- property: 'opacity',
- runningOnCompositor: true
- },
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
- }
- ]
- }
- },
- ].forEach(subtest => {
- promise_test(function(t) {
- var animation = addDivAndAnimate(t, { class: 'compositable' },
- subtest.frames, 100 * MS_PER_SEC);
- return animation.ready.then(function() {
- // First, a transform animation is running on compositor.
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- subtest.expected.withoutGeometric);
- }).then(function() {
- // Add a 'width' property.
- var keyframes = animation.effect.getKeyframes();
-
- keyframes[0].width = '100px';
- keyframes[1].width = '200px';
-
- animation.effect.setKeyframes(keyframes);
- return waitForFrame();
- }).then(function() {
- // Now the transform animation is not running on compositor because of
- // the 'width' property.
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- subtest.expected.withGeometric);
- }).then(function() {
- // Remove the 'width' property.
- var keyframes = animation.effect.getKeyframes();
-
- delete keyframes[0].width;
- delete keyframes[1].width;
-
- animation.effect.setKeyframes(keyframes);
- return waitForFrame();
- }).then(function() {
- // Finally, the transform animation is running on compositor.
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- subtest.expected.withoutGeometric);
- });
- }, 'An animation has: ' + subtest.desc);
- });
-}
-
-// Test that the expected set of geometric properties all block transform
-// animations.
-function testSetOfGeometricProperties() {
- const geometricProperties = [
- 'width', 'height',
- 'top', 'right', 'bottom', 'left',
- 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
- 'padding-top', 'padding-right', 'padding-bottom', 'padding-left'
- ];
-
- geometricProperties.forEach(property => {
- promise_test(function(t) {
- const keyframes = {
- [propertyToIDL(property)]: [ '100px', '200px' ],
- transform: [ 'translate(0px)', 'translate(100px)' ]
- };
- var animation = addDivAndAnimate(t, { class: 'compositable' },
- keyframes, 100 * MS_PER_SEC);
-
- return animation.ready.then(function() {
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- [
- {
- property,
- runningOnCompositor: false
- },
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
- }
- ]);
- }, 'Transform animation should be run on the main thread');
- }, `${property} is treated as a geometric property`);
- });
-}
-
-// Performance warning tests that set and clear a style property.
-function testStyleChanges() {
- [
- {
- desc: 'preserve-3d transform',
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- style: 'transform-style: preserve-3d',
- expected: [
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformPreserve3D'
- }
- ]
- },
- {
- desc: 'transform with backface-visibility:hidden',
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- style: 'backface-visibility: hidden;',
- expected: [
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
- }
- ]
- },
- {
- desc: 'opacity and transform with preserve-3d',
- frames: {
- opacity: [0, 1],
- transform: ['translate(0px)', 'translate(100px)']
- },
- style: 'transform-style: preserve-3d',
- expected: [
- {
- property: 'opacity',
- runningOnCompositor: true
- },
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformPreserve3D'
- }
- ]
- },
- {
- desc: 'opacity and transform with backface-visibility:hidden',
- frames: {
- opacity: [0, 1],
- transform: ['translate(0px)', 'translate(100px)']
- },
- style: 'backface-visibility: hidden;',
- expected: [
- {
- property: 'opacity',
- runningOnCompositor: true
- },
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
- }
- ]
- },
- ].forEach(subtest => {
- promise_test(function(t) {
- var animation = addDivAndAnimate(t, { class: 'compositable' },
- subtest.frames, 100 * MS_PER_SEC);
- return animation.ready.then(function() {
- assert_all_properties_running_on_compositor(
- animation.effect.getProperties(),
- subtest.expected);
- animation.effect.target.style = subtest.style;
- return waitForFrame();
- }).then(function() {
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- subtest.expected);
- animation.effect.target.style = '';
- return waitForFrame();
- }).then(function() {
- assert_all_properties_running_on_compositor(
- animation.effect.getProperties(),
- subtest.expected);
- });
- }, subtest.desc);
- });
-}
-
-// Performance warning tests that set and clear the id property
-function testIdChanges() {
- [
- {
- desc: 'moz-element referencing a transform',
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- id: 'transformed',
- createelement: 'width:100px; height:100px; background: -moz-element(#transformed)',
- expected: [
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningHasRenderingObserver'
- }
- ]
- },
- ].forEach(subtest => {
- promise_test(function(t) {
- if (subtest.createelement) {
- addDiv(t, { style: subtest.createelement });
- }
-
- var animation = addDivAndAnimate(t, { class: 'compositable' },
- subtest.frames, 100 * MS_PER_SEC);
- return animation.ready.then(function() {
- assert_all_properties_running_on_compositor(
- animation.effect.getProperties(),
- subtest.expected);
- animation.effect.target.id = subtest.id;
- return waitForFrame();
- }).then(function() {
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- subtest.expected);
- animation.effect.target.id = '';
- return waitForFrame();
- }).then(function() {
- assert_all_properties_running_on_compositor(
- animation.effect.getProperties(),
- subtest.expected);
- });
- }, subtest.desc);
- });
-}
-
-function testMultipleAnimations() {
- [
- {
- desc: 'opacity and transform with preserve-3d',
- style: 'transform-style: preserve-3d',
- animations: [
- {
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- expected: [
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformPreserve3D'
- }
- ]
- },
- {
- frames: {
- opacity: [0, 1]
- },
- expected: [
- {
- property: 'opacity',
- runningOnCompositor: true,
- }
- ]
- }
- ],
- },
- {
- desc: 'opacity and transform with backface-visibility:hidden',
- style: 'backface-visibility: hidden;',
- animations: [
- {
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- expected: [
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
- }
- ]
- },
- {
- frames: {
- opacity: [0, 1]
- },
- expected: [
- {
- property: 'opacity',
- runningOnCompositor: true,
- }
- ]
- }
- ],
- },
- ].forEach(subtest => {
- promise_test(function(t) {
- var div = addDiv(t, { class: 'compositable' });
- var animations = subtest.animations.map(function(anim) {
- var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
-
- // Bind expected values to animation object.
- animation.expected = anim.expected;
- return animation;
- });
- return waitForAllAnimations(animations).then(function() {
- animations.forEach(anim => {
- assert_all_properties_running_on_compositor(
- anim.effect.getProperties(),
- anim.expected);
- });
- div.style = subtest.style;
- return waitForFrame();
- }).then(function() {
- animations.forEach(anim => {
- assert_animation_property_state_equals(
- anim.effect.getProperties(),
- anim.expected);
- });
- div.style = '';
- return waitForFrame();
- }).then(function() {
- animations.forEach(anim => {
- assert_all_properties_running_on_compositor(
- anim.effect.getProperties(),
- anim.expected);
- });
- });
- }, 'Multiple animations: ' + subtest.desc);
- });
-}
-
-// Test adding/removing a 'width' keyframe on the same animation object, where
-// multiple animation objects belong to the same element.
-// The 'width' property is added to animations[1].
-function testMultipleAnimationsWithGeometricKeyframes() {
- [
- {
- desc: 'transform and opacity with geometric keyframes',
- animations: [
- {
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- expected: {
- withoutGeometric: [
- {
- property: 'transform',
- runningOnCompositor: true
- }
- ],
- withGeometric: [
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
- }
- ]
- }
- },
- {
- frames: {
- opacity: [0, 1]
- },
- expected: {
- withoutGeometric: [
- {
- property: 'opacity',
- runningOnCompositor: true,
- }
- ],
- withGeometric: [
- {
- property: 'width',
- runningOnCompositor: false,
- },
- {
- property: 'opacity',
- runningOnCompositor: true,
- }
- ]
- }
- }
- ],
- },
- {
- desc: 'opacity and transform with geometric keyframes',
- animations: [
- {
- frames: {
- opacity: [0, 1]
- },
- expected: {
- withoutGeometric: [
- {
- property: 'opacity',
- runningOnCompositor: true,
- }
- ],
- withGeometric: [
- {
- property: 'opacity',
- runningOnCompositor: true,
- }
- ]
- }
- },
- {
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- expected: {
- withoutGeometric: [
- {
- property: 'transform',
- runningOnCompositor: true
- }
- ],
- withGeometric: [
- {
- property: 'width',
- runningOnCompositor: false,
- },
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
- }
- ]
- }
- }
- ]
- },
- ].forEach(subtest => {
- promise_test(function(t) {
- var div = addDiv(t, { class: 'compositable' });
- var animations = subtest.animations.map(function(anim) {
- var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
-
- // Bind expected values to animation object.
- animation.expected = anim.expected;
- return animation;
- });
- return waitForAllAnimations(animations).then(function() {
- // First, all animations are running on compositor.
- animations.forEach(anim => {
- assert_animation_property_state_equals(
- anim.effect.getProperties(),
- anim.expected.withoutGeometric);
- });
- }).then(function() {
- // Add a 'width' property to animations[1].
- var keyframes = animations[1].effect.getKeyframes();
-
- keyframes[0].width = '100px';
- keyframes[1].width = '200px';
-
- animations[1].effect.setKeyframes(keyframes);
- return waitForFrame();
- }).then(function() {
- // Now the transform animation is not running on compositor because of
- // the 'width' property.
- animations.forEach(anim => {
- assert_animation_property_state_equals(
- anim.effect.getProperties(),
- anim.expected.withGeometric);
- });
- }).then(function() {
- // Remove the 'width' property from animations[1].
- var keyframes = animations[1].effect.getKeyframes();
-
- delete keyframes[0].width;
- delete keyframes[1].width;
-
- animations[1].effect.setKeyframes(keyframes);
- return waitForFrame();
- }).then(function() {
- // Finally, all animations are running on compositor.
- animations.forEach(anim => {
- assert_animation_property_state_equals(
- anim.effect.getProperties(),
- anim.expected.withoutGeometric);
- });
- });
- }, 'Multiple animations with geometric property: ' + subtest.desc);
- });
-}
-
-// Tests adding/removing 'width' animation on the same element which has async
-// animations.
-function testMultipleAnimationsWithGeometricAnimations() {
- [
- {
- desc: 'transform',
- animations: [
- {
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- expected: [
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
- }
- ]
- },
- ]
- },
- {
- desc: 'opacity',
- animations: [
- {
- frames: {
- opacity: [0, 1]
- },
- expected: [
- {
- property: 'opacity',
- runningOnCompositor: true
- }
- ]
- },
- ]
- },
- {
- desc: 'opacity and transform',
- animations: [
- {
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- expected: [
- {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
- }
- ]
- },
- {
- frames: {
- opacity: [0, 1]
- },
- expected: [
- {
- property: 'opacity',
- runningOnCompositor: true,
- }
- ]
- }
- ],
- },
- ].forEach(subtest => {
- promise_test(function(t) {
- var div = addDiv(t, { class: 'compositable' });
- var animations = subtest.animations.map(function(anim) {
- var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
-
- // Bind expected values to animation object.
- animation.expected = anim.expected;
- return animation;
- });
-
- var widthAnimation;
-
- return waitForAllAnimations(animations).then(function() {
- animations.forEach(anim => {
- assert_all_properties_running_on_compositor(
- anim.effect.getProperties(),
- anim.expected);
- });
- }).then(function() {
- // Append 'width' animation on the same element.
- widthAnimation = div.animate({ width: ['100px', '200px'] },
- 100 * MS_PER_SEC);
- return waitForFrame();
- }).then(function() {
- // Now transform animations are not running on compositor because of
- // the 'width' animation.
- animations.forEach(anim => {
- assert_animation_property_state_equals(
- anim.effect.getProperties(),
- anim.expected);
- });
- // Remove the 'width' animation.
- widthAnimation.cancel();
- return waitForFrame();
- }).then(function() {
- // Now all animations are running on compositor.
- animations.forEach(anim => {
- assert_all_properties_running_on_compositor(
- anim.effect.getProperties(),
- anim.expected);
- });
- });
- }, 'Multiple async animations and geometric animation: ' + subtest.desc);
- });
-}
-
-function testSmallElements() {
- [
- {
- desc: 'opacity on small element',
- frames: {
- opacity: [0, 1]
- },
- style: { style: 'width: 8px; height: 8px; background-color: red;' +
- // We need to set transform here to try creating an
- // individual frame for this opacity element.
- // Without this, this small element is created on the same
- // nsIFrame of mochitest iframe, i.e. the document which are
- // running this test, as a result the layer corresponding
- // to the frame is sent to compositor.
- 'transform: translateX(100px);' },
- expected: [
- {
- property: 'opacity',
- runningOnCompositor: true
- }
- ]
- },
- {
- desc: 'transform on small element',
- frames: {
- transform: ['translate(0px)', 'translate(100px)']
- },
- style: { style: 'width: 8px; height: 8px; background-color: red;' },
- expected: [
- {
- property: 'transform',
- runningOnCompositor: true
- }
- ]
- },
- ].forEach(subtest => {
- promise_test(function(t) {
- var div = addDiv(t, subtest.style);
- var animation = div.animate(subtest.frames, 100 * MS_PER_SEC);
- return animation.ready.then(function() {
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- subtest.expected);
- });
- }, subtest.desc);
- });
-}
-
-function testSynchronizedAnimations() {
- promise_test(function(t) {
- const elemA = addDiv(t, { class: 'compositable' });
- const elemB = addDiv(t, { class: 'compositable' });
-
- const animA = elemA.animate({ transform: [ 'translate(0px)',
- 'translate(100px)' ] },
- 100 * MS_PER_SEC);
- const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
- 100 * MS_PER_SEC);
-
- return Promise.all([animA.ready, animB.ready])
- .then(() => {
- assert_animation_property_state_equals(
- animA.effect.getProperties(),
- [ { property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
- } ]);
- });
- }, 'Animations created within the same tick are synchronized'
- + ' (compositor animation created first)');
-
- promise_test(function(t) {
- const elemA = addDiv(t, { class: 'compositable' });
- const elemB = addDiv(t, { class: 'compositable' });
-
- const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] },
- 100 * MS_PER_SEC);
- const animB = elemB.animate({ transform: [ 'translate(0px)',
- 'translate(100px)' ] },
- 100 * MS_PER_SEC);
-
- return Promise.all([animA.ready, animB.ready])
- .then(() => {
- assert_animation_property_state_equals(
- animB.effect.getProperties(),
- [ { property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
- } ]);
- });
- }, 'Animations created within the same tick are synchronized'
- + ' (compositor animation created second)');
-
- promise_test(function(t) {
- const attrs = { class: 'compositable',
- style: 'transition: all 100s' };
- const elemA = addDiv(t, attrs);
- const elemB = addDiv(t, attrs);
- elemA.style.transform = 'translate(0px)';
- elemB.style.marginLeft = '0px';
- getComputedStyle(elemA).transform;
- getComputedStyle(elemB).marginLeft;
-
- // Generally the sequence of steps is as follows:
- //
- // Tick -> requestAnimationFrame -> Style -> Paint -> Events (-> Tick...)
- //
- // In this test we want to set up two transitions during the "Events"
- // stage but only flush style for one such that the second one is actually
- // generated during the "Style" stage of the *next* tick.
- //
- // Web content often generates transitions in this way (that is, it doesn't
- // pay regard to when style is flushed and nor should it). However, we
- // still want transitions generated in this way to be synchronized.
- let timeForFirstFrame;
- return waitForIdleCallback()
- .then(() => {
- timeForFirstFrame = document.timeline.currentTime;
- elemA.style.transform = 'translate(100px)';
- // Flush style to trigger first transition
- getComputedStyle(elemA).transform;
- elemB.style.marginLeft = '100px';
- // DON'T flush style here (this includes calling getAnimations!)
- return waitForFrame();
- }).then(() => {
- assert_not_equals(timeForFirstFrame, document.timeline.currentTime,
- 'Should be on the other side of a tick');
- // Wait another tick so we can let the transition be started
- // by regular style resolution.
- return waitForFrame();
- }).then(() => {
- const transitionA = elemA.getAnimations()[0];
- assert_animation_property_state_equals(
- transitionA.effect.getProperties(),
- [ { property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
- } ]);
- });
- }, 'Transitions created before and after a tick are synchronized');
-
- promise_test(function(t) {
- const elemA = addDiv(t, { class: 'compositable' });
- const elemB = addDiv(t, { class: 'compositable' });
-
- const animA = elemA.animate({ transform: [ 'translate(0px)',
- 'translate(100px)' ],
- opacity: [ 0, 1 ] },
- 100 * MS_PER_SEC);
- const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
- 100 * MS_PER_SEC);
-
- return Promise.all([animA.ready, animB.ready])
- .then(() => {
- assert_animation_property_state_equals(
- animA.effect.getProperties(),
- [ { property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
- },
- { property: 'opacity',
- runningOnCompositor: true
- } ]);
- });
- }, 'Opacity animations on the same element continue running on the'
- + ' compositor when transform animations are synchronized with geometric'
- + ' animations');
-
- promise_test(function(t) {
- const elemA = addDiv(t, { class: 'compositable' });
- const elemB = addDiv(t, { class: 'compositable' });
-
- const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] },
- 100 * MS_PER_SEC);
- let animB;
-
- return waitForFrame()
- .then(() => {
- animB = elemB.animate({ transform: [ 'translate(0px)',
- 'translate(100px)' ] },
- 100 * MS_PER_SEC);
- return animB.ready;
- }).then(() => {
- assert_animation_property_state_equals(
- animB.effect.getProperties(),
- [ { property: 'transform',
- runningOnCompositor: true } ]);
- });
- }, 'Transform animations are NOT synchronized with geometric animations'
- + ' started in the previous frame');
-
- promise_test(function(t) {
- const elemA = addDiv(t, { class: 'compositable' });
- const elemB = addDiv(t, { class: 'compositable' });
-
- const animA = elemA.animate({ transform: [ 'translate(0px)',
- 'translate(100px)' ] },
- 100 * MS_PER_SEC);
- let animB;
-
- return waitForFrame()
- .then(() => {
- animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
- 100 * MS_PER_SEC);
- return animB.ready;
- }).then(() => {
- assert_animation_property_state_equals(
- animA.effect.getProperties(),
- [ { property: 'transform',
- runningOnCompositor: true } ]);
- });
- }, 'Transform animations are NOT synchronized with geometric animations'
- + ' started in the next frame');
-
- promise_test(function(t) {
- const elemA = addDiv(t, { class: 'compositable' });
- const elemB = addDiv(t, { class: 'compositable' });
-
- const animA = elemA.animate({ transform: [ 'translate(0px)',
- 'translate(100px)' ] },
- 100 * MS_PER_SEC);
- const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
- 100 * MS_PER_SEC);
- animB.pause();
-
- return Promise.all([animA.ready, animB.ready])
- .then(() => {
- assert_animation_property_state_equals(
- animA.effect.getProperties(),
- [ { property: 'transform', runningOnCompositor: true } ]);
- });
- }, 'Paused animations are not synchronized');
-
- promise_test(function(t) {
- const elemA = addDiv(t, { class: 'compositable' });
- const elemB = addDiv(t, { class: 'compositable' });
-
- const animA = elemA.animate({ transform: [ 'translate(0px)',
- 'translate(100px)' ] },
- 100 * MS_PER_SEC);
- const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
- 100 * MS_PER_SEC);
-
- // Seek one of the animations so that their start times will differ
- animA.currentTime = 5000;
-
- return Promise.all([animA.ready, animB.ready])
- .then(() => {
- assert_not_equals(animA.startTime, animB.startTime,
- 'Animations should have different start times');
- assert_animation_property_state_equals(
- animA.effect.getProperties(),
- [ { property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
- } ]);
- });
- }, 'Animations are synchronized based on when they are started'
- + ' and NOT their start time');
-
- promise_test(function(t) {
- const elemA = addDiv(t, { class: 'compositable' });
- const elemB = addDiv(t, { class: 'compositable' });
-
- const animA = elemA.animate({ transform: [ 'translate(0px)',
- 'translate(100px)' ] },
- 100 * MS_PER_SEC);
- const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
- 100 * MS_PER_SEC);
-
- return Promise.all([animA.ready, animB.ready])
- .then(() => {
- assert_animation_property_state_equals(
- animA.effect.getProperties(),
- [ { property: 'transform',
- runningOnCompositor: false } ]);
- // Restart animation
- animA.pause();
- animA.play();
- return animA.ready;
- }).then(() => {
- assert_animation_property_state_equals(
- animA.effect.getProperties(),
- [ { property: 'transform',
- runningOnCompositor: true } ]);
- });
- }, 'An initially synchronized animation may be unsynchronized if restarted');
-
- promise_test(function(t) {
- const elemA = addDiv(t, { class: 'compositable' });
- const elemB = addDiv(t, { class: 'compositable' });
-
- const animA = elemA.animate({ transform: [ 'translate(0px)',
- 'translate(100px)' ] },
- 100 * MS_PER_SEC);
- const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
- 100 * MS_PER_SEC);
-
- // Clear target effect
- animB.effect.target = null;
-
- return Promise.all([animA.ready, animB.ready])
- .then(() => {
- assert_animation_property_state_equals(
- animA.effect.getProperties(),
- [ { property: 'transform',
- runningOnCompositor: true } ]);
- });
- }, 'A geometric animation with no target element is not synchronized');
-}
-
-function start() {
- var bundleService = SpecialPowers.Cc['@mozilla.org/intl/stringbundle;1']
- .getService(SpecialPowers.Ci.nsIStringBundleService);
- gStringBundle = bundleService
- .createBundle("chrome://global/locale/layout_errors.properties");
-
- testBasicOperation();
- testKeyframesWithGeometricProperties();
- testSetOfGeometricProperties();
- testStyleChanges();
- testIdChanges();
- testMultipleAnimations();
- testMultipleAnimationsWithGeometricKeyframes();
- testMultipleAnimationsWithGeometricAnimations();
- testSmallElements();
- testSynchronizedAnimations();
-
- promise_test(function(t) {
- var animation = addDivAndAnimate(t,
- { class: 'compositable' },
- { transform: [ 'translate(0px)',
- 'translate(100px)'] },
- 100 * MS_PER_SEC);
- return animation.ready.then(function() {
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- [ { property: 'transform', runningOnCompositor: true } ]);
- animation.effect.target.style = 'width: 10000px; height: 10000px';
- return waitForFrame();
- }).then(function() {
- // viewport depends on test environment.
- var expectedWarning = new RegExp(
- "Animation cannot be run on the compositor because the frame size " +
- "\\(10000, 10000\\) is too large relative to the viewport " +
- "\\(larger than \\(\\d+, \\d+\\)\\) or larger than the " +
- "maximum allowed value \\(\\d+, \\d+\\)");
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- [ {
- property: 'transform',
- runningOnCompositor: false,
- warning: expectedWarning
- } ]);
- animation.effect.target.style = 'width: 100px; height: 100px';
- return waitForFrame();
- }).then(function() {
- // FIXME: Bug 1253164: the animation should get back on compositor.
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- [ { property: 'transform', runningOnCompositor: false } ]);
- });
- }, 'transform on too big element');
-
- promise_test(function(t) {
- var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- svg.setAttribute('width', '100');
- svg.setAttribute('height', '100');
- var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
- rect.setAttribute('width', '100');
- rect.setAttribute('height', '100');
- rect.setAttribute('fill', 'red');
- svg.appendChild(rect);
- document.body.appendChild(svg);
- t.add_cleanup(function() {
- svg.remove();
- });
-
- var animation = svg.animate(
- { transform: ['translate(0px)', 'translate(100px)'] }, 100 * MS_PER_SEC);
- return animation.ready.then(function() {
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- [ { property: 'transform', runningOnCompositor: true } ]);
- svg.setAttribute('transform', 'translate(10, 20)');
- return waitForFrame();
- }).then(function() {
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- [ {
- property: 'transform',
- runningOnCompositor: false,
- warning: 'CompositorAnimationWarningTransformSVG'
- } ]);
- svg.removeAttribute('transform');
- return waitForFrame();
- }).then(function() {
- assert_animation_property_state_equals(
- animation.effect.getProperties(),
- [ { property: 'transform', runningOnCompositor: true } ]);
- });
- }, 'transform of nsIFrame with SVG transform');
-
- promise_test(function(t) {
- var div = addDiv(t, { class: 'compositable',
- style: 'animation: fade 100s' });
- var cssAnimation = div.getAnimations()[0];
- var scriptAnimation = div.animate({ opacity: [ 1, 0 ] }, 100 * MS_PER_SEC);
- return scriptAnimation.ready.then(function() {
- assert_animation_property_state_equals(
- cssAnimation.effect.getProperties(),
- [ { property: 'opacity', runningOnCompositor: true } ]);
- assert_animation_property_state_equals(
- scriptAnimation.effect.getProperties(),
- [ { property: 'opacity', runningOnCompositor: true } ]);
- });
- }, 'overridden animation');
-
- done();
-}
-
-start();
-
-</script>
-
-</body>
--- a/dom/animation/test/chrome/test_animation_performance_warning.html
+++ b/dom/animation/test/chrome/test_animation_performance_warning.html
@@ -1,21 +1,1276 @@
<!doctype html>
+<head>
<meta charset=utf-8>
+<title>Bug 1196114 - Test metadata related to which animation properties
+ are running on the compositor</title>
<script type="application/javascript" src="../testharness.js"></script>
<script type="application/javascript" src="../testharnessreport.js"></script>
+<script type="application/javascript" src="../testcommon.js"></script>
+<style>
+.compositable {
+ /* Element needs geometry to be eligible for layerization */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+}
+@keyframes fade {
+ from { opacity: 1 }
+ to { opacity: 0 }
+}
+</style>
+</head>
+<body>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1196114"
+ target="_blank">Mozilla Bug 1196114</a>
<div id="log"></div>
<script>
'use strict';
-setup({explicit_done: true});
-SpecialPowers.pushPrefEnv(
- { "set": [["dom.requestIdleCallback.enabled", true],
- ["general.useragent.locale", "en-US"],
- // Need to set devPixelsPerPx explicitly to gain
- // consistent pixel values in warning messages
- // regardless of platform DPIs.
- ["layout.css.devPixelsPerPx", 1]]},
- function() {
- // Open a new window after enabling requestIdleCallback so that we can
- // use the function in the new window.
- window.open("file_animation_performance_warning.html");
+
+// This is used for obtaining localized strings.
+var gStringBundle;
+
+W3CTest.runner.requestLongerTimeout(2);
+
+SpecialPowers.pushPrefEnv({ "set": [
+ ["general.useragent.locale", "en-US"],
+ // Need to set devPixelsPerPx explicitly to gain
+ // consistent pixel values in warning messages
+ // regardless of platform DPIs.
+ ["layout.css.devPixelsPerPx", 1],
+ ] },
+ start);
+
+/**
+ * Promise wrapper for requestIdleCallback.
+ * NOTE: DON'T move this function into testcommon.js until requestIdleCallback
+ * is enabled by default on all channels.
+ */
+function waitForIdleCallback() {
+ return new Promise(function(resolve, reject) {
+ window.requestIdleCallback(resolve);
+ });
+}
+
+function compare_property_state(a, b) {
+ if (a.property > b.property) {
+ return -1;
+ } else if (a.property < b.property) {
+ return 1;
+ }
+ if (a.runningOnCompositor != b.runningOnCompositor) {
+ return a.runningOnCompositor ? 1 : -1;
+ }
+ return a.warning > b.warning ? -1 : 1;
+}
+
+function assert_animation_property_state_equals(actual, expected) {
+ assert_equals(actual.length, expected.length, 'Number of properties');
+
+ var sortedActual = actual.sort(compare_property_state);
+ var sortedExpected = expected.sort(compare_property_state);
+
+ for (var i = 0; i < sortedActual.length; i++) {
+ assert_equals(sortedActual[i].property,
+ sortedExpected[i].property,
+ 'CSS property name should match');
+ assert_equals(sortedActual[i].runningOnCompositor,
+ sortedExpected[i].runningOnCompositor,
+ 'runningOnCompositor property should match');
+ if (sortedExpected[i].warning instanceof RegExp) {
+ assert_regexp_match(sortedActual[i].warning,
+ sortedExpected[i].warning,
+ 'warning message should match');
+ } else if (sortedExpected[i].warning) {
+ assert_equals(sortedActual[i].warning,
+ gStringBundle.GetStringFromName(sortedExpected[i].warning),
+ 'warning message should match');
+ }
+ }
+}
+
+// Check that the animation is running on compositor and
+// warning property is not set for the CSS property regardless
+// expected values.
+function assert_all_properties_running_on_compositor(actual, expected) {
+ assert_equals(actual.length, expected.length);
+
+ var sortedActual = actual.sort(compare_property_state);
+ var sortedExpected = expected.sort(compare_property_state);
+
+ for (var i = 0; i < sortedActual.length; i++) {
+ assert_equals(sortedActual[i].property,
+ sortedExpected[i].property,
+ 'CSS property name should match');
+ assert_true(sortedActual[i].runningOnCompositor,
+ 'runningOnCompositor property should be true on ' +
+ sortedActual[i].property);
+ assert_not_exists(sortedActual[i], 'warning',
+ 'warning property should not be set');
+ }
+}
+
+function testBasicOperation() {
+ [
+ {
+ desc: 'animations on compositor',
+ frames: {
+ opacity: [0, 1]
+ },
+ expected: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true
+ }
+ ]
+ },
+ {
+ desc: 'animations on main thread',
+ frames: {
+ backgroundColor: ['white', 'red']
+ },
+ expected: [
+ {
+ property: 'background-color',
+ runningOnCompositor: false
+ }
+ ]
+ },
+ {
+ desc: 'animations on both threads',
+ frames: {
+ backgroundColor: ['white', 'red'],
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: [
+ {
+ property: 'background-color',
+ runningOnCompositor: false
+ },
+ {
+ property: 'transform',
+ runningOnCompositor: true
+ }
+ ]
+ },
+ {
+ desc: 'two animation properties on compositor thread',
+ frames: {
+ opacity: [0, 1],
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true
+ },
+ {
+ property: 'transform',
+ runningOnCompositor: true
+ }
+ ]
+ },
+ {
+ desc: 'opacity on compositor with animation of geometric properties',
+ frames: {
+ width: ['100px', '200px'],
+ opacity: [0, 1]
+ },
+ expected: [
+ {
+ property: 'width',
+ runningOnCompositor: false
+ },
+ {
+ property: 'opacity',
+ runningOnCompositor: true
+ }
+ ]
+ },
+ ].forEach(subtest => {
+ promise_test(function(t) {
+ var animation = addDivAndAnimate(t, { class: 'compositable' },
+ subtest.frames, 100 * MS_PER_SEC);
+ return animation.ready.then(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ subtest.expected);
+ });
+ }, subtest.desc);
+ });
+}
+
+// Test adding/removing a 'width' property on the same animation object.
+function testKeyframesWithGeometricProperties() {
+ [
+ {
+ desc: 'transform',
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: {
+ withoutGeometric: [
+ {
+ property: 'transform',
+ runningOnCompositor: true
+ }
+ ],
+ withGeometric: [
+ {
+ property: 'width',
+ runningOnCompositor: false
+ },
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+ }
+ ]
+ }
+ },
+ {
+ desc: 'opacity and transform',
+ frames: {
+ opacity: [0, 1],
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: {
+ withoutGeometric: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true
+ },
+ {
+ property: 'transform',
+ runningOnCompositor: true
+ }
+ ],
+ withGeometric: [
+ {
+ property: 'width',
+ runningOnCompositor: false
+ },
+ {
+ property: 'opacity',
+ runningOnCompositor: true
+ },
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+ }
+ ]
+ }
+ },
+ ].forEach(subtest => {
+ promise_test(function(t) {
+ var animation = addDivAndAnimate(t, { class: 'compositable' },
+ subtest.frames, 100 * MS_PER_SEC);
+ return animation.ready.then(function() {
+ // First, a transform animation is running on compositor.
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ subtest.expected.withoutGeometric);
+ }).then(function() {
+ // Add a 'width' property.
+ var keyframes = animation.effect.getKeyframes();
+
+ keyframes[0].width = '100px';
+ keyframes[1].width = '200px';
+
+ animation.effect.setKeyframes(keyframes);
+ return waitForFrame();
+ }).then(function() {
+ // Now the transform animation is not running on compositor because of
+ // the 'width' property.
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ subtest.expected.withGeometric);
+ }).then(function() {
+ // Remove the 'width' property.
+ var keyframes = animation.effect.getKeyframes();
+
+ delete keyframes[0].width;
+ delete keyframes[1].width;
+
+ animation.effect.setKeyframes(keyframes);
+ return waitForFrame();
+ }).then(function() {
+ // Finally, the transform animation is running on compositor.
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ subtest.expected.withoutGeometric);
+ });
+ }, 'An animation has: ' + subtest.desc);
+ });
+}
+
+// Test that the expected set of geometric properties all block transform
+// animations.
+function testSetOfGeometricProperties() {
+ const geometricProperties = [
+ 'width', 'height',
+ 'top', 'right', 'bottom', 'left',
+ 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
+ 'padding-top', 'padding-right', 'padding-bottom', 'padding-left'
+ ];
+
+ geometricProperties.forEach(property => {
+ promise_test(function(t) {
+ const keyframes = {
+ [propertyToIDL(property)]: [ '100px', '200px' ],
+ transform: [ 'translate(0px)', 'translate(100px)' ]
+ };
+ var animation = addDivAndAnimate(t, { class: 'compositable' },
+ keyframes, 100 * MS_PER_SEC);
+
+ return animation.ready.then(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ [
+ {
+ property,
+ runningOnCompositor: false
+ },
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+ }
+ ]);
+ }, 'Transform animation should be run on the main thread');
+ }, `${property} is treated as a geometric property`);
+ });
+}
+
+// Performance warning tests that set and clear a style property.
+function testStyleChanges() {
+ [
+ {
+ desc: 'preserve-3d transform',
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ style: 'transform-style: preserve-3d',
+ expected: [
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformPreserve3D'
+ }
+ ]
+ },
+ {
+ desc: 'transform with backface-visibility:hidden',
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ style: 'backface-visibility: hidden;',
+ expected: [
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
+ }
+ ]
+ },
+ {
+ desc: 'opacity and transform with preserve-3d',
+ frames: {
+ opacity: [0, 1],
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ style: 'transform-style: preserve-3d',
+ expected: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true
+ },
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformPreserve3D'
+ }
+ ]
+ },
+ {
+ desc: 'opacity and transform with backface-visibility:hidden',
+ frames: {
+ opacity: [0, 1],
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ style: 'backface-visibility: hidden;',
+ expected: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true
+ },
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
+ }
+ ]
+ },
+ ].forEach(subtest => {
+ promise_test(function(t) {
+ var animation = addDivAndAnimate(t, { class: 'compositable' },
+ subtest.frames, 100 * MS_PER_SEC);
+ return animation.ready.then(function() {
+ assert_all_properties_running_on_compositor(
+ animation.effect.getProperties(),
+ subtest.expected);
+ animation.effect.target.style = subtest.style;
+ return waitForFrame();
+ }).then(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ subtest.expected);
+ animation.effect.target.style = '';
+ return waitForFrame();
+ }).then(function() {
+ assert_all_properties_running_on_compositor(
+ animation.effect.getProperties(),
+ subtest.expected);
+ });
+ }, subtest.desc);
+ });
+}
+
+// Performance warning tests that set and clear the id property
+function testIdChanges() {
+ [
+ {
+ desc: 'moz-element referencing a transform',
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ id: 'transformed',
+ createelement: 'width:100px; height:100px; background: -moz-element(#transformed)',
+ expected: [
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningHasRenderingObserver'
+ }
+ ]
+ },
+ ].forEach(subtest => {
+ promise_test(function(t) {
+ if (subtest.createelement) {
+ addDiv(t, { style: subtest.createelement });
+ }
+
+ var animation = addDivAndAnimate(t, { class: 'compositable' },
+ subtest.frames, 100 * MS_PER_SEC);
+ return animation.ready.then(function() {
+ assert_all_properties_running_on_compositor(
+ animation.effect.getProperties(),
+ subtest.expected);
+ animation.effect.target.id = subtest.id;
+ return waitForFrame();
+ }).then(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ subtest.expected);
+ animation.effect.target.id = '';
+ return waitForFrame();
+ }).then(function() {
+ assert_all_properties_running_on_compositor(
+ animation.effect.getProperties(),
+ subtest.expected);
+ });
+ }, subtest.desc);
+ });
+}
+
+function testMultipleAnimations() {
+ [
+ {
+ desc: 'opacity and transform with preserve-3d',
+ style: 'transform-style: preserve-3d',
+ animations: [
+ {
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: [
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformPreserve3D'
+ }
+ ]
+ },
+ {
+ frames: {
+ opacity: [0, 1]
+ },
+ expected: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true,
+ }
+ ]
+ }
+ ],
+ },
+ {
+ desc: 'opacity and transform with backface-visibility:hidden',
+ style: 'backface-visibility: hidden;',
+ animations: [
+ {
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: [
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
+ }
+ ]
+ },
+ {
+ frames: {
+ opacity: [0, 1]
+ },
+ expected: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true,
+ }
+ ]
+ }
+ ],
+ },
+ ].forEach(subtest => {
+ promise_test(function(t) {
+ var div = addDiv(t, { class: 'compositable' });
+ var animations = subtest.animations.map(function(anim) {
+ var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
+
+ // Bind expected values to animation object.
+ animation.expected = anim.expected;
+ return animation;
+ });
+ return waitForAllAnimations(animations).then(function() {
+ animations.forEach(anim => {
+ assert_all_properties_running_on_compositor(
+ anim.effect.getProperties(),
+ anim.expected);
+ });
+ div.style = subtest.style;
+ return waitForFrame();
+ }).then(function() {
+ animations.forEach(anim => {
+ assert_animation_property_state_equals(
+ anim.effect.getProperties(),
+ anim.expected);
+ });
+ div.style = '';
+ return waitForFrame();
+ }).then(function() {
+ animations.forEach(anim => {
+ assert_all_properties_running_on_compositor(
+ anim.effect.getProperties(),
+ anim.expected);
+ });
+ });
+ }, 'Multiple animations: ' + subtest.desc);
});
+}
+
+// Test adding/removing a 'width' keyframe on the same animation object, where
+// multiple animation objects belong to the same element.
+// The 'width' property is added to animations[1].
+function testMultipleAnimationsWithGeometricKeyframes() {
+ [
+ {
+ desc: 'transform and opacity with geometric keyframes',
+ animations: [
+ {
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: {
+ withoutGeometric: [
+ {
+ property: 'transform',
+ runningOnCompositor: true
+ }
+ ],
+ withGeometric: [
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+ }
+ ]
+ }
+ },
+ {
+ frames: {
+ opacity: [0, 1]
+ },
+ expected: {
+ withoutGeometric: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true,
+ }
+ ],
+ withGeometric: [
+ {
+ property: 'width',
+ runningOnCompositor: false,
+ },
+ {
+ property: 'opacity',
+ runningOnCompositor: true,
+ }
+ ]
+ }
+ }
+ ],
+ },
+ {
+ desc: 'opacity and transform with geometric keyframes',
+ animations: [
+ {
+ frames: {
+ opacity: [0, 1]
+ },
+ expected: {
+ withoutGeometric: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true,
+ }
+ ],
+ withGeometric: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true,
+ }
+ ]
+ }
+ },
+ {
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: {
+ withoutGeometric: [
+ {
+ property: 'transform',
+ runningOnCompositor: true
+ }
+ ],
+ withGeometric: [
+ {
+ property: 'width',
+ runningOnCompositor: false,
+ },
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+ }
+ ]
+ }
+ }
+ ]
+ },
+ ].forEach(subtest => {
+ promise_test(function(t) {
+ var div = addDiv(t, { class: 'compositable' });
+ var animations = subtest.animations.map(function(anim) {
+ var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
+
+ // Bind expected values to animation object.
+ animation.expected = anim.expected;
+ return animation;
+ });
+ return waitForAllAnimations(animations).then(function() {
+ // First, all animations are running on compositor.
+ animations.forEach(anim => {
+ assert_animation_property_state_equals(
+ anim.effect.getProperties(),
+ anim.expected.withoutGeometric);
+ });
+ }).then(function() {
+ // Add a 'width' property to animations[1].
+ var keyframes = animations[1].effect.getKeyframes();
+
+ keyframes[0].width = '100px';
+ keyframes[1].width = '200px';
+
+ animations[1].effect.setKeyframes(keyframes);
+ return waitForFrame();
+ }).then(function() {
+ // Now the transform animation is not running on compositor because of
+ // the 'width' property.
+ animations.forEach(anim => {
+ assert_animation_property_state_equals(
+ anim.effect.getProperties(),
+ anim.expected.withGeometric);
+ });
+ }).then(function() {
+ // Remove the 'width' property from animations[1].
+ var keyframes = animations[1].effect.getKeyframes();
+
+ delete keyframes[0].width;
+ delete keyframes[1].width;
+
+ animations[1].effect.setKeyframes(keyframes);
+ return waitForFrame();
+ }).then(function() {
+ // Finally, all animations are running on compositor.
+ animations.forEach(anim => {
+ assert_animation_property_state_equals(
+ anim.effect.getProperties(),
+ anim.expected.withoutGeometric);
+ });
+ });
+ }, 'Multiple animations with geometric property: ' + subtest.desc);
+ });
+}
+
+// Tests adding/removing 'width' animation on the same element which has async
+// animations.
+function testMultipleAnimationsWithGeometricAnimations() {
+ [
+ {
+ desc: 'transform',
+ animations: [
+ {
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: [
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+ }
+ ]
+ },
+ ]
+ },
+ {
+ desc: 'opacity',
+ animations: [
+ {
+ frames: {
+ opacity: [0, 1]
+ },
+ expected: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true
+ }
+ ]
+ },
+ ]
+ },
+ {
+ desc: 'opacity and transform',
+ animations: [
+ {
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ expected: [
+ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+ }
+ ]
+ },
+ {
+ frames: {
+ opacity: [0, 1]
+ },
+ expected: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true,
+ }
+ ]
+ }
+ ],
+ },
+ ].forEach(subtest => {
+ promise_test(function(t) {
+ var div = addDiv(t, { class: 'compositable' });
+ var animations = subtest.animations.map(function(anim) {
+ var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
+
+ // Bind expected values to animation object.
+ animation.expected = anim.expected;
+ return animation;
+ });
+
+ var widthAnimation;
+
+ return waitForAllAnimations(animations).then(function() {
+ animations.forEach(anim => {
+ assert_all_properties_running_on_compositor(
+ anim.effect.getProperties(),
+ anim.expected);
+ });
+ }).then(function() {
+ // Append 'width' animation on the same element.
+ widthAnimation = div.animate({ width: ['100px', '200px'] },
+ 100 * MS_PER_SEC);
+ return waitForFrame();
+ }).then(function() {
+ // Now transform animations are not running on compositor because of
+ // the 'width' animation.
+ animations.forEach(anim => {
+ assert_animation_property_state_equals(
+ anim.effect.getProperties(),
+ anim.expected);
+ });
+ // Remove the 'width' animation.
+ widthAnimation.cancel();
+ return waitForFrame();
+ }).then(function() {
+ // Now all animations are running on compositor.
+ animations.forEach(anim => {
+ assert_all_properties_running_on_compositor(
+ anim.effect.getProperties(),
+ anim.expected);
+ });
+ });
+ }, 'Multiple async animations and geometric animation: ' + subtest.desc);
+ });
+}
+
+function testSmallElements() {
+ [
+ {
+ desc: 'opacity on small element',
+ frames: {
+ opacity: [0, 1]
+ },
+ style: { style: 'width: 8px; height: 8px; background-color: red;' +
+ // We need to set transform here to try creating an
+ // individual frame for this opacity element.
+ // Without this, this small element is created on the same
+ // nsIFrame of mochitest iframe, i.e. the document which are
+ // running this test, as a result the layer corresponding
+ // to the frame is sent to compositor.
+ 'transform: translateX(100px);' },
+ expected: [
+ {
+ property: 'opacity',
+ runningOnCompositor: true
+ }
+ ]
+ },
+ {
+ desc: 'transform on small element',
+ frames: {
+ transform: ['translate(0px)', 'translate(100px)']
+ },
+ style: { style: 'width: 8px; height: 8px; background-color: red;' },
+ expected: [
+ {
+ property: 'transform',
+ runningOnCompositor: true
+ }
+ ]
+ },
+ ].forEach(subtest => {
+ promise_test(function(t) {
+ var div = addDiv(t, subtest.style);
+ var animation = div.animate(subtest.frames, 100 * MS_PER_SEC);
+ return animation.ready.then(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ subtest.expected);
+ });
+ }, subtest.desc);
+ });
+}
+
+function testSynchronizedAnimations() {
+ promise_test(function(t) {
+ const elemA = addDiv(t, { class: 'compositable' });
+ const elemB = addDiv(t, { class: 'compositable' });
+
+ const animA = elemA.animate({ transform: [ 'translate(0px)',
+ 'translate(100px)' ] },
+ 100 * MS_PER_SEC);
+ const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+
+ return Promise.all([animA.ready, animB.ready])
+ .then(() => {
+ assert_animation_property_state_equals(
+ animA.effect.getProperties(),
+ [ { property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+ } ]);
+ });
+ }, 'Animations created within the same tick are synchronized'
+ + ' (compositor animation created first)');
+
+ promise_test(function(t) {
+ const elemA = addDiv(t, { class: 'compositable' });
+ const elemB = addDiv(t, { class: 'compositable' });
+
+ const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+ const animB = elemB.animate({ transform: [ 'translate(0px)',
+ 'translate(100px)' ] },
+ 100 * MS_PER_SEC);
+
+ return Promise.all([animA.ready, animB.ready])
+ .then(() => {
+ assert_animation_property_state_equals(
+ animB.effect.getProperties(),
+ [ { property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+ } ]);
+ });
+ }, 'Animations created within the same tick are synchronized'
+ + ' (compositor animation created second)');
+
+ promise_test(function(t) {
+ const attrs = { class: 'compositable',
+ style: 'transition: all 100s' };
+ const elemA = addDiv(t, attrs);
+ const elemB = addDiv(t, attrs);
+ elemA.style.transform = 'translate(0px)';
+ elemB.style.marginLeft = '0px';
+ getComputedStyle(elemA).transform;
+ getComputedStyle(elemB).marginLeft;
+
+ // Generally the sequence of steps is as follows:
+ //
+ // Tick -> requestAnimationFrame -> Style -> Paint -> Events (-> Tick...)
+ //
+ // In this test we want to set up two transitions during the "Events"
+ // stage but only flush style for one such that the second one is actually
+ // generated during the "Style" stage of the *next* tick.
+ //
+ // Web content often generates transitions in this way (that is, it doesn't
+ // pay regard to when style is flushed and nor should it). However, we
+ // still want transitions generated in this way to be synchronized.
+ let timeForFirstFrame;
+ return waitForIdleCallback()
+ .then(() => {
+ timeForFirstFrame = document.timeline.currentTime;
+ elemA.style.transform = 'translate(100px)';
+ // Flush style to trigger first transition
+ getComputedStyle(elemA).transform;
+ elemB.style.marginLeft = '100px';
+ // DON'T flush style here (this includes calling getAnimations!)
+ return waitForFrame();
+ }).then(() => {
+ assert_not_equals(timeForFirstFrame, document.timeline.currentTime,
+ 'Should be on the other side of a tick');
+ // Wait another tick so we can let the transition be started
+ // by regular style resolution.
+ return waitForFrame();
+ }).then(() => {
+ const transitionA = elemA.getAnimations()[0];
+ assert_animation_property_state_equals(
+ transitionA.effect.getProperties(),
+ [ { property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+ } ]);
+ });
+ }, 'Transitions created before and after a tick are synchronized');
+
+ promise_test(function(t) {
+ const elemA = addDiv(t, { class: 'compositable' });
+ const elemB = addDiv(t, { class: 'compositable' });
+
+ const animA = elemA.animate({ transform: [ 'translate(0px)',
+ 'translate(100px)' ],
+ opacity: [ 0, 1 ] },
+ 100 * MS_PER_SEC);
+ const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+
+ return Promise.all([animA.ready, animB.ready])
+ .then(() => {
+ assert_animation_property_state_equals(
+ animA.effect.getProperties(),
+ [ { property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+ },
+ { property: 'opacity',
+ runningOnCompositor: true
+ } ]);
+ });
+ }, 'Opacity animations on the same element continue running on the'
+ + ' compositor when transform animations are synchronized with geometric'
+ + ' animations');
+
+ promise_test(function(t) {
+ const elemA = addDiv(t, { class: 'compositable' });
+ const elemB = addDiv(t, { class: 'compositable' });
+
+ const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+ let animB;
+
+ return waitForFrame()
+ .then(() => {
+ animB = elemB.animate({ transform: [ 'translate(0px)',
+ 'translate(100px)' ] },
+ 100 * MS_PER_SEC);
+ return animB.ready;
+ }).then(() => {
+ assert_animation_property_state_equals(
+ animB.effect.getProperties(),
+ [ { property: 'transform',
+ runningOnCompositor: true } ]);
+ });
+ }, 'Transform animations are NOT synchronized with geometric animations'
+ + ' started in the previous frame');
+
+ promise_test(function(t) {
+ const elemA = addDiv(t, { class: 'compositable' });
+ const elemB = addDiv(t, { class: 'compositable' });
+
+ const animA = elemA.animate({ transform: [ 'translate(0px)',
+ 'translate(100px)' ] },
+ 100 * MS_PER_SEC);
+ let animB;
+
+ return waitForFrame()
+ .then(() => {
+ animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+ return animB.ready;
+ }).then(() => {
+ assert_animation_property_state_equals(
+ animA.effect.getProperties(),
+ [ { property: 'transform',
+ runningOnCompositor: true } ]);
+ });
+ }, 'Transform animations are NOT synchronized with geometric animations'
+ + ' started in the next frame');
+
+ promise_test(function(t) {
+ const elemA = addDiv(t, { class: 'compositable' });
+ const elemB = addDiv(t, { class: 'compositable' });
+
+ const animA = elemA.animate({ transform: [ 'translate(0px)',
+ 'translate(100px)' ] },
+ 100 * MS_PER_SEC);
+ const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+ animB.pause();
+
+ return Promise.all([animA.ready, animB.ready])
+ .then(() => {
+ assert_animation_property_state_equals(
+ animA.effect.getProperties(),
+ [ { property: 'transform', runningOnCompositor: true } ]);
+ });
+ }, 'Paused animations are not synchronized');
+
+ promise_test(function(t) {
+ const elemA = addDiv(t, { class: 'compositable' });
+ const elemB = addDiv(t, { class: 'compositable' });
+
+ const animA = elemA.animate({ transform: [ 'translate(0px)',
+ 'translate(100px)' ] },
+ 100 * MS_PER_SEC);
+ const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+
+ // Seek one of the animations so that their start times will differ
+ animA.currentTime = 5000;
+
+ return Promise.all([animA.ready, animB.ready])
+ .then(() => {
+ assert_not_equals(animA.startTime, animB.startTime,
+ 'Animations should have different start times');
+ assert_animation_property_state_equals(
+ animA.effect.getProperties(),
+ [ { property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+ } ]);
+ });
+ }, 'Animations are synchronized based on when they are started'
+ + ' and NOT their start time');
+
+ promise_test(function(t) {
+ const elemA = addDiv(t, { class: 'compositable' });
+ const elemB = addDiv(t, { class: 'compositable' });
+
+ const animA = elemA.animate({ transform: [ 'translate(0px)',
+ 'translate(100px)' ] },
+ 100 * MS_PER_SEC);
+ const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+
+ return Promise.all([animA.ready, animB.ready])
+ .then(() => {
+ assert_animation_property_state_equals(
+ animA.effect.getProperties(),
+ [ { property: 'transform',
+ runningOnCompositor: false } ]);
+ // Restart animation
+ animA.pause();
+ animA.play();
+ return animA.ready;
+ }).then(() => {
+ assert_animation_property_state_equals(
+ animA.effect.getProperties(),
+ [ { property: 'transform',
+ runningOnCompositor: true } ]);
+ });
+ }, 'An initially synchronized animation may be unsynchronized if restarted');
+
+ promise_test(function(t) {
+ const elemA = addDiv(t, { class: 'compositable' });
+ const elemB = addDiv(t, { class: 'compositable' });
+
+ const animA = elemA.animate({ transform: [ 'translate(0px)',
+ 'translate(100px)' ] },
+ 100 * MS_PER_SEC);
+ const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+
+ // Clear target effect
+ animB.effect.target = null;
+
+ return Promise.all([animA.ready, animB.ready])
+ .then(() => {
+ assert_animation_property_state_equals(
+ animA.effect.getProperties(),
+ [ { property: 'transform',
+ runningOnCompositor: true } ]);
+ });
+ }, 'A geometric animation with no target element is not synchronized');
+}
+
+function start() {
+ var bundleService = SpecialPowers.Cc['@mozilla.org/intl/stringbundle;1']
+ .getService(SpecialPowers.Ci.nsIStringBundleService);
+ gStringBundle = bundleService
+ .createBundle("chrome://global/locale/layout_errors.properties");
+
+ testBasicOperation();
+ testKeyframesWithGeometricProperties();
+ testSetOfGeometricProperties();
+ testStyleChanges();
+ testIdChanges();
+ testMultipleAnimations();
+ testMultipleAnimationsWithGeometricKeyframes();
+ testMultipleAnimationsWithGeometricAnimations();
+ testSmallElements();
+ testSynchronizedAnimations();
+
+ promise_test(function(t) {
+ var animation = addDivAndAnimate(t,
+ { class: 'compositable' },
+ { transform: [ 'translate(0px)',
+ 'translate(100px)'] },
+ 100 * MS_PER_SEC);
+ return animation.ready.then(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ [ { property: 'transform', runningOnCompositor: true } ]);
+ animation.effect.target.style = 'width: 10000px; height: 10000px';
+ return waitForFrame();
+ }).then(function() {
+ // viewport depends on test environment.
+ var expectedWarning = new RegExp(
+ "Animation cannot be run on the compositor because the frame size " +
+ "\\(10000, 10000\\) is too large relative to the viewport " +
+ "\\(larger than \\(\\d+, \\d+\\)\\) or larger than the " +
+ "maximum allowed value \\(\\d+, \\d+\\)");
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ [ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: expectedWarning
+ } ]);
+ animation.effect.target.style = 'width: 100px; height: 100px';
+ return waitForFrame();
+ }).then(function() {
+ // FIXME: Bug 1253164: the animation should get back on compositor.
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ [ { property: 'transform', runningOnCompositor: false } ]);
+ });
+ }, 'transform on too big element');
+
+ promise_test(function(t) {
+ var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svg.setAttribute('width', '100');
+ svg.setAttribute('height', '100');
+ var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+ rect.setAttribute('width', '100');
+ rect.setAttribute('height', '100');
+ rect.setAttribute('fill', 'red');
+ svg.appendChild(rect);
+ document.body.appendChild(svg);
+ t.add_cleanup(function() {
+ svg.remove();
+ });
+
+ var animation = svg.animate(
+ { transform: ['translate(0px)', 'translate(100px)'] }, 100 * MS_PER_SEC);
+ return animation.ready.then(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ [ { property: 'transform', runningOnCompositor: true } ]);
+ svg.setAttribute('transform', 'translate(10, 20)');
+ return waitForFrame();
+ }).then(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ [ {
+ property: 'transform',
+ runningOnCompositor: false,
+ warning: 'CompositorAnimationWarningTransformSVG'
+ } ]);
+ svg.removeAttribute('transform');
+ return waitForFrame();
+ }).then(function() {
+ assert_animation_property_state_equals(
+ animation.effect.getProperties(),
+ [ { property: 'transform', runningOnCompositor: true } ]);
+ });
+ }, 'transform of nsIFrame with SVG transform');
+
+ promise_test(function(t) {
+ var div = addDiv(t, { class: 'compositable',
+ style: 'animation: fade 100s' });
+ var cssAnimation = div.getAnimations()[0];
+ var scriptAnimation = div.animate({ opacity: [ 1, 0 ] }, 100 * MS_PER_SEC);
+ return scriptAnimation.ready.then(function() {
+ assert_animation_property_state_equals(
+ cssAnimation.effect.getProperties(),
+ [ { property: 'opacity', runningOnCompositor: true } ]);
+ assert_animation_property_state_equals(
+ scriptAnimation.effect.getProperties(),
+ [ { property: 'opacity', runningOnCompositor: true } ]);
+ });
+ }, 'overridden animation');
+
+ done();
+}
+
+start();
+
</script>
+
+</body>
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -264,29 +264,25 @@ function flushComputedStyle(elem) {
var cs = getComputedStyle(elem);
cs.marginLeft;
}
if (opener) {
for (var funcName of ["async_test", "assert_not_equals", "assert_equals",
"assert_approx_equals", "assert_less_than",
"assert_less_than_equal", "assert_greater_than",
- "assert_greater_than_equal",
- "assert_not_exists",
"assert_between_inclusive",
"assert_true", "assert_false",
"assert_class_string", "assert_throws",
"assert_unreached", "assert_regexp_match",
"promise_test", "test"]) {
window[funcName] = opener[funcName].bind(opener);
}
window.EventWatcher = opener.EventWatcher;
- // Used for requestLongerTimeout.
- window.W3CTest = opener.W3CTest;
function done() {
opener.add_completion_callback(function() {
self.close();
});
opener.done();
}
}