Bug 1211886 - Make infinite animations that iterated at least once pausable; r=miker; a=blanket-sylvestre
authorPatrick Brosset <pbrosset@mozilla.com>
Wed, 28 Oct 2015 12:58:39 +0100
changeset 305374 56e2e69aea4d8e87652f560a72c5dc7376d834ad
parent 305373 cb41b90cd72f67ea8abda60551d1af6f79888c42
child 305375 bc9c6e9960069076f78bd91cb660e05779c01aa2
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmiker, blanket-sylvestre
bugs1211886
milestone44.0a2
Bug 1211886 - Make infinite animations that iterated at least once pausable; r=miker; a=blanket-sylvestre
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