Bug 1211886 - Make infinite animations that iterated at least once pausable; r=miker
authorPatrick Brosset <pbrosset@mozilla.com>
Wed, 28 Oct 2015 12:58:39 +0100
changeset 270202 a88a1f9ba215a0b5f2235dd33d672a1347a8b136
parent 270201 302277d37f39f4cbddfe9d5ae72e4353b745165f
child 270203 f79ce8bc3f10dd99618b07716139fe692920e572
push id29607
push userkwierso@gmail.com
push dateFri, 30 Oct 2015 00:07:48 +0000
treeherdermozilla-central@6d1e12f5725b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmiker
bugs1211886
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1211886 - Make infinite animations that iterated at least once pausable; r=miker
devtools/client/animationinspector/animation-panel.js
devtools/client/animationinspector/components.js
devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js
devtools/client/animationinspector/test/browser_animation_toggle_button_resets_on_navigate.js
devtools/client/animationinspector/test/head.js
--- a/devtools/client/animationinspector/animation-panel.js
+++ b/devtools/client/animationinspector/animation-panel.js
@@ -174,17 +174,17 @@ var AnimationsPanel = {
   },
 
   onTabNavigated: function() {
     this.toggleAllButtonEl.classList.remove("paused");
   },
 
   onTimelineDataChanged: function(e, data) {
     this.timelineData = data;
-    let {isMoving, isUserDrag, time} = data;
+    let {isMoving, isPaused, isUserDrag, time} = data;
 
     this.playTimelineButtonEl.classList.toggle("paused", !isMoving);
 
     // If the timeline data changed as a result of the user dragging the
     // scrubber, then pause all animations and set their currentTimes.
     // (Note that we want server-side requests to be sequenced, so we only do
     // this after the previous currentTime setting was done).
     if (isUserDrag && !this.setCurrentTimeAllPromise) {
--- a/devtools/client/animationinspector/components.js
+++ b/devtools/client/animationinspector/components.js
@@ -679,23 +679,32 @@ AnimationsTimeline.prototype = {
     return this.animations.some(({state}) => state.playState === "running");
   },
 
   wasRewound: function() {
     return !this.isAtLeastOneAnimationPlaying() &&
            this.animations.every(({state}) => state.currentTime === 0);
   },
 
+  hasInfiniteAnimations: function() {
+    return this.animations.some(({state}) => !state.iterationCount);
+  },
+
   startAnimatingScrubber: function(time) {
     let x = TimeScale.startTimeToDistance(time, this.timeHeaderEl.offsetWidth);
     this.scrubberEl.style.left = x + "px";
 
-    if (time < TimeScale.minStartTime ||
-        time > TimeScale.maxEndTime ||
-        !this.isAtLeastOneAnimationPlaying()) {
+    // Only stop the scrubber if it's out of bounds or all animations have been
+    // paused, but not if at least an animation is infinite.
+    let isOutOfBounds = time < TimeScale.minStartTime ||
+                        time > TimeScale.maxEndTime;
+    let isAllPaused = !this.isAtLeastOneAnimationPlaying();
+    let hasInfinite = this.hasInfiniteAnimations();
+
+    if (isAllPaused || (isOutOfBounds && !hasInfinite)) {
       this.stopAnimatingScrubber();
       this.emit("timeline-data-changed", {
         isPaused: !this.isAtLeastOneAnimationPlaying(),
         isMoving: false,
         isUserDrag: false,
         time: TimeScale.distanceToRelativeTime(x, this.timeHeaderEl.offsetWidth)
       });
       return;
--- a/devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js
@@ -3,21 +3,27 @@
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Check that the timeline toolbar contains a pause button and that this pause
 // button can be clicked. Check that when it is, the current animations
 // displayed in the timeline get their playstates changed accordingly, and check
 // that the scrubber resumes/stops moving.
+// Also checks that the button goes to the right state when the scrubber has
+// reached the end of the timeline: continues to be in playing mode for infinite
+// animations, goes to paused mode otherwise.
+// And test that clicking the button once the scrubber has reached the end of
+// the timeline does the right thing.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
 
-  let {panel} = yield openAnimationInspector();
+  let {panel, inspector} = yield openAnimationInspector();
+  let timeline = panel.animationsTimelineComponent;
   let btn = panel.playTimelineButtonEl;
 
   ok(btn, "The play/pause button exists");
   ok(!btn.classList.contains("paused"),
      "The play/pause button is in its playing state");
 
   info("Click on the button to pause all timeline animations");
   yield clickTimelinePlayPauseButton(panel);
@@ -27,9 +33,62 @@ add_task(function*() {
   yield assertScrubberMoving(panel, false);
 
   info("Click again on the button to play all timeline animations");
   yield clickTimelinePlayPauseButton(panel);
 
   ok(!btn.classList.contains("paused"),
      "The play/pause button is in its playing state again");
   yield assertScrubberMoving(panel, true);
+
+  // Some animations on the test page are infinite, so the scrubber won't stop
+  // at the end of the timeline, and the button should remain in play mode.
+  info("Select an infinite animation, reload the page and wait for the " +
+       "animation to complete");
+  yield selectNode(".multi", inspector);
+  yield reloadTab(inspector);
+  yield waitForOutOfBoundScrubber(timeline);
+
+  ok(!btn.classList.contains("paused"),
+     "The button is in its playing state still, animations are infinite.");
+  yield assertScrubberMoving(panel, true);
+
+  info("Click on the button after the scrubber has moved out of bounds");
+  yield clickTimelinePlayPauseButton(panel);
+
+  ok(btn.classList.contains("paused"),
+     "The button can be paused after the scrubber has moved out of bounds");
+  yield assertScrubberMoving(panel, false);
+
+  // For a finite animation though, once the scrubber reaches the end of the
+  // timeline, it should go back to paused mode.
+  info("Select a finite animation, reload the page and wait for the " +
+       "animation to complete");
+  yield selectNode(".negative-delay", inspector);
+  yield reloadTab(inspector);
+  yield waitForOutOfBoundScrubber(timeline);
+
+  ok(btn.classList.contains("paused"),
+     "The button is in paused state once finite animations are done");
+  yield assertScrubberMoving(panel, false);
+
+  info("Click again on the button to play the animation from the start again");
+  yield clickTimelinePlayPauseButton(panel);
+
+  ok(!btn.classList.contains("paused"),
+     "Clicking the button once finite animations are done should restart them");
+  yield assertScrubberMoving(panel, true);
 });
+
+function waitForOutOfBoundScrubber({win, scrubberEl}) {
+  return new Promise(resolve => {
+    function check() {
+      let pos = scrubberEl.getBoxQuads()[0].bounds.right;
+      let width = win.document.documentElement.offsetWidth;
+      if (pos >= width) {
+        setTimeout(resolve, 50);
+      } else {
+        setTimeout(check, 50);
+      }
+    }
+    check();
+  });
+}
--- a/devtools/client/animationinspector/test/browser_animation_toggle_button_resets_on_navigate.js
+++ b/devtools/client/animationinspector/test/browser_animation_toggle_button_resets_on_navigate.js
@@ -17,16 +17,13 @@ add_task(function*() {
     "The toggle button is in its running state by default");
 
   info("Toggle all animations, so that they pause");
   yield panel.toggleAll();
   ok(panel.toggleAllButtonEl.classList.contains("paused"),
     "The toggle button now is in its paused state");
 
   info("Reloading the page");
-  let onNewRoot = inspector.once("new-root");
-  yield reloadTab();
-  yield onNewRoot;
-  yield inspector.once("inspector-updated");
+  yield reloadTab(inspector);
 
   ok(!panel.toggleAllButtonEl.classList.contains("paused"),
     "The toggle button is back in its running state");
 });
--- a/devtools/client/animationinspector/test/head.js
+++ b/devtools/client/animationinspector/test/head.js
@@ -80,19 +80,24 @@ function addTab(url) {
     def.resolve(tab);
   }, true);
 
   return def.promise;
 }
 
 /**
  * Reload the current tab location.
+ * @param {InspectorPanel} inspector The instance of InspectorPanel currently
+ * loaded in the toolbox
  */
-function reloadTab() {
-  return executeInContent("devtools:test:reload", {}, {}, false);
+function* reloadTab(inspector) {
+  let onNewRoot = inspector.once("new-root");
+  yield executeInContent("devtools:test:reload", {}, {}, false);
+  yield onNewRoot;
+  yield inspector.once("inspector-updated");
 }
 
 /**
  * Get the NodeFront for a given css selector, via the protocol
  * @param {String} selector
  * @param {InspectorPanel} inspector The instance of InspectorPanel currently
  * loaded in the toolbox
  * @return {Promise} Resolves to the NodeFront instance