Bug 1406285 - Part 7: Implement effect timing graph. r=gl
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Thu, 18 Jan 2018 12:46:38 +0900
changeset 454263 92156bb3ceb69b3eeedcceb4857ac52b86da2053
parent 454262 9226b8d3ed05274de973c069b400d1dd8dc9e223
child 454264 2e4538c1d08effdbdf02fce6f29d087b04793cbd
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1406285
milestone59.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 1406285 - Part 7: Implement effect timing graph. r=gl MozReview-Commit-ID: DIrt9PdY2Nd
devtools/client/inspector/animation/components/graph/EffectTimingPath.js
devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
devtools/client/inspector/animation/components/graph/moz.build
devtools/client/inspector/animation/utils/graph-helper.js
devtools/client/themes/animation.css
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/graph/EffectTimingPath.js
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+const { SummaryGraphHelper, toPathString } = require("../../utils/graph-helper");
+const TimingPath = require("./TimingPath");
+
+class EffectTimingPath extends TimingPath {
+  static get propTypes() {
+    return {
+      animation: PropTypes.object.isRequired,
+      durationPerPixel: PropTypes.number.isRequired,
+      simulateAnimation: PropTypes.func.isRequired,
+      totalDuration: PropTypes.number.isRequired,
+    };
+  }
+
+  render() {
+    const {
+      animation,
+      durationPerPixel,
+      simulateAnimation,
+      totalDuration,
+    } = this.props;
+
+    const { state } = animation;
+    const effectTiming = Object.assign({}, state, {
+      iterations: state.iterationCount ? state.iterationCount : Infinity
+    });
+
+    const simulatedAnimation = simulateAnimation(null, effectTiming, false);
+    const endTime = simulatedAnimation.effect.getComputedTiming().endTime;
+
+    const getValueFunc = time => {
+      if (time < 0) {
+        return { x: time, y: 0 };
+      }
+
+      simulatedAnimation.currentTime = time < endTime ? time : endTime;
+      return Math.max(simulatedAnimation.effect.getComputedTiming().progress, 0);
+    };
+
+    const toPathStringFunc = segments => {
+      const firstSegment = segments[0];
+      let pathString = `M${ firstSegment.x },0 `;
+      pathString += toPathString(segments);
+      const lastSegment = segments[segments.length - 1];
+      pathString += `L${ lastSegment.x },0`;
+      return pathString;
+    };
+
+    const helper = new SummaryGraphHelper(state, null,
+                                          totalDuration, durationPerPixel,
+                                          getValueFunc, toPathStringFunc);
+    const offset = state.previousStartTime ? state.previousStartTime : 0;
+
+    return dom.g(
+      {
+        className: "animation-effect-timing-path",
+        transform: `translate(${ offset })`
+      },
+      super.renderGraph(state, helper)
+    );
+  }
+}
+
+module.exports = EffectTimingPath;
--- a/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
+++ b/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
@@ -5,16 +5,19 @@
 "use strict";
 
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 
 const ComputedTimingPath = createFactory(require("./ComputedTimingPath"));
+const EffectTimingPath = createFactory(require("./EffectTimingPath"));
+const { DEFAULT_GRAPH_HEIGHT } = require("../../utils/graph-helper");
+
 // Minimum opacity for semitransparent fill color for keyframes's easing graph.
 const MIN_KEYFRAMES_EASING_OPACITY = 0.5;
 
 class SummaryGraphPath extends PureComponent {
   static get propTypes() {
     return {
       animation: PropTypes.object.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
@@ -158,27 +161,39 @@ class SummaryGraphPath extends PureCompo
     const keyframesList =
       this.getOffsetAndEasingOnlyKeyframes(animation.animatedPropertyMap);
     const opacity = Math.max(1 / keyframesList.length, MIN_KEYFRAMES_EASING_OPACITY);
 
     return dom.svg(
       {
         className: "animation-summary-graph-path",
         preserveAspectRatio: "none",
-        viewBox: `${ startTime } -1 ${ totalDuration } 1`
+        viewBox: `${ startTime } -${ DEFAULT_GRAPH_HEIGHT } `
+                 + `${ totalDuration } ${ DEFAULT_GRAPH_HEIGHT }`,
       },
       keyframesList.map(keyframes =>
         ComputedTimingPath(
           {
             animation,
             durationPerPixel,
             keyframes,
             opacity,
             simulateAnimation,
             totalDuration,
           }
         )
+      ),
+      animation.state.easing !== "linear" ?
+      EffectTimingPath(
+        {
+          animation,
+          durationPerPixel,
+          simulateAnimation,
+          totalDuration,
+        }
       )
+      :
+      null
     );
   }
 }
 
 module.exports = SummaryGraphPath;
--- a/devtools/client/inspector/animation/components/graph/moz.build
+++ b/devtools/client/inspector/animation/components/graph/moz.build
@@ -1,10 +1,11 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'ComputedTimingPath.js',
+    'EffectTimingPath.js',
     'SummaryGraph.js',
     'SummaryGraphPath.js',
     'TimingPath.js'
 )
--- a/devtools/client/inspector/animation/utils/graph-helper.js
+++ b/devtools/client/inspector/animation/utils/graph-helper.js
@@ -2,25 +2,28 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // BOUND_EXCLUDING_TIME should be less than 1ms and is used to exclude start
 // and end bounds when dividing  duration in createPathSegments.
 const BOUND_EXCLUDING_TIME = 0.001;
+// We define default graph height since if the height of viewport in SVG is
+// too small (e.g. 1), vector-effect may not be able to calculate correctly.
+const DEFAULT_GRAPH_HEIGHT = 100;
 // DEFAULT_MIN_PROGRESS_THRESHOLD shoud be between more than 0 to 1.
 const DEFAULT_MIN_PROGRESS_THRESHOLD = 0.1;
 // In the createPathSegments function, an animation duration is divided by
 // DURATION_RESOLUTION in order to draw the way the animation progresses.
 // But depending on the timing-function, we may be not able to make the graph
 // smoothly progress if this resolution is not high enough.
 // So, if the difference of animation progress between 2 divisions is more than
-// DEFAULT_MIN_PROGRESS_THRESHOLD, then createPathSegments re-divides
-// by DURATION_RESOLUTION.
+// DEFAULT_MIN_PROGRESS_THRESHOLD * DEFAULT_GRAPH_HEIGHT, then createPathSegments
+// re-divides by DURATION_RESOLUTION.
 // DURATION_RESOLUTION shoud be integer and more than 2.
 const DURATION_RESOLUTION = 4;
 
 /**
  * The helper class for creating summary graph.
  */
 class SummaryGraphHelper {
   /**
@@ -40,17 +43,18 @@ class SummaryGraphHelper {
    *        e.g. time => { return 1.0 };
    * @param {Function} toPathStringFunc
    *        Which returns a path string for 'd' attribute for <path> from given segments.
    */
   constructor(state, keyframes, totalDuration, minSegmentDuration,
               getValueFunc, toPathStringFunc) {
     this.totalDuration = totalDuration;
     this.minSegmentDuration = minSegmentDuration;
-    this.minProgressThreshold = getPreferredProgressThreshold(state, keyframes);
+    this.minProgressThreshold =
+      getPreferredProgressThreshold(state, keyframes) * DEFAULT_GRAPH_HEIGHT;
     this.durationResolution = getPreferredDurationResolution(keyframes);
     this.getValue = getValueFunc;
     this.toPathString = toPathStringFunc;
 
     this.getSegment = this.getSegment.bind(this);
   }
 
   /**
@@ -74,17 +78,17 @@ class SummaryGraphHelper {
    * Return a coordinate as a graph segment at given time.
    *
    * @param {Number} time
    * @return {Object}
    *         { x: Number, y: Number }
    */
   getSegment(time) {
     const value = this.getValue(time);
-    return { x: time, y: value };
+    return { x: time, y: value * DEFAULT_GRAPH_HEIGHT };
   }
 }
 
 /**
  * Create the path segments from given parameters.
  *
  * @param {Number} startTime
  *        Starting time of animation.
@@ -230,10 +234,11 @@ function getStepsOrFramesCount(easing) {
 function toPathString(segments) {
   let pathString = "";
   segments.forEach(segment => {
     pathString += `L${ segment.x },${ segment.y } `;
   });
   return pathString;
 }
 
+module.exports.DEFAULT_GRAPH_HEIGHT = DEFAULT_GRAPH_HEIGHT;
 exports.SummaryGraphHelper = SummaryGraphHelper;
 exports.toPathString = toPathString;
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -53,24 +53,27 @@
 }
 
 .animation-item:nth-child(2n+1) {
   background-color: var(--animation-even-background-color);
 }
 
 .animation-item.cssanimation {
   --computed-timing-graph-color: var(--theme-contrast-background);
+  --effect-timing-graph-color: var(--theme-highlight-lightorange);
 }
 
 .animation-item.csstransition {
   --computed-timing-graph-color: var(--theme-highlight-blue);
+  --effect-timing-graph-color: var(--theme-highlight-bluegrey);
 }
 
 .animation-item.scriptanimation {
   --computed-timing-graph-color: var(--theme-graphs-green);
+  --effect-timing-graph-color: var(--theme-highlight-green);
 }
 
 /* Animation Target */
 .animation-target {
   align-items: center;
   display: flex;
   height: 100%;
   padding-left: 4px;
@@ -98,16 +101,28 @@
   vector-effect: non-scaling-stroke;
   transform: scale(1, -1);
 }
 
 .animation-computed-timing-path path.infinity:nth-child(n+2) {
   opacity: 0.3;
 }
 
+.animation-effect-timing-path path {
+  fill: none;
+  stroke: var(--effect-timing-graph-color);
+  stroke-dasharray: 2px 2px;
+  transform: scale(1, -1);
+  vector-effect: non-scaling-stroke;
+}
+
+.animation-effect-timing-path path.infinity:nth-child(n+2) {
+  opacity: 0.3;
+}
+
 /* No Animation Panel */
 .animation-error-message {
   overflow: auto;
 }
 
 .animation-error-message > p {
   white-space: pre;
 }