Bug 1253494 part 1 - Implement endDelay representation in the animation inspector r=pbro
authorRyo Motozawa <motozawa@mozilla-japan.org>
Wed, 23 Mar 2016 10:39:49 +0900
changeset 290552 170088059bf79fd3f43eb3ec77b062f6e6ba9e99
parent 290551 604371ad19b0b60ca45f801b846e67f9f71f4264
child 290553 214a78b9334d7bb05ecc9e26d8ed1190318418b2
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro
bugs1253494
milestone48.0a1
Bug 1253494 part 1 - Implement endDelay representation in the animation inspector r=pbro MozReview-Commit-ID: CxUvQRu6Yrn
devtools/client/animationinspector/components/animation-time-block.js
devtools/client/animationinspector/utils.js
devtools/client/locales/en-US/animationinspector.properties
devtools/client/themes/animationinspector.css
devtools/server/actors/animation.js
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -49,17 +49,17 @@ AnimationTimeBlock.prototype = {
     this.unrender();
 
     this.animation = animation;
     let {state} = this.animation;
 
     // Create a container element to hold the delay and iterations.
     // It is positioned according to its delay (divided by the playbackrate),
     // and its width is according to its duration (divided by the playbackrate).
-    let {x, iterationW, delayX, delayW, negativeDelayW} =
+    let {x, iterationW, delayX, delayW, negativeDelayW, endDelayX, endDelayW} =
       TimeScale.getAnimationDimensions(animation);
 
     // background properties for .iterations element
     let backgroundIterations = TimeScale.getIterationsBackgroundData(animation);
 
     createNode({
       parent: this.containerEl,
       attributes: {
@@ -101,38 +101,59 @@ AnimationTimeBlock.prototype = {
         parent: this.containerEl,
         attributes: {
           "class": "delay" + (state.delay < 0 ? " negative" : ""),
           "style": `left:${delayX}%;
                     width:${delayW}%;`
         }
       });
     }
+
+    // endDelay
+    if (state.endDelay) {
+      createNode({
+        parent: this.containerEl,
+        attributes: {
+          "class": "end-delay" + (state.endDelay < 0 ? " negative" : ""),
+          "style": `left:${endDelayX}%;
+                    width:${endDelayW}%;`
+        }
+      });
+    }
   },
 
   getTooltipText: function(state) {
     let getTime = time => L10N.getFormatStr("player.timeLabel",
                             L10N.numberWithDecimals(time / 1000, 2));
 
     let text = "";
 
     // Adding the name.
     text += getFormattedAnimationTitle({state});
     text += "\n";
 
     // Adding the delay.
-    text += L10N.getStr("player.animationDelayLabel") + " ";
-    text += getTime(state.delay);
-    text += "\n";
+    if (state.delay) {
+      text += L10N.getStr("player.animationDelayLabel") + " ";
+      text += getTime(state.delay);
+      text += "\n";
+    }
 
     // Adding the duration.
     text += L10N.getStr("player.animationDurationLabel") + " ";
     text += getTime(state.duration);
     text += "\n";
 
+    // Adding the endDelay.
+    if (state.endDelay) {
+      text += L10N.getStr("player.animationEndDelayLabel") + " ";
+      text += getTime(state.endDelay);
+      text += "\n";
+    }
+
     // Adding the iteration count (the infinite symbol, or an integer).
     if (state.iterationCount !== 1) {
       text += L10N.getStr("player.animationIterationCountLabel") + " ";
       text += state.iterationCount ||
               L10N.getStr("player.infiniteIterationCountText");
       text += "\n";
     }
 
--- a/devtools/client/animationinspector/utils.js
+++ b/devtools/client/animationinspector/utils.js
@@ -194,30 +194,42 @@ var TimeScale = {
   minStartTime: Infinity,
   maxEndTime: 0,
 
   /**
    * Add a new animation to time scale.
    * @param {Object} state A PlayerFront.state object.
    */
   addAnimation: function(state) {
-    let {previousStartTime, delay, duration,
+    let {previousStartTime, delay, duration, endDelay,
          iterationCount, playbackRate} = state;
 
+    endDelay = typeof endDelay === "undefined" ? 0 : endDelay;
+    let toRate = v => v / playbackRate;
+    let minZero = v => Math.max(v, 0);
+    let rateRelativeDuration =
+      toRate(duration * (!iterationCount ? 1 : iterationCount));
     // Negative-delayed animations have their startTimes set such that we would
     // be displaying the delay outside the time window if we didn't take it into
     // account here.
-    let relevantDelay = delay < 0 ? delay / playbackRate : 0;
+    let relevantDelay = delay < 0 ? toRate(delay) : 0;
     previousStartTime = previousStartTime || 0;
 
-    this.minStartTime = Math.min(this.minStartTime,
-                                 previousStartTime + relevantDelay);
-    let length = (delay / playbackRate) +
-                 ((duration / playbackRate) *
-                  (!iterationCount ? 1 : iterationCount));
+    let startTime = toRate(minZero(delay)) +
+                    rateRelativeDuration +
+                    endDelay;
+    this.minStartTime = Math.min(
+      this.minStartTime,
+      previousStartTime +
+      relevantDelay +
+      Math.min(startTime, 0)
+    );
+    let length = toRate(delay) +
+                 rateRelativeDuration +
+                 toRate(minZero(endDelay));
     let endTime = previousStartTime + length;
     this.maxEndTime = Math.max(this.maxEndTime, endTime);
   },
 
   /**
    * Reset the current time scale.
    */
   reset: function() {
@@ -289,31 +301,37 @@ var TimeScale = {
    * animation in the timeline.
    */
   getAnimationDimensions: function({state}) {
     let start = state.previousStartTime || 0;
     let duration = state.duration;
     let rate = state.playbackRate;
     let count = state.iterationCount;
     let delay = state.delay || 0;
+    let endDelay = state.endDelay || 0;
 
     // The start position.
     let x = this.startTimeToDistance(start + (delay / rate));
     // The width for a single iteration.
     let w = this.durationToDistance(duration / rate);
     // The width for all iterations.
     let iterationW = w * (count || 1);
     // The start position of the delay.
     let delayX = delay < 0 ? x : this.startTimeToDistance(start);
     // The width of the delay.
     let delayW = this.durationToDistance(Math.abs(delay) / rate);
     // The width of the delay if it is negative, 0 otherwise.
     let negativeDelayW = delay < 0 ? delayW : 0;
+    // The width of the endDelay.
+    let endDelayW = this.durationToDistance(Math.abs(endDelay) / rate);
+    // The start position of the endDelay.
+    let endDelayX = endDelay < 0 ? x + w - endDelayW : x + w;
 
-    return {x, w, iterationW, delayX, delayW, negativeDelayW};
+    return {x, w, iterationW, delayX, delayW, negativeDelayW,
+            endDelayX, endDelayW};
   },
 
   /**
    * Given an animation, get the background data for .iterations element.
    * This background represents iterationCount and iterationStart.
    * Returns three properties.
    * 1. size: x of background-size (%)
    * 2. position: x of background-position (%)
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -38,16 +38,21 @@ player.transitionNameLabel=Transition
 # displayed before the animation duration.
 player.animationDurationLabel=Duration:
 
 # LOCALIZATION NOTE (player.animationDelayLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the animation delay.
 player.animationDelayLabel=Delay:
 
+# LOCALIZATION NOTE (player.animationEndDelayLabel):
+# This string is displayed in each animation player widget. It is the label
+# displayed before the animation endDelay.
+player.animationEndDelayLabel=End delay:
+
 # LOCALIZATION NOTE (player.animationRateLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the animation playback rate.
 player.animationRateLabel=Playback rate:
 
 # LOCALIZATION NOTE (player.animationIterationCountLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the number of times the animation is set to repeat.
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -222,17 +222,17 @@ body {
   left: var(--timeline-sidebar-width);
   /* Leave the width of a marker right of a track so the 100% markers can be
      selected easily */
   right: var(--keyframes-marker-size);
   height: var(--timeline-animation-height);
 }
 
 .animation-timeline .scrubber-wrapper {
-  z-index: 1;
+  z-index: 2;
   pointer-events: none;
   height: 100%;
 }
 
 .animation-timeline .scrubber {
   position: absolute;
   height: 100%;
   width: 0;
@@ -369,16 +369,17 @@ body {
   position: absolute;
   color: var(--theme-selection-color);
   height: 100%;
   display: flex;
   align-items: center;
   padding: 0 2px;
   box-sizing: border-box;
   --fast-track-icon-width: 12px;
+  z-index: 1;
 }
 
 .animation-timeline .animation .name div {
   /* Flex items don't support text-overflow, so a child div is used */
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
 }
@@ -391,39 +392,53 @@ body {
   /* Animations running on the compositor have the fast-track background image*/
   content: "";
   display: block;
   position: absolute;
   top: 0;
   right: 0;
   height: 100%;
   width: var(--fast-track-icon-width);
+  z-index: 1;
 
   background-image: url("images/animation-fast-track.svg");
   background-repeat: no-repeat;
   background-position: center;
 }
 
-.animation-timeline .animation .delay {
+.animation-timeline .animation .delay,
+.animation-timeline .animation .end-delay {
   position: absolute;
   height: 100%;
-
   border: 1px solid var(--timeline-border-color);
   box-sizing: border-box;
+}
+
+.animation-timeline .animation .delay {
   border-width: 1px 0 1px 1px;
-
   background-image: repeating-linear-gradient(45deg,
                                               transparent,
                                               transparent 1px,
                                               var(--theme-selection-color) 1px,
                                               var(--theme-selection-color) 4px);
   background-color: var(--timeline-border-color);
 }
 
-.animation-timeline .animation .delay.negative {
+.animation-timeline .animation .end-delay {
+  border-width: 1px 1px 1px 0;
+  background-image: repeating-linear-gradient(
+                      -45deg,
+                      transparent,
+                      transparent 3px,
+                      var(--timeline-border-color) 3px,
+                      var(--timeline-border-color) 4px);
+}
+
+.animation-timeline .animation .delay.negative,
+.animation-timeline .animation .end-delay.negative {
   /* Negative delays are displayed on top of the animation, so they need a
      right border. Whereas normal delays are displayed just before the
      animation, so there's already the animation's left border that serves as
      a separation. */
   border-width: 1px;
 }
 
 /* Animation target node gutter, contains a preview of the dom node */
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -177,16 +177,24 @@ var AnimationPlayerActor = ActorClass({
    * Get the animation delay from this player, in milliseconds.
    * @return {Number}
    */
   getDelay: function() {
     return this.player.effect.getComputedTiming().delay;
   },
 
   /**
+   * Get the animation endDelay from this player, in milliseconds.
+   * @return {Number}
+   */
+  getEndDelay: function() {
+    return this.player.effect.getComputedTiming().endDelay;
+  },
+
+  /**
    * Get the animation iteration count for this player. That is, how many times
    * is the animation scheduled to run.
    * @return {Number} The number of iterations, or null if the animation repeats
    * infinitely.
    */
   getIterationCount: function() {
     let iterations = this.player.effect.getComputedTiming().iterations;
     return iterations === "Infinity" ? null : iterations;
@@ -224,16 +232,17 @@ var AnimationPlayerActor = ActorClass({
       startTime: this.player.startTime,
       previousStartTime: this.previousStartTime,
       currentTime: this.player.currentTime,
       playState: this.player.playState,
       playbackRate: this.player.playbackRate,
       name: this.getName(),
       duration: this.getDuration(),
       delay: this.getDelay(),
+      endDelay: this.getEndDelay(),
       iterationCount: this.getIterationCount(),
       iterationStart: this.getIterationStart(),
       // animation is hitting the fast path or not. Returns false whenever the
       // animation is paused as it is taken off the compositor then.
       isRunningOnCompositor: this.player.isRunningOnCompositor,
       // 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.
@@ -298,17 +307,18 @@ var AnimationPlayerActor = ActorClass({
       if (hasCurrentAnimation(changedAnimations)) {
         // Only consider the state has having changed if any of delay, duration,
         // iterationcount or iterationStart has changed (for now at least).
         let newState = this.getState();
         let oldState = this.currentState;
         hasChanged = newState.delay !== oldState.delay ||
                      newState.iterationCount !== oldState.iterationCount ||
                      newState.iterationStart !== oldState.iterationStart ||
-                     newState.duration !== oldState.duration;
+                     newState.duration !== oldState.duration ||
+                     newState.endDelay !== oldState.endDelay;
         break;
       }
     }
 
     if (hasChanged) {
       events.emit(this, "changed", this.getCurrentState());
     }
   },
@@ -451,16 +461,17 @@ var AnimationPlayerFront = FrontClass(An
       startTime: this._form.startTime,
       previousStartTime: this._form.previousStartTime,
       currentTime: this._form.currentTime,
       playState: this._form.playState,
       playbackRate: this._form.playbackRate,
       name: this._form.name,
       duration: this._form.duration,
       delay: this._form.delay,
+      endDelay: this._form.endDelay,
       iterationCount: this._form.iterationCount,
       iterationStart: this._form.iterationStart,
       isRunningOnCompositor: this._form.isRunningOnCompositor,
       documentCurrentTime: this._form.documentCurrentTime
     };
   },
 
   /**