Bug 1468475 - Part 1. Introduce zeroPositionTime in order to shift the graduation if animations have negative-delay. r?daisuke draft
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Fri, 06 Jul 2018 19:34:38 +0900
changeset 815510 86f77e9d3952c376e942fec0ef4e35043060a699
parent 814704 afdeb0288690f0acde2ba6bd008f860e1acdd026
child 815511 2ed7f042063c55de4c58fb2aa57efcb8f2236cbd
child 815512 fe7d3ee17f8f66539fdb07325e687ff2da2ed4a8
push id115521
push userbmo:mantaroh@gmail.com
push dateMon, 09 Jul 2018 00:22:27 +0000
reviewersdaisuke
bugs1468475
milestone63.0a1
Bug 1468475 - Part 1. Introduce zeroPositionTime in order to shift the graduation if animations have negative-delay. r?daisuke This patch will introduce the zeroPositionTime, this value means the time that current time of animation will be zero. In front-side, use this value for shifting the graduation in order to display the zero. MozReview-Commit-ID: 3XawdLTehJo
devtools/client/inspector/animation/animation.js
devtools/client/inspector/animation/test/browser.ini
devtools/client/inspector/animation/test/browser_animation_current-time-scrubber-with-negative-delay.js
devtools/client/inspector/animation/test/browser_animation_pause-resume-button_end-time.js
devtools/client/inspector/animation/test/browser_animation_rewind-button.js
devtools/client/inspector/animation/utils/timescale.js
devtools/server/actors/animation.js
devtools/shared/fronts/animation.js
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -372,17 +372,18 @@ class AnimationInspector {
   }
 
   removeAnimationsCurrentTimeListener(listener) {
     this.animationsCurrentTimeListeners =
       this.animationsCurrentTimeListeners.filter(l => l !== listener);
   }
 
   async rewindAnimationsCurrentTime() {
-    await this.setAnimationsCurrentTime(0, true);
+    const { timeScale } = this.state;
+    await this.setAnimationsCurrentTime(timeScale.zeroPositionTime, true);
   }
 
   selectAnimation(animation) {
     this.inspector.store.dispatch(updateSelectedAnimation(animation));
   }
 
   async setSelectedNode(nodeFront) {
     if (this.inspector.selection.nodeFront === nodeFront) {
@@ -450,17 +451,17 @@ class AnimationInspector {
         await this.inspector.target.actorHasMethod("animations", "pauseSome");
     }
 
     let { animations, timeScale } = this.state;
 
     try {
       if (doPlay && animations.every(animation =>
                       timeScale.getEndTime(animation) <= animation.state.currentTime)) {
-        await this.doSetCurrentTimes(0);
+        await this.doSetCurrentTimes(timeScale.zeroPositionTime);
       }
 
       // If the server does not support pauseSome/playSome function, (which happens
       // when connected to server older than FF62), use pauseAll/playAll instead.
       // See bug 1456857.
       if (this.hasPausePlaySome) {
         if (doPlay) {
           await this.animationsFront.playSome(animations);
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -38,16 +38,17 @@ support-files =
 [browser_animation_animation-target_highlight.js]
 [browser_animation_animation-target_select.js]
 [browser_animation_animation-timeline-tick.js]
 [browser_animation_css-transition-with-playstate-idle.js]
 [browser_animation_current-time-label.js]
 [browser_animation_current-time-scrubber.js]
 [browser_animation_current-time-scrubber-rtl.js]
 [browser_animation_current-time-scrubber_each-different-creation-time-animations.js]
+[browser_animation_current-time-scrubber-with-negative-delay.js]
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_indication-bar.js]
 [browser_animation_infinity-duration_current-time-scrubber.js]
 [browser_animation_infinity-duration_summary-graph.js]
 [browser_animation_infinity-duration_tick-label.js]
 [browser_animation_inspector_exists.js]
 [browser_animation_keyframes-graph_computed-value-path-01.js]
 [browser_animation_keyframes-graph_computed-value-path-02.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_current-time-scrubber-with-negative-delay.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test whether the most left position means negative current time.
+
+add_task(async function() {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+  await removeAnimatedElementsExcept([".cssanimation-normal", ".delay-negative"]);
+  const { animationInspector, panel, inspector } = await openAnimationInspector();
+
+  info("Checking scrubber controller existence");
+  const controllerEl = panel.querySelector(".current-time-scrubber-area");
+  ok(controllerEl, "scrubber controller should exist");
+
+  info("Checking the current time of most left scrubber position");
+  const timeScale = animationInspector.state.timeScale;
+  await clickOnCurrentTimeScrubberController(animationInspector, panel, 0);
+  assertAnimationsCurrentTime(animationInspector, -1 * timeScale.zeroPositionTime);
+
+  info("Select negative current time animation");
+  await selectNodeAndWaitForAnimations(".cssanimation-normal", inspector);
+  assertAnimationsCurrentTime(animationInspector, -1 * timeScale.zeroPositionTime);
+
+  info("Back to 'body' and rewind the animation");
+  await selectNodeAndWaitForAnimations("body", inspector);
+  await clickOnRewindButton(animationInspector, panel);
+  assertAnimationsCurrentTime(animationInspector, 0);
+});
--- a/devtools/client/inspector/animation/test/browser_animation_pause-resume-button_end-time.js
+++ b/devtools/client/inspector/animation/test/browser_animation_pause-resume-button_end-time.js
@@ -3,38 +3,41 @@
 
 "use strict";
 
 // Test whether the animation can rewind if the current time is over end time when
 // the resume button clicked.
 
 add_task(async function() {
   await addTab(URL_ROOT + "doc_simple_animation.html");
-  await removeAnimatedElementsExcept([".animated", ".end-delay", ".long"]);
+  await removeAnimatedElementsExcept([".animated",
+                                      ".end-delay",
+                                      ".long",
+                                      ".negative-delay"]);
   const { animationInspector, panel } = await openAnimationInspector();
 
   info("Check animations state after resuming with infinite animation");
   info("Make the current time of animation to be over its end time");
   await clickOnCurrentTimeScrubberController(animationInspector, panel, 1);
   info("Resume animations");
   await clickOnPauseResumeButton(animationInspector, panel);
   await wait(1000);
   assertPlayState(animationInspector.state.animations,
-                  ["running", "finished", "finished"]);
+                  ["running", "finished", "finished", "finished"]);
   await clickOnCurrentTimeScrubberController(animationInspector, panel, 0);
 
   info("Check animations state after resuming without infinite animation");
   info("Remove infinite animation");
   await setClassAttribute(animationInspector, ".animated", "ball still");
   info("Make the current time of animation to be over its end time");
   await clickOnCurrentTimeScrubberController(animationInspector, panel, 1);
   await clickOnPlaybackRateSelector(animationInspector, panel, 10);
   info("Resume animations");
   await clickOnPauseResumeButton(animationInspector, panel);
-  assertPlayState(animationInspector.state.animations, ["running", "running"]);
+  assertPlayState(animationInspector.state.animations, ["running", "running", "running"]);
   assertCurrentTimeLessThanDuration(animationInspector.state.animations);
   assertScrubberPosition(panel);
 });
 
 function assertPlayState(animations, expectedState) {
   animations.forEach((animation, index) => {
     is(animation.state.playState, expectedState[index],
        `The playState of animation [${ index }] should be ${ expectedState[index] }`);
--- a/devtools/client/inspector/animation/test/browser_animation_rewind-button.js
+++ b/devtools/client/inspector/animation/test/browser_animation_rewind-button.js
@@ -4,17 +4,17 @@
 "use strict";
 
 // Test for following RewindButton component:
 // * element existence
 // * make animations to rewind to zero
 // * the state should be always paused after rewinding
 
 add_task(async function() {
-  await addTab(URL_ROOT + "doc_custom_playback_rate.html");
+  await addTab(URL_ROOT + "doc_multi_timings.html");
   const { animationInspector, panel } = await openAnimationInspector();
 
   info("Checking button existence");
   ok(panel.querySelector(".rewind-button"), "Rewind button should exist");
 
   info("Checking rewind button makes animations to rewind to zero");
   await clickOnRewindButton(animationInspector, panel);
   assertAnimationsCurrentTime(animationInspector, 0);
--- a/devtools/client/inspector/animation/utils/timescale.js
+++ b/devtools/client/inspector/animation/utils/timescale.js
@@ -21,30 +21,38 @@ class TimeScale {
     if (!animations.every(animation => animation.state.createdTime)) {
       // Backward compatibility for createdTime.
       return this._initializeWithoutCreatedTime(animations);
     }
 
     let animationsCurrentTime = -Number.MAX_VALUE;
     let minStartTime = Infinity;
     let maxEndTime = 0;
+    let zeroPositionTime = 0;
 
     for (const animation of animations) {
       const {
         createdTime,
+        createdCurrentTime,
         currentTime,
         delay,
         duration,
         endDelay = 0,
         iterationCount,
         playbackRate,
       } = animation.state;
 
       const toRate = v => v / playbackRate;
-      const startTime = createdTime + toRate(Math.min(delay, 0));
+      const startPositionTime = createdTime + toRate(createdCurrentTime);
+      let startTime = createdTime + toRate(Math.min(delay, 0));
+      // The created current time shorter than start time, this mean this animation
+      // doesn't start the animation due to other animation factors.
+      if (startPositionTime < startTime) {
+        startTime = startPositionTime;
+      }
       let endTime = 0;
 
       if (duration === Infinity) {
         // Set endTime so as to enable the scrubber with keeping the consinstency of UI
         // even the duration was Infinity. In case of delay is longer than zero, handle
         // the graph duration as double of the delay amount. In case of no delay, handle
         // the duration as 1ms which is short enough so as to make the scrubber movable
         // and the limited duration is prioritized.
@@ -55,21 +63,23 @@ class TimeScale {
                          duration * (iterationCount || 1) +
                          Math.max(endDelay, 0));
       }
 
       minStartTime = Math.min(minStartTime, startTime);
       maxEndTime = Math.max(maxEndTime, endTime);
       animationsCurrentTime =
         Math.max(animationsCurrentTime, createdTime + toRate(currentTime));
+      zeroPositionTime = Math.min(zeroPositionTime, (startTime - createdTime));
     }
 
     this.minStartTime = minStartTime;
     this.maxEndTime = maxEndTime;
     this.currentTime = animationsCurrentTime;
+    this.zeroPositionTime = -1 * zeroPositionTime;
   }
 
   /**
    * Same as the constructor but doesn't use the animation's createdTime property
    * which has only been added in FF62, for backward compatbility reasons.
    *
    * @param {Array} animations
    */
@@ -110,45 +120,42 @@ class TimeScale {
       const endTime = previousStartTime + length;
       this.maxEndTime = Math.max(this.maxEndTime, endTime);
 
       this.documentCurrentTime = Math.max(this.documentCurrentTime, documentCurrentTime);
     }
   }
 
   /**
-   * Convert a distance in % to a time, in the current time scale.
-   *
-   * @param {Number} distance
-   * @return {Number}
-   */
-  distanceToTime(distance) {
-    return this.minStartTime + (this.getDuration() * distance / 100);
-  }
-
-  /**
-   * Convert a distance in % to a time, in the current time scale.
-   * The time will be relative to the current minimum start time.
+   *  Convert a distance in % to a time, in the current time scale. The time
+   *  will be relative to the zero position time.
+   *  i.e., If zeroPositionTime will be negative and specified time is shorter
+   *  than the absolute value of zero position time, relative time will be
+   *  negative time.
    *
    * @param {Number} distance
    * @return {Number}
    */
   distanceToRelativeTime(distance) {
-    const time = this.distanceToTime(distance);
-    return time - this.minStartTime;
+    return (this.getDuration() * distance / 100)
+           - this.zeroPositionTime;
   }
 
   /**
    * Depending on the time scale, format the given time as milliseconds or
    * seconds.
    *
    * @param {Number} time
    * @return {String} The formatted time string.
    */
   formatTime(time) {
+    // Ignore negative zero
+    if (Math.abs(time) < (1 / 1000)) {
+      time = 0.0;
+    }
     // Format in milliseconds if the total duration is short enough.
     if (this.getDuration() <= TIME_FORMAT_MAX_DURATION_IN_MS) {
       return getFormatStr("timeline.timeGraduationLabel", time.toFixed(0));
     }
 
     // Otherwise format in seconds.
     return getFormatStr("player.timeLabel", (time / 1000).toFixed(1));
   }
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -83,16 +83,17 @@ var AnimationPlayerActor = protocol.Acto
     if (this.isPseudoElement) {
       this.observer.observe(this.node.parentElement,
                             {animations: true, subtree: true});
     } else {
       this.observer.observe(this.node, {animations: true});
     }
 
     this.createdTime = createdTime;
+    this.createdCurrentTime = player.currentTime;
   },
 
   destroy: function() {
     // Only try to disconnect the observer if it's not already dead (i.e. if the
     // container view hasn't navigated since).
     if (this.observer && !Cu.isDeadWrapper(this.observer)) {
       this.observer.disconnect();
     }
@@ -331,16 +332,18 @@ var AnimationPlayerActor = protocol.Acto
             .some(propState => propState.runningOnCompositor),
       propertyState: this.getPropertiesCompositorStatus(),
       // The document timeline's currentTime is being sent along too. This is
       // not strictly related to the node's animationPlayer, but is useful to
       // know the current time of the animation with respect to the document's.
       documentCurrentTime: this.node.ownerDocument.timeline.currentTime,
       // The time which this animation created.
       createdTime: this.createdTime,
+      // The time which an animation's current time when this animation has created.
+      createdCurrentTime: this.createdCurrentTime,
     };
   },
 
   /**
    * Get the current state of the AnimationPlayer (currentTime, playState, ...).
    * Note that the initial state is returned as the form of this actor when it
    * is initialized.
    * This protocol method only returns a trimed down version of this state in
--- a/devtools/shared/fronts/animation.js
+++ b/devtools/shared/fronts/animation.js
@@ -67,16 +67,17 @@ const AnimationPlayerFront = FrontClassW
       easing: this._form.easing,
       fill: this._form.fill,
       direction: this._form.direction,
       animationTimingFunction: this._form.animationTimingFunction,
       isRunningOnCompositor: this._form.isRunningOnCompositor,
       propertyState: this._form.propertyState,
       documentCurrentTime: this._form.documentCurrentTime,
       createdTime: this._form.createdTime,
+      createdCurrentTime: this._form.createdCurrentTime,
     };
   },
 
   /**
    * Executed when the AnimationPlayerActor emits a "changed" event. Used to
    * update the local knowledge of the state.
    */
   onChanged: preEvent("changed", function(partialState) {