Bug 1454392 - Part 2: Apply createdTime to summary graph so as keeping proper graph. r?pbro draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Mon, 21 May 2018 18:30:27 +0900
changeset 797667 cd77d04e995478df825ce60c592237cd725e1f12
parent 797666 2923373cdcddcfd9310594faf9b5673676d756cf
child 797668 70ea911ace699ca5709c56a5108ca7b22b1c5d98
push id110532
push userbmo:dakatsuka@mozilla.com
push dateMon, 21 May 2018 10:23:12 +0000
reviewerspbro
bugs1454392
milestone62.0a1
Bug 1454392 - Part 2: Apply createdTime to summary graph so as keeping proper graph. r?pbro MozReview-Commit-ID: 4udVcj2Zy5C
devtools/client/inspector/animation/components/graph/DelaySign.js
devtools/client/inspector/animation/components/graph/EndDelaySign.js
devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
devtools/client/inspector/animation/utils/timescale.js
--- a/devtools/client/inspector/animation/components/graph/DelaySign.js
+++ b/devtools/client/inspector/animation/components/graph/DelaySign.js
@@ -17,26 +17,31 @@ class DelaySign extends PureComponent {
   }
 
   render() {
     const {
       animation,
       timeScale,
     } = this.props;
     const {
+      createdTime,
+      delay,
       fill,
       playbackRate,
-      previousStartTime = 0,
     } = animation.state;
 
-    const delay = animation.state.delay / playbackRate;
-    const startTime =
-      previousStartTime - timeScale.minStartTime + (delay < 0 ? delay : 0);
+    const toRate = v => v / playbackRate;
+    // If createdTime is not defined (which happens when connected to server older
+    // than FF62), use previousStartTime instead. See bug 1454392
+    const baseTime = typeof createdTime === "undefined"
+                       ? (animation.state.previousStartTime || 0)
+                       : createdTime;
+    const startTime = baseTime + toRate(Math.min(delay, 0)) - timeScale.minStartTime;
     const offset = startTime / timeScale.getDuration() * 100;
-    const width = Math.abs(delay) / timeScale.getDuration() * 100;
+    const width = Math.abs(toRate(delay)) / timeScale.getDuration() * 100;
 
     return dom.div(
       {
         className: "animation-delay-sign" +
                    (delay < 0 ? " negative" : "") +
                    (fill === "both" || fill === "backwards" ? " fill" : ""),
         style: {
           width: `${ width }%`,
--- a/devtools/client/inspector/animation/components/graph/EndDelaySign.js
+++ b/devtools/client/inspector/animation/components/graph/EndDelaySign.js
@@ -17,30 +17,35 @@ class EndDelaySign extends PureComponent
   }
 
   render() {
     const {
       animation,
       timeScale,
     } = this.props;
     const {
+      createdTime,
       delay,
       duration,
+      endDelay,
       fill,
       iterationCount,
       playbackRate,
-      previousStartTime = 0,
     } = animation.state;
 
-    const endDelay = animation.state.endDelay / playbackRate;
-    const startTime = previousStartTime - timeScale.minStartTime;
-    const endTime =
-      (duration * iterationCount + delay) / playbackRate + (endDelay < 0 ? endDelay : 0);
+    const toRate = v => v / playbackRate;
+    // If createdTime is not defined (which happens when connected to server older
+    // than FF62), use previousStartTime instead. See bug 1454392
+    const baseTime = typeof createdTime === "undefined"
+                       ? (animation.state.previousStartTime || 0)
+                       : createdTime;
+    const startTime = baseTime - timeScale.minStartTime;
+    const endTime = toRate(delay + duration * iterationCount + Math.min(endDelay, 0));
     const offset = (startTime + endTime) / timeScale.getDuration() * 100;
-    const width = Math.abs(endDelay) / timeScale.getDuration() * 100;
+    const width = Math.abs(toRate(endDelay)) / timeScale.getDuration() * 100;
 
     return dom.div(
       {
         className: "animation-end-delay-sign" +
                    (endDelay < 0 ? " negative" : "") +
                    (fill === "both" || fill === "forwards" ? " fill" : ""),
         style: {
           width: `${ width }%`,
--- a/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
+++ b/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
@@ -116,20 +116,16 @@ class SummaryGraphPath extends Component
       });
     }).map(keyframes => {
       return keyframes.map(keyframe => {
         return { easing: keyframe.easing, offset: keyframe.offset };
       });
     });
   }
 
-  getTotalDuration(animation, timeScale) {
-    return animation.state.playbackRate * timeScale.getDuration();
-  }
-
   /**
    * Return true if given keyframes have same length, offset and easing.
    *
    * @param {Array} keyframes1
    * @param {Array} keyframes2
    * @return {Boolean} true: equals
    */
   isOffsetAndEasingKeyframesEqual(keyframes1, keyframes2) {
@@ -167,17 +163,17 @@ class SummaryGraphPath extends Component
       // in the meantime.
       console.error(e);
       return;
     }
 
     const keyframesList = this.getOffsetAndEasingOnlyKeyframes(animatedPropertyMap);
 
     const thisEl = ReactDOM.findDOMNode(this);
-    const totalDuration = this.getTotalDuration(animation, timeScale);
+    const totalDuration = timeScale.getDuration() * animation.state.playbackRate;
     const durationPerPixel = totalDuration / thisEl.parentNode.clientWidth;
 
     this.setState(
       {
         durationPerPixel,
         isStateUpdating: false,
         keyframesList
       }
@@ -194,20 +190,29 @@ class SummaryGraphPath extends Component
     }
 
     const {
       animation,
       simulateAnimation,
       timeScale,
     } = this.props;
 
-    const totalDuration = this.getTotalDuration(animation, timeScale);
-    const { playbackRate, previousStartTime = 0 } = animation.state;
+    const { createdTime, playbackRate } = animation.state;
+
+    // If createdTime is not defined (which happens when connected to server older
+    // than FF62), use previousStartTime instead. See bug 1454392
+    const baseTime = typeof createdTime === "undefined"
+                       ? (animation.state.previousStartTime || 0)
+                       : createdTime;
+    // Absorb the playbackRate in viewBox of SVG and offset of child path elements
+    // in order to each graph path components can draw without considering to the
+    // playbackRate.
+    const offset = baseTime * playbackRate;
     const startTime = timeScale.minStartTime * playbackRate;
-    const offset = previousStartTime * playbackRate;
+    const totalDuration = timeScale.getDuration() * playbackRate;
     const opacity = Math.max(1 / keyframesList.length, MIN_KEYFRAMES_EASING_OPACITY);
 
     return dom.svg(
       {
         className: "animation-summary-graph-path",
         preserveAspectRatio: "none",
         viewBox: `${ startTime } -${ DEFAULT_GRAPH_HEIGHT } `
                  + `${ totalDuration } ${ DEFAULT_GRAPH_HEIGHT }`,
--- a/devtools/client/inspector/animation/utils/timescale.js
+++ b/devtools/client/inspector/animation/utils/timescale.js
@@ -10,71 +10,96 @@ const { getFormatStr } = require("./l10n
 // TIME_FORMAT_MAX_DURATION_IN_MS, the text which expresses time is in milliseconds,
 // and seconds otherwise. Use in formatTime function.
 const TIME_FORMAT_MAX_DURATION_IN_MS = 4000;
 
 /**
  * TimeScale object holds the total duration, start time and end time information for all
  * animations which should be displayed, and is used to calculate the displayed area for
  * each animation.
- *
- * For the helper to know how to convert, it needs to know all the animations.
- * Whenever a new animation is added to the panel, addAnimation(state) should be
- * called.
  */
 class TimeScale {
   constructor(animations) {
+    if (!animations.every(animation => animation.state.createdTime)) {
+      // Backward compatibility for createdTime.
+      return this._initializeWithoutCreatedTime(animations);
+    }
+
     this.minStartTime = Infinity;
     this.maxEndTime = 0;
     this.documentCurrentTime = 0;
 
     for (const animation of animations) {
-      this.addAnimation(animation.state);
+      const {
+        createdTime,
+        delay,
+        documentCurrentTime,
+        duration,
+        endDelay = 0,
+        iterationCount,
+        playbackRate,
+      } = animation.state;
+
+      const toRate = v => v / playbackRate;
+      const startTime = createdTime + toRate(Math.min(delay, 0));
+      const endTime = createdTime +
+                      toRate(delay +
+                             duration * (iterationCount || 1) +
+                             Math.max(endDelay, 0));
+      this.minStartTime = Math.min(this.minStartTime, startTime);
+      this.maxEndTime = Math.max(this.maxEndTime, endTime);
+      this.documentCurrentTime = Math.max(this.documentCurrentTime, documentCurrentTime);
     }
   }
 
   /**
-   * Add a new animation to time scale.
+   * 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 {Object} state
-   *                 A PlayerFront.state object.
+   * @param {Array} animations
    */
-  addAnimation(state) {
-    let {
-      delay,
-      documentCurrentTime,
-      duration,
-      endDelay = 0,
-      iterationCount,
-      playbackRate,
-      previousStartTime = 0,
-    } = state;
+  _initializeWithoutCreatedTime(animations) {
+    this.minStartTime = Infinity;
+    this.maxEndTime = 0;
+    this.documentCurrentTime = 0;
+
+    for (const animation of animations) {
+      const {
+        delay,
+        documentCurrentTime,
+        duration,
+        endDelay = 0,
+        iterationCount,
+        playbackRate,
+        previousStartTime = 0,
+      } = animation.state;
 
-    const toRate = v => v / playbackRate;
-    const minZero = v => Math.max(v, 0);
-    const 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.
-    const relevantDelay = delay < 0 ? toRate(delay) : 0;
-    const startTime = toRate(minZero(delay)) +
-                      rateRelativeDuration +
-                      endDelay;
-    this.minStartTime = Math.min(
-      this.minStartTime,
-      previousStartTime +
-      relevantDelay +
-      Math.min(startTime, 0)
-    );
-    const length = toRate(delay) + rateRelativeDuration + toRate(minZero(endDelay));
-    const endTime = previousStartTime + length;
-    this.maxEndTime = Math.max(this.maxEndTime, endTime);
+      const toRate = v => v / playbackRate;
+      const minZero = v => Math.max(v, 0);
+      const 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.
+      const relevantDelay = delay < 0 ? toRate(delay) : 0;
+      const startTime = toRate(minZero(delay)) +
+                        rateRelativeDuration +
+                        endDelay;
+      this.minStartTime = Math.min(
+        this.minStartTime,
+        previousStartTime +
+        relevantDelay +
+        Math.min(startTime, 0)
+      );
+      const length = toRate(delay) + rateRelativeDuration + toRate(minZero(endDelay));
+      const endTime = previousStartTime + length;
+      this.maxEndTime = Math.max(this.maxEndTime, endTime);
 
-    this.documentCurrentTime = Math.max(this.documentCurrentTime, documentCurrentTime);
+      this.documentCurrentTime = Math.max(this.documentCurrentTime, documentCurrentTime);
+    }
   }
 
   /**
    * Convert a distance in % to a time, in the current time scale.
    *
    * @param {Number} distance
    * @return {Number}
    */