Bug 1235286 - Part 2: Tests for animation optimizations. r?birtles draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Fri, 25 Dec 2015 19:37:34 +0900
changeset 322560 050837f18e6ec556f5d964737dc85c73420287ec
parent 322559 afa020b8d73a7fc25f790a8288775e958d17eaef
child 513130 047374c0ce4345174d38da8aa656deb979288fce
push id9633
push userhiikezoe@mozilla-japan.org
push dateTue, 19 Jan 2016 05:05:15 +0000
reviewersbirtles
bugs1235286
milestone46.0a1
Bug 1235286 - Part 2: Tests for animation optimizations. r?birtles
dom/animation/test/chrome.ini
dom/animation/test/chrome/test_restyles.html
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 support-files =
   testcommon.js
   ../../imptests/testharness.js
   ../../imptests/testharnessreport.js
 [chrome/test_animation_observers.html]
+[chrome/test_restyles.html]
 [chrome/test_running_on_compositor.html]
 skip-if = buildapp == 'b2g'
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/chrome/test_restyles.html
@@ -0,0 +1,318 @@
+<!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';
+      });
+      resolve(stylingMarkers);
+    });
+  });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
+var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote &&
+                  SpecialPowers.getBoolPref(OMTAPrefKey);
+
+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_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');
+    div.remove(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');
+    div.remove(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');
+    div.remove(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');
+    div.remove(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');
+    div.remove(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');
+    div.remove(div);
+  });
+
+  add_task_if_omta_enabled(function* no_restyling_compositor_animations_out_of_view_element() {
+    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);
+
+    todo_is(markers.length, 0,
+            'Bug 1166500: Animations running on the compositor in out of ' +
+            'view element should never cause restyles');
+    div.remove(div);
+  });
+
+  add_task(function* no_restyling_main_thread_animations_out_of_view_element() {
+    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);
+
+    todo_is(markers.length, 0,
+            'Bug 1166500: Animations running on the main-thread in out of ' +
+            'view element should never cause restyles');
+    div.remove(div);
+  });
+
+  add_task_if_omta_enabled(function* no_restyling_compositor_animations_in_scrolled_out_element() {
+    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;
+    ok(!animation.isRunningOnCompositor);
+
+    var markers = yield observeStyling(5);
+
+    todo_is(markers.length, 0,
+            'Bug 1166500: Animations running on the compositor in elements ' +
+            'which are scrolled out should never cause restyles');
+    parentElement.remove(div);
+  });
+
+  add_task(function* no_restyling_main_thread_animations_in_scrolled_out_element() {
+    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);
+
+    todo_is(markers.length, 0,
+            'Bug 1166500: Animations running on the main-thread in elements ' +
+            'which are scrolled out should never cause restyles');
+    parentElement.remove(div);
+  });
+
+  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');
+    div.remove(div);
+  });
+
+  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');
+    div.remove(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');
+    div.remove(div);
+  });
+
+  add_task(function* no_restyling_matn_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');
+    div.remove(div);
+  });
+
+});
+
+</script>
+</body>