summaryrefslogtreecommitdiffstats
path: root/dom/animation/test/chrome/test_restyles.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/animation/test/chrome/test_restyles.html')
-rw-r--r--dom/animation/test/chrome/test_restyles.html815
1 files changed, 815 insertions, 0 deletions
diff --git a/dom/animation/test/chrome/test_restyles.html b/dom/animation/test/chrome/test_restyles.html
new file mode 100644
index 000000000..e59967c19
--- /dev/null
+++ b/dom/animation/test/chrome/test_restyles.html
@@ -0,0 +1,815 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Tests restyles caused by animations</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+<script src="../testcommon.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+<style>
+@keyframes opacity {
+ from { opacity: 1; }
+ to { opacity: 0; }
+}
+@keyframes background-color {
+ from { background-color: red; }
+ to { background-color: blue; }
+}
+@keyframes rotate {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+div {
+ /* Element needs geometry to be eligible for layerization */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+}
+</style>
+</head>
+<body>
+<script>
+'use strict';
+
+function observeStyling(frameCount, onFrame) {
+ var docShell = window.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell);
+
+ docShell.recordProfileTimelineMarkers = true;
+ docShell.popProfileTimelineMarkers();
+
+ return new Promise(function(resolve) {
+ return waitForAnimationFrames(frameCount, onFrame).then(function() {
+ var markers = docShell.popProfileTimelineMarkers();
+ docShell.recordProfileTimelineMarkers = false;
+ var stylingMarkers = markers.filter(function(marker, index) {
+ return marker.name == 'Styles' &&
+ (marker.restyleHint == 'eRestyle_CSSAnimations' ||
+ marker.restyleHint == 'eRestyle_CSSTransitions');
+ });
+ resolve(stylingMarkers);
+ });
+ });
+}
+
+function ensureElementRemoval(aElement) {
+ return new Promise(function(resolve) {
+ aElement.remove();
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+var omtaEnabled = isOMTAEnabled();
+
+var isAndroid = !!navigator.userAgent.includes("Android");
+
+function add_task_if_omta_enabled(test) {
+ if (!omtaEnabled) {
+ info(test.name + " is skipped because OMTA is disabled");
+ return;
+ }
+ add_task(test);
+}
+
+// We need to wait for all paints before running tests to avoid contaminations
+// from styling of this document itself.
+waitForAllPaints(function() {
+ add_task(function* restyling_for_main_thread_animations() {
+ var div = addDiv(null, { style: 'animation: background-color 100s' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ ok(!animation.isRunningOnCompositor);
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 5,
+ 'CSS animations running on the main-thread should update style ' +
+ 'on the main thread');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* no_restyling_for_compositor_animations() {
+ var div = addDiv(null, { style: 'animation: opacity 100s' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ ok(animation.isRunningOnCompositor);
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 0,
+ 'CSS animations running on the compositor should not update style ' +
+ 'on the main thread');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* no_restyling_for_compositor_transitions() {
+ var div = addDiv(null, { style: 'transition: opacity 100s; opacity: 0' });
+ getComputedStyle(div).opacity;
+ div.style.opacity = 1;
+
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ ok(animation.isRunningOnCompositor);
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 0,
+ 'CSS transitions running on the compositor should not update style ' +
+ 'on the main thread');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* no_restyling_when_animation_duration_is_changed() {
+ var div = addDiv(null, { style: 'animation: opacity 100s' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ ok(animation.isRunningOnCompositor);
+
+ div.animationDuration = '200s';
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 0,
+ 'Animations running on the compositor should not update style ' +
+ 'on the main thread');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* only_one_restyling_after_finish_is_called() {
+ var div = addDiv(null, { style: 'animation: opacity 100s' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ ok(animation.isRunningOnCompositor);
+
+ animation.finish();
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 1,
+ 'Animations running on the compositor should only update style ' +
+ 'once after finish() is called');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task(function* no_restyling_mouse_movement_on_finished_transition() {
+ var div = addDiv(null, { style: 'transition: opacity 1ms; opacity: 0' });
+ getComputedStyle(div).opacity;
+ div.style.opacity = 1;
+
+ var animation = div.getAnimations()[0];
+ var initialRect = div.getBoundingClientRect();
+
+ yield animation.finished;
+
+ var mouseX = initialRect.left + initialRect.width / 2;
+ var mouseY = initialRect.top + initialRect.height / 2;
+ var markers = yield observeStyling(5, function() {
+ // We can't use synthesizeMouse here since synthesizeMouse causes
+ // layout flush.
+ synthesizeMouseAtPoint(mouseX++, mouseY++,
+ { type: 'mousemove' }, window);
+ });
+
+ is(markers.length, 0,
+ 'Bug 1219236: Finished transitions should never cause restyles ' +
+ 'when mouse is moved on the animations');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task(function* no_restyling_mouse_movement_on_finished_animation() {
+ var div = addDiv(null, { style: 'animation: opacity 1ms' });
+ var animation = div.getAnimations()[0];
+
+ var initialRect = div.getBoundingClientRect();
+
+ yield animation.finished;
+
+ var mouseX = initialRect.left + initialRect.width / 2;
+ var mouseY = initialRect.top + initialRect.height / 2;
+ var markers = yield observeStyling(5, function() {
+ // We can't use synthesizeMouse here since synthesizeMouse causes
+ // layout flush.
+ synthesizeMouseAtPoint(mouseX++, mouseY++,
+ { type: 'mousemove' }, window);
+ });
+
+ is(markers.length, 0,
+ 'Bug 1219236: Finished animations should never cause restyles ' +
+ 'when mouse is moved on the animations');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* no_restyling_compositor_animations_out_of_view_element() {
+ if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+ return;
+ }
+
+ var div = addDiv(null,
+ { style: 'animation: opacity 100s; transform: translateY(-400px);' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ ok(!animation.isRunningOnCompositor);
+
+ var markers = yield observeStyling(5);
+
+ is(markers.length, 0,
+ 'Animations running on the compositor in an out-of-view element ' +
+ 'should never cause restyles');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task(function* no_restyling_main_thread_animations_out_of_view_element() {
+ if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+ return;
+ }
+
+ var div = addDiv(null,
+ { style: 'animation: background-color 100s; transform: translateY(-400px);' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ var markers = yield observeStyling(5);
+
+ is(markers.length, 0,
+ 'Animations running on the main-thread in an out-of-view element ' +
+ 'should never cause restyles');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* no_restyling_compositor_animations_in_scrolled_out_element() {
+ if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+ return;
+ }
+
+ /*
+ On Android the opacity animation runs on the compositor even if it is
+ scrolled out of view. We will fix this in bug 1247800.
+ */
+ if (isAndroid) {
+ return;
+ }
+ var parentElement = addDiv(null,
+ { style: 'overflow-y: scroll; height: 20px;' });
+ var div = addDiv(null,
+ { style: 'animation: opacity 100s; position: relative; top: 100px;' });
+ parentElement.appendChild(div);
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+
+ var markers = yield observeStyling(5);
+
+ is(markers.length, 0,
+ 'Animations running on the compositor for elements ' +
+ 'which are scrolled out should never cause restyles');
+
+ yield ensureElementRemoval(parentElement);
+ });
+
+ add_task(function* no_restyling_main_thread_animations_in_scrolled_out_element() {
+ if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+ return;
+ }
+
+ /*
+ On Android throttled animations are left behind on the main thread in some
+ frames, We will fix this in bug 1247800.
+ */
+ if (isAndroid) {
+ return;
+ }
+
+ var parentElement = addDiv(null,
+ { style: 'overflow-y: scroll; height: 20px;' });
+ var div = addDiv(null,
+ { style: 'animation: background-color 100s; position: relative; top: 100px;' });
+ parentElement.appendChild(div);
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ var markers = yield observeStyling(5);
+
+ is(markers.length, 0,
+ 'Animations running on the main-thread for elements ' +
+ 'which are scrolled out should never cause restyles');
+
+ yield ensureElementRemoval(parentElement);
+ });
+
+ add_task(function* no_restyling_main_thread_animations_in_nested_scrolled_out_element() {
+ if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+ return;
+ }
+
+ /*
+ On Android throttled animations are left behind on the main thread in some
+ frames, We will fix this in bug 1247800.
+ */
+ if (isAndroid) {
+ return;
+ }
+
+ var grandParent = addDiv(null,
+ { style: 'overflow-y: scroll; height: 20px;' });
+ var parentElement = addDiv(null,
+ { style: 'overflow-y: scroll; height: 100px;' });
+ var div = addDiv(null,
+ { style: 'animation: background-color 100s; position: relative; top: 100px;' });
+ grandParent.appendChild(parentElement);
+ parentElement.appendChild(div);
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ var markers = yield observeStyling(5);
+
+ is(markers.length, 0,
+ 'Animations running on the main-thread which are in nested elements ' +
+ 'which are scrolled out should never cause restyles');
+
+ yield ensureElementRemoval(grandParent);
+ });
+
+ add_task_if_omta_enabled(function* no_restyling_compositor_animations_in_visiblily_hidden_element() {
+ var div = addDiv(null,
+ { style: 'animation: opacity 100s; visibility: hidden' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ ok(!animation.isRunningOnCompositor);
+
+ var markers = yield observeStyling(5);
+
+ todo_is(markers.length, 0,
+ 'Bug 1237454: Animations running on the compositor in ' +
+ 'visibility hidden element should never cause restyles');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task(function* restyling_main_thread_animations_moved_in_view_by_scrolling() {
+ if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+ return;
+ }
+
+ /*
+ On Android throttled animations are left behind on the main thread in some
+ frames, We will fix this in bug 1247800.
+ */
+ if (isAndroid) {
+ return;
+ }
+
+ var parentElement = addDiv(null,
+ { style: 'overflow-y: scroll; height: 20px;' });
+ var div = addDiv(null,
+ { style: 'animation: background-color 100s; position: relative; top: 100px;' });
+ parentElement.appendChild(div);
+ var animation = div.getAnimations()[0];
+
+ var parentRect = parentElement.getBoundingClientRect();
+ var centerX = parentRect.left + parentRect.width / 2;
+ var centerY = parentRect.top + parentRect.height / 2;
+
+ yield animation.ready;
+
+ var markers = yield observeStyling(1, function() {
+ // We can't use synthesizeWheel here since synthesizeWheel causes
+ // layout flush.
+ synthesizeWheelAtPoint(centerX, centerY,
+ { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+ deltaY: 100 });
+ });
+
+ is(markers.length, 1,
+ 'Animations running on the main-thread which were in scrolled out ' +
+ 'elements should update restyling soon after the element moved in ' +
+ 'view by scrolling');
+
+ yield ensureElementRemoval(parentElement);
+ });
+
+ add_task(function* restyling_main_thread_animations_moved_in_view_by_scrolling() {
+ if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+ return;
+ }
+
+ var grandParent = addDiv(null,
+ { style: 'overflow-y: scroll; height: 20px;' });
+ var parentElement = addDiv(null,
+ { style: 'overflow-y: scroll; height: 200px;' });
+ var div = addDiv(null,
+ { style: 'animation: background-color 100s; position: relative; top: 100px;' });
+ grandParent.appendChild(parentElement);
+ parentElement.appendChild(div);
+ var animation = div.getAnimations()[0];
+
+ var parentRect = grandParent.getBoundingClientRect();
+ var centerX = parentRect.left + parentRect.width / 2;
+ var centerY = parentRect.top + parentRect.height / 2;
+
+ yield animation.ready;
+
+ var markers = yield observeStyling(1, function() {
+ // We can't use synthesizeWheel here since synthesizeWheel causes
+ // layout flush.
+ synthesizeWheelAtPoint(centerX, centerY,
+ { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+ deltaY: 100 });
+ });
+
+ // FIXME: We should reduce a redundant restyle here.
+ ok(markers.length >= 1,
+ 'Animations running on the main-thread which were in nested scrolled ' +
+ 'out elements should update restyle soon after the element moved ' +
+ 'in view by scrolling');
+
+ yield ensureElementRemoval(grandParent);
+ });
+
+ add_task(function* restyling_main_thread_animations_move_out_of_view_by_scrolling() {
+ if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+ return;
+ }
+
+ /*
+ On Android throttled animations are left behind on the main thread in some
+ frames, We will fix this in bug 1247800.
+ */
+ if (isAndroid) {
+ return;
+ }
+
+ var parentElement = addDiv(null,
+ { style: 'overflow-y: scroll; height: 200px;' });
+ var div = addDiv(null,
+ { style: 'animation: background-color 100s;' });
+ var pad = addDiv(null,
+ { style: 'height: 400px;' });
+ parentElement.appendChild(div);
+ parentElement.appendChild(pad);
+ var animation = div.getAnimations()[0];
+
+ var parentRect = parentElement.getBoundingClientRect();
+ var centerX = parentRect.left + parentRect.width / 2;
+ var centerY = parentRect.top + parentRect.height / 2;
+
+ yield animation.ready;
+
+ // We can't use synthesizeWheel here since synthesizeWheel causes
+ // layout flush.
+ synthesizeWheelAtPoint(centerX, centerY,
+ { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
+ deltaY: 200 });
+
+ var markers = yield observeStyling(5);
+
+ // FIXME: We should reduce a redundant restyle here.
+ ok(markers.length >= 0,
+ 'Animations running on the main-thread which are in scrolled out ' +
+ 'elements should throttle restyling');
+
+ yield ensureElementRemoval(parentElement);
+ });
+
+ add_task(function* restyling_main_thread_animations_moved_in_view_by_resizing() {
+ if (!SpecialPowers.getBoolPref('dom.animations.offscreen-throttling')) {
+ return;
+ }
+
+ var parentElement = addDiv(null,
+ { style: 'overflow-y: scroll; height: 20px;' });
+ var div = addDiv(null,
+ { style: 'animation: background-color 100s; position: relative; top: 100px;' });
+ parentElement.appendChild(div);
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+
+ var markers = yield observeStyling(1, function() {
+ parentElement.style.height = '100px';
+ });
+
+ is(markers.length, 1,
+ 'Animations running on the main-thread which was in scrolled out ' +
+ 'elements should update restyling soon after the element moved in ' +
+ 'view by resizing');
+
+ yield ensureElementRemoval(parentElement);
+ });
+
+ add_task(function* no_restyling_main_thread_animations_in_visiblily_hidden_element() {
+ var div = addDiv(null,
+ { style: 'animation: background-color 100s; visibility: hidden' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ var markers = yield observeStyling(5);
+
+ todo_is(markers.length, 0,
+ 'Bug 1237454: Animations running on the main-thread in ' +
+ 'visibility hidden element should never cause restyles');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* no_restyling_compositor_animations_after_pause_is_called() {
+ var div = addDiv(null, { style: 'animation: opacity 100s' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+ ok(animation.isRunningOnCompositor);
+
+ animation.pause();
+
+ yield animation.ready;
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 0,
+ 'Bug 1232563: Paused animations running on the compositor should ' +
+ 'never cause restyles once after pause() is called');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task(function* no_restyling_main_thread_animations_after_pause_is_called() {
+ var div = addDiv(null, { style: 'animation: background-color 100s' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+
+ animation.pause();
+
+ yield animation.ready;
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 0,
+ 'Bug 1232563: Paused animations running on the main-thread should ' +
+ 'never cause restyles after pause() is called');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* only_one_restyling_when_current_time_is_set_to_middle_of_duration() {
+ var div = addDiv(null, { style: 'animation: opacity 100s' });
+ var animation = div.getAnimations()[0];
+
+ yield animation.ready;
+
+ animation.currentTime = 50 * MS_PER_SEC;
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 1,
+ 'Bug 1235478: Animations running on the compositor should only once ' +
+ 'update style when currentTime is set to middle of duration time');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* change_duration_and_currenttime() {
+ var div = addDiv(null);
+ var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
+
+ yield animation.ready;
+ ok(animation.isRunningOnCompositor);
+
+ // Set currentTime to a time longer than duration.
+ animation.currentTime = 500 * MS_PER_SEC;
+
+ // Now the animation immediately get back from compositor.
+ ok(!animation.isRunningOnCompositor);
+
+ // Extend the duration.
+ animation.effect.timing.duration = 800 * MS_PER_SEC;
+ var markers = yield observeStyling(5);
+ is(markers.length, 1,
+ 'Animations running on the compositor should update style ' +
+ 'when timing.duration is made longer than the current time');
+
+ yield ensureElementRemoval(div);
+ });
+
+ add_task(function* script_animation_on_display_none_element() {
+ var div = addDiv(null);
+ var animation = div.animate({ backgroundColor: [ 'red', 'blue' ] },
+ 100 * MS_PER_SEC);
+
+ yield animation.ready;
+
+ div.style.display = 'none';
+
+ // We need to wait a frame to apply display:none style.
+ yield waitForFrame();
+
+ is(animation.playState, 'running',
+ 'Script animations keep running even when the target element has ' +
+ '"display: none" style');
+
+ ok(!animation.isRunningOnCompositor,
+ 'Script animations on "display:none" element should not run on the ' +
+ 'compositor');
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 0,
+ 'Script animations on "display: none" element should not update styles');
+
+ div.style.display = '';
+
+ // We need to wait a frame to unapply display:none style.
+ yield waitForFrame();
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 5,
+ 'Script animations restored from "display: none" state should update ' +
+ 'styles');
+
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* compositable_script_animation_on_display_none_element() {
+ var div = addDiv(null);
+ var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
+
+ yield animation.ready;
+
+ div.style.display = 'none';
+
+ // We need to wait a frame to apply display:none style.
+ yield waitForFrame();
+
+ is(animation.playState, 'running',
+ 'Opacity script animations keep running even when the target element ' +
+ 'has "display: none" style');
+
+ ok(!animation.isRunningOnCompositor,
+ 'Opacity script animations on "display:none" element should not ' +
+ 'run on the compositor');
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 0,
+ 'Opacity script animations on "display: none" element should not ' +
+ 'update styles');
+
+ div.style.display = '';
+
+ // We need to wait a frame to unapply display:none style.
+ yield waitForFrame();
+
+ ok(animation.isRunningOnCompositor,
+ 'Opacity script animations restored from "display: none" should be ' +
+ 'run on the compositor');
+
+ yield ensureElementRemoval(div);
+ });
+
+ add_task(function* restyling_for_empty_keyframes() {
+ var div = addDiv(null);
+ var animation = div.animate({ }, 100 * MS_PER_SEC);
+
+ yield animation.ready;
+ var markers = yield observeStyling(5);
+
+ is(markers.length, 0,
+ 'Animations with no keyframes should not cause restyles');
+
+ animation.effect.setKeyframes({ backgroundColor: ['red', 'blue'] });
+ markers = yield observeStyling(5);
+
+ is(markers.length, 5,
+ 'Setting valid keyframes should cause regular animation restyles to ' +
+ 'occur');
+
+ animation.effect.setKeyframes({ });
+ markers = yield observeStyling(5);
+
+ is(markers.length, 1,
+ 'Setting an empty set of keyframes should trigger a single restyle ' +
+ 'to remove the previous animated style');
+
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(function* no_restyling_when_animation_style_when_re_setting_same_animation_property() {
+ var div = addDiv(null, { style: 'animation: opacity 100s' });
+ var animation = div.getAnimations()[0];
+ yield animation.ready;
+ ok(animation.isRunningOnCompositor);
+ // Apply the same animation style
+ div.style.animation = 'opacity 100s';
+ var markers = yield observeStyling(5);
+ is(markers.length, 0,
+ 'Applying same animation style ' +
+ 'should never cause restyles');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task(function* necessary_update_should_be_invoked() {
+ var div = addDiv(null, { style: 'animation: background-color 100s' });
+ var animation = div.getAnimations()[0];
+ yield animation.ready;
+ yield waitForAnimationFrames(5);
+ // Apply another animation style
+ div.style.animation = 'background-color 110s';
+ var animation = div.getAnimations()[0];
+ var markers = yield observeStyling(5);
+ is(markers.length, 5,
+ 'Applying animation style with different duration ' +
+ 'should cause restyles on every frame.');
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(
+ function* changing_cascading_result_for_main_thread_animation() {
+ var div = addDiv(null, { style: 'background-color: blue' });
+ var animation = div.animate({ opacity: [0, 1],
+ backgroundColor: ['green', 'red'] },
+ 100 * MS_PER_SEC);
+ yield animation.ready;
+ ok(animation.isRunningOnCompositor,
+ 'The opacity animation is running on the compositor');
+ // Make the background-color style as !important to cause an update
+ // to the cascade.
+ // Bug 1300982: The background-color animation should be no longer
+ // running on the main thread.
+ div.style.setProperty('background-color', '1', 'important');
+ var markers = yield observeStyling(5);
+ todo_is(markers.length, 0,
+ 'Changing cascading result for the property running on the main ' +
+ 'thread does not cause synchronization layer of opacity animation ' +
+ 'running on the compositor');
+ yield ensureElementRemoval(div);
+ }
+ );
+
+ add_task(function* restyling_for_animation_on_orphaned_element() {
+ var div = addDiv(null);
+ var animation = div.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+
+ yield animation.ready;
+
+ div.remove();
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 0,
+ 'Animation on orphaned element should not cause restyles');
+
+ document.body.appendChild(div);
+
+ markers = yield observeStyling(1);
+ // We are observing restyles in rAF callback which is processed before
+ // restyling process in each frame, so in the first frame there should be
+ // no observed restyle since we don't process restyle while the element
+ // is not attached to the document.
+ is(markers.length, 0,
+ 'We observe no restyle in the first frame right after re-atatching ' +
+ 'to the document');
+ markers = yield observeStyling(5);
+ is(markers.length, 5,
+ 'Animation on re-attached to the document begins to update style');
+
+ yield ensureElementRemoval(div);
+ });
+
+ add_task_if_omta_enabled(
+ // Tests that if we remove an element from the document whose animation
+ // cascade needs recalculating, that it is correctly updated when it is
+ // re-attached to the document.
+ function* restyling_for_opacity_animation_on_re_attached_element() {
+ var div = addDiv(null, { style: 'opacity: 1 ! important' });
+ var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
+
+ yield animation.ready;
+ ok(!animation.isRunningOnCompositor,
+ 'The opacity animation overridden by an !important rule is NOT ' +
+ 'running on the compositor');
+
+ // Drop the !important rule to update the cascade.
+ div.style.setProperty('opacity', '1', '');
+
+ div.remove();
+
+ var markers = yield observeStyling(5);
+ is(markers.length, 0,
+ 'Opacity animation on orphaned element should not cause restyles');
+
+ document.body.appendChild(div);
+
+ // Need a frame to give the animation a chance to be sent to the
+ // compositor.
+ yield waitForFrame();
+
+ ok(animation.isRunningOnCompositor,
+ 'The opacity animation which is no longer overridden by the ' +
+ '!important rule begins running on the compositor even if the ' +
+ '!important rule had been dropped before the target element was ' +
+ 'removed');
+
+ yield ensureElementRemoval(div);
+ }
+ );
+
+});
+
+</script>
+</body>