Backed out changeset 693f32f563d2 (bug 1383974) for failing devtools' devtools/client/animationinspector/test/browser_animation_detail_easings.js. r=backout
authorSebastian Hengst <archaeopteryx@coole-files.de>
Fri, 22 Sep 2017 18:12:42 +0200
changeset 382465 c2bbc9f00c6360fb66c6fcbbe7ab8e0e4727a2a6
parent 382464 5a9b45a4a60df1d7a5460a5286571fc79dd29b91
child 382466 d9b87e369bdbb8ae9307d744fb7b6111858cfc40
push id32558
push userkwierso@gmail.com
push dateFri, 22 Sep 2017 21:29:46 +0000
treeherdermozilla-central@61e58a7d800b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1383974
milestone58.0a1
backs out693f32f563d2c4610c4b6da682394dfe82d7fc38
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
Backed out changeset 693f32f563d2 (bug 1383974) for failing devtools' devtools/client/animationinspector/test/browser_animation_detail_easings.js. r=backout
devtools/client/animationinspector/components/animation-time-block.js
devtools/client/animationinspector/components/keyframes.js
devtools/client/animationinspector/graph-helper.js
devtools/client/themes/animationinspector.css
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -457,33 +457,33 @@ function renderGraph(parentEl, state, to
  * Render delay section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderDelay(parentEl, state, graphHelper) {
   const startSegment = graphHelper.getSegment(0);
   const endSegment = { x: state.delay, y: startSegment.y };
-  graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "delay-path");
+  graphHelper.appendPathElement(parentEl, [startSegment, endSegment], "delay-path");
 }
 
 /**
  * Render first iteration section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
  * @param {Number} firstSectionCount - Iteration count of first section.
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderFirstIteration(parentEl, state, mainIterationStartTime,
                               firstSectionCount, graphHelper) {
   const startTime = mainIterationStartTime;
   const endTime = startTime + firstSectionCount * state.duration;
   const segments = graphHelper.createPathSegments(startTime, endTime);
-  graphHelper.appendShapePath(parentEl, segments, "iteration-path");
+  graphHelper.appendPathElement(parentEl, segments, "iteration-path");
 }
 
 /**
  * Render middle iterations section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
  * @param {Number} firstSectionCount - Iteration count of first section.
@@ -494,17 +494,17 @@ function renderMiddleIterations(parentEl
                                 firstSectionCount, middleSectionCount,
                                 graphHelper) {
   const offset = mainIterationStartTime + firstSectionCount * state.duration;
   for (let i = 0; i < middleSectionCount; i++) {
     // Get the path segments of each iteration.
     const startTime = offset + i * state.duration;
     const endTime = startTime + state.duration;
     const segments = graphHelper.createPathSegments(startTime, endTime);
-    graphHelper.appendShapePath(parentEl, segments, "iteration-path");
+    graphHelper.appendPathElement(parentEl, segments, "iteration-path");
   }
 }
 
 /**
  * Render last iteration section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
@@ -515,17 +515,17 @@ function renderMiddleIterations(parentEl
  */
 function renderLastIteration(parentEl, state, mainIterationStartTime,
                              firstSectionCount, middleSectionCount,
                              lastSectionCount, graphHelper) {
   const startTime = mainIterationStartTime +
                       (firstSectionCount + middleSectionCount) * state.duration;
   const endTime = startTime + lastSectionCount * state.duration;
   const segments = graphHelper.createPathSegments(startTime, endTime);
-  graphHelper.appendShapePath(parentEl, segments, "iteration-path");
+  graphHelper.appendPathElement(parentEl, segments, "iteration-path");
 }
 
 /**
  * Render Infinity iterations.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
  * @param {Number} firstSectionCount - Iteration count of first section.
@@ -547,17 +547,17 @@ function renderInfinity(parentEl, state,
              Math.ceil(uncappedInfinityIterationCount));
 
   // Append first full iteration path.
   const firstStartTime =
     mainIterationStartTime + firstSectionCount * state.duration;
   const firstEndTime = firstStartTime + state.duration;
   const firstSegments =
     graphHelper.createPathSegments(firstStartTime, firstEndTime);
-  graphHelper.appendShapePath(parentEl, firstSegments, "iteration-path infinity");
+  graphHelper.appendPathElement(parentEl, firstSegments, "iteration-path infinity");
 
   // Append other iterations. We can copy first segments.
   const isAlternate = state.direction.match(/alternate/);
   for (let i = 1; i < infinityIterationCount; i++) {
     const startTime = firstStartTime + i * state.duration;
     let segments;
     if (isAlternate && i % 2) {
       // Copy as reverse.
@@ -565,34 +565,34 @@ function renderInfinity(parentEl, state,
         return { x: firstEndTime - segment.x + startTime, y: segment.y };
       });
     } else {
       // Copy as is.
       segments = firstSegments.map(segment => {
         return { x: segment.x - firstStartTime + startTime, y: segment.y };
       });
     }
-    graphHelper.appendShapePath(parentEl, segments, "iteration-path infinity copied");
+    graphHelper.appendPathElement(parentEl, segments, "iteration-path infinity copied");
   }
 }
 
 /**
  * Render endDelay section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
  * @param {Number} iterationCount - Whole iteration count.
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderEndDelay(parentEl, state,
                         mainIterationStartTime, iterationCount, graphHelper) {
   const startTime = mainIterationStartTime + iterationCount * state.duration;
   const startSegment = graphHelper.getSegment(startTime);
   const endSegment = { x: startTime + state.endDelay, y: startSegment.y };
-  graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "enddelay-path");
+  graphHelper.appendPathElement(parentEl, [startSegment, endSegment], "enddelay-path");
 }
 
 /**
  * Render forwards fill section.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Number} mainIterationStartTime - Starting time of main iteration.
  * @param {Number} iterationCount - Whole iteration count.
@@ -600,44 +600,45 @@ function renderEndDelay(parentEl, state,
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderForwardsFill(parentEl, state, mainIterationStartTime,
                             iterationCount, totalDuration, graphHelper) {
   const startTime = mainIterationStartTime + iterationCount * state.duration +
                       (state.endDelay > 0 ? state.endDelay : 0);
   const startSegment = graphHelper.getSegment(startTime);
   const endSegment = { x: totalDuration, y: startSegment.y };
-  graphHelper.appendShapePath(parentEl, [startSegment, endSegment], "fill-forwards-path");
+  graphHelper.appendPathElement(parentEl, [startSegment, endSegment],
+                                "fill-forwards-path");
 }
 
 /**
  * Render hidden progress of negative delay.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderNegativeDelayHiddenProgress(parentEl, state, graphHelper) {
   const startTime = state.delay;
   const endTime = 0;
   const segments =
     graphHelper.createPathSegments(startTime, endTime);
-  graphHelper.appendShapePath(parentEl, segments, "delay-path negative");
+  graphHelper.appendPathElement(parentEl, segments, "delay-path negative");
 }
 
 /**
  * Render hidden progress of negative endDelay.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Object} state - State of animation.
  * @param {Object} graphHelper - SummaryGraphHelper.
  */
 function renderNegativeEndDelayHiddenProgress(parentEl, state, graphHelper) {
   const endTime = state.delay + state.iterationCount * state.duration;
   const startTime = endTime + state.endDelay;
   const segments = graphHelper.createPathSegments(startTime, endTime);
-  graphHelper.appendShapePath(parentEl, segments, "enddelay-path negative");
+  graphHelper.appendPathElement(parentEl, segments, "enddelay-path negative");
 }
 
 /**
  * Create new keyframes object which has only offset and easing.
  * Also, the returned value has no duplication.
  * @param {Object} tracks - The value of AnimationsTimeline.getTracks().
  * @return {Array} keyframes list.
  */
--- a/devtools/client/animationinspector/components/keyframes.js
+++ b/devtools/client/animationinspector/components/keyframes.js
@@ -4,17 +4,17 @@
  * 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 {createNode, createSVGNode} =
   require("devtools/client/animationinspector/utils");
 const {ProgressGraphHelper, getPreferredKeyframesProgressThreshold} =
-  require("devtools/client/animationinspector/graph-helper.js");
+         require("devtools/client/animationinspector/graph-helper.js");
 
 // Counter for linearGradient ID.
 let LINEAR_GRADIENT_ID_COUNTER = 0;
 
 /**
  * UI component responsible for displaying a list of keyframes.
  * Also, shows a graphical graph for the animation progress of one iteration.
  */
@@ -50,44 +50,39 @@ Keyframes.prototype = {
         "preserveAspectRatio": "none"
       }
     });
 
     // This visual is only one iteration,
     // so we use animation.state.duration as total duration.
     const totalDuration = animation.state.duration;
 
+    // Calculate stroke height in viewBox to display stroke of path.
+    const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight;
     // Minimum segment duration is the duration of one pixel.
     const minSegmentDuration =
       totalDuration / this.containerEl.clientWidth;
 
+    // Set viewBox.
+    graphEl.setAttribute("viewBox",
+                         `0 -${ 1 + strokeHeightForViewBox }
+                          ${ totalDuration }
+                          ${ 1 + strokeHeightForViewBox * 2 }`);
+
     // Create graph helper to render the animation property graph.
-    const win = this.containerEl.ownerGlobal;
     const graphHelper =
-      new ProgressGraphHelper(win, propertyName, animationType, keyframes, totalDuration);
+      new ProgressGraphHelper(this.containerEl.ownerDocument.defaultView,
+                              propertyName, animationType, keyframes, totalDuration);
 
     renderPropertyGraph(graphEl, totalDuration, minSegmentDuration,
                         getPreferredKeyframesProgressThreshold(keyframes), graphHelper);
 
     // Destroy ProgressGraphHelper resources.
     graphHelper.destroy();
 
-    // Set viewBox which includes invisible stroke width.
-    // At first, calculate invisible stroke width from maximum width.
-    // The reason why divide by 2 is that half of stroke width will be invisible
-    // if we use 0 or 1 for y axis.
-    const maxStrokeWidth =
-      win.getComputedStyle(graphEl.querySelector(".keyframes svg .hint")).strokeWidth;
-    const invisibleStrokeWidthInViewBox =
-      maxStrokeWidth / 2 / this.containerEl.clientHeight;
-    graphEl.setAttribute("viewBox",
-                         `0 -${ 1 + invisibleStrokeWidthInViewBox }
-                          ${ totalDuration }
-                          ${ 1 + invisibleStrokeWidthInViewBox * 2 }`);
-
     // Append elements to display keyframe values.
     this.keyframesEl.classList.add(animation.state.type);
     for (let frame of this.keyframes) {
       createNode({
         parent: this.keyframesEl,
         attributes: {
           "class": "frame",
           "style": `left:${frame.offset * 100}%;`,
@@ -110,26 +105,25 @@ Keyframes.prototype = {
  */
 function renderPropertyGraph(parentEl, duration, minSegmentDuration,
                              minProgressThreshold, graphHelper) {
   const segments = graphHelper.createPathSegments(0, duration, minSegmentDuration,
                                                   minProgressThreshold);
 
   const graphType = graphHelper.getGraphType();
   if (graphType !== "color") {
-    graphHelper.appendShapePath(parentEl, segments, graphType);
-    renderEasingHint(parentEl, segments, graphHelper);
+    graphHelper.appendPathElement(parentEl, segments, graphType);
     return;
   }
 
   // Append the color to the path.
   segments.forEach(segment => {
     segment.y = 1;
   });
-  const path = graphHelper.appendShapePath(parentEl, segments, graphType);
+  const path = graphHelper.appendPathElement(parentEl, segments, graphType);
   const defEl = createSVGNode({
     parent: parentEl,
     nodeType: "def"
   });
   const id = `color-property-${ LINEAR_GRADIENT_ID_COUNTER++ }`;
   const linearGradientEl = createSVGNode({
     parent: defEl,
     nodeType: "linearGradient",
@@ -143,110 +137,9 @@ function renderPropertyGraph(parentEl, d
       nodeType: "stop",
       attributes: {
         "stop-color": segment.style,
         "offset": segment.x / duration
       }
     });
   });
   path.style.fill = `url(#${ id })`;
-
-  renderEasingHintForColor(parentEl, graphHelper);
 }
-
-/**
- * Renders the easing hint.
- * This method renders an emphasized path over the easing path for a keyframe.
- * It appears when hovering over the easing.
- * It also renders a tooltip that appears when hovering.
- * @param {Element} parentEl - Parent element of this appended path element.
- * @param {Array} path segments - [{x: {Number} time, y: {Number} progress}, ...]
- * @param {ProgressGraphHelper} graphHelper - The object of ProgressGraphHelper.
- */
-function renderEasingHint(parentEl, segments, helper) {
-  const keyframes = helper.getKeyframes();
-  const duration = helper.getDuration();
-
-  // Split segments for each keyframe.
-  for (let i = 0, indexOfSegments = 0; i < keyframes.length - 1; i++) {
-    const startKeyframe = keyframes[i];
-    const startTime = startKeyframe.offset * duration;
-    const endKeyframe = keyframes[i + 1];
-    const endTime = endKeyframe.offset * duration;
-
-    const keyframeSegments = [];
-    for (; indexOfSegments < segments.length; indexOfSegments++) {
-      const segment = segments[indexOfSegments];
-      if (segment.x < startTime) {
-        // If previous easings were linear, we need to increment the indexOfSegments.
-        continue;
-      }
-      if (segment.x > endTime) {
-        indexOfSegments -= 1;
-        break;
-      }
-      keyframeSegments.push(segment);
-    }
-
-    // If keyframeSegments does not have segment which is at startTime,
-    // get and set the segment.
-    if (keyframeSegments[0].x !== startTime) {
-      keyframeSegments.unshift(helper.getSegment(startTime));
-    }
-    // Also, endTime.
-    if (keyframeSegments[keyframeSegments.length - 1].x !== endTime) {
-      keyframeSegments.push(helper.getSegment(endTime));
-    }
-
-    // Append easing hint as text and emphasis path.
-    const gEl = createSVGNode({
-      parent: parentEl,
-      nodeType: "g"
-    });
-    createSVGNode({
-      parent: gEl,
-      nodeType: "title",
-      textContent: startKeyframe.easing
-    });
-    helper.appendLinePath(gEl, keyframeSegments, `${helper.getGraphType()} hint`);
-  }
-}
-
-/**
- * Render easing hint for properties that are represented by color.
- * This method render as text only.
- * @param {Element} parentEl - Parent element of this appended path element.
- * @param {ProgressGraphHelper} graphHelper - The object of ProgressGraphHalper.
- */
-function renderEasingHintForColor(parentEl, helper) {
-  const keyframes = helper.getKeyframes();
-  const duration = helper.getDuration();
-
-  // Split segments for each keyframe.
-  for (let i = 0; i < keyframes.length - 1; i++) {
-    const startKeyframe = keyframes[i];
-    const startTime = startKeyframe.offset * duration;
-    const endKeyframe = keyframes[i + 1];
-    const endTime = endKeyframe.offset * duration;
-
-    // Append easing hint.
-    const gEl = createSVGNode({
-      parent: parentEl,
-      nodeType: "g"
-    });
-    createSVGNode({
-      parent: gEl,
-      nodeType: "title",
-      textContent: startKeyframe.easing
-    });
-    createSVGNode({
-      parent: gEl,
-      nodeType: "rect",
-      attributes: {
-        x: startTime,
-        y: -1,
-        width: endTime - startTime,
-        height: 1,
-        class: "hint",
-      }
-    });
-  }
-}
--- a/devtools/client/animationinspector/graph-helper.js
+++ b/devtools/client/animationinspector/graph-helper.js
@@ -81,32 +81,16 @@ ProgressGraphHelper.prototype = {
     this.propertyCSSName = null;
     this.propertyJSName = null;
     this.animationType = null;
     this.keyframes = null;
     this.win = null;
   },
 
   /**
-   * Return animation duration.
-   * @return {Number} duration
-   */
-  getDuration: function () {
-    return this.animation.effect.timing.duration;
-  },
-
-  /**
-   * Return animation's keyframe.
-   * @return {Object} keyframe
-   */
-  getKeyframes: function () {
-    return this.keyframes;
-  },
-
-  /**
    * Return graph type.
    * @return {String} if property is 'opacity' or 'transform', return that value.
    *                  Otherwise, return given animation type in constructor.
    */
   getGraphType: function () {
     return (this.propertyJSName === "opacity" || this.propertyJSName === "transform")
            ? this.propertyJSName : this.animationType;
   },
@@ -259,53 +243,40 @@ ProgressGraphHelper.prototype = {
                                 minSegmentDuration, minProgressThreshold) {
     return !this.valueHelperFunction
            ? createKeyframesPathSegments(endTime - startTime, this.devtoolsKeyframes)
            : createPathSegments(startTime, endTime,
                                 minSegmentDuration, minProgressThreshold, this);
   },
 
   /**
-   * Append path element as shape. Also, this method appends two segment
-   * that are {start x, 0} and {end x, 0} to make shape.
+   * Append path element.
    * @param {Element} parentEl - Parent element of this appended path element.
    * @param {Array} pathSegments - Path segments. Please see createPathSegments.
    * @param {String} cls - Class name.
    * @return {Element} path element.
    */
-  appendShapePath: function (parentEl, pathSegments, cls) {
-    return appendShapePath(parentEl, pathSegments, cls);
-  },
-
-  /**
-   * Append path element as line.
-   * @param {Element} parentEl - Parent element of this appended path element.
-   * @param {Array} pathSegments - Path segments. Please see createPathSegments.
-   * @param {String} cls - Class name.
-   * @return {Element} path element.
-   */
-  appendLinePath: function (parentEl, pathSegments, cls) {
-    const isClosePathNeeded = false;
-    return appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded);
+  appendPathElement: function (parentEl, pathSegments, cls) {
+    return appendPathElement(parentEl, pathSegments, cls);
   },
 };
 
 exports.ProgressGraphHelper = ProgressGraphHelper;
 
 /**
  * This class is used for creating the summary graph in animation-timeline.
  * The shape of the graph can be changed by using the following methods:
  * setKeyframes:
  *   If null, the shape is by computed timing progress.
  *   Otherwise, by computed style of 'opacity' to combine effect easing and
  *   keyframe's easing.
  * setFillMode:
  *   Animation fill-mode (e.g. "none", "backwards", "forwards" or "both")
  * setClosePathNeeded:
- *   If true, appendShapePath make the last segment of <path> element to
+ *   If true, appendPathElement make the last segment of <path> element to
  *   "close" segment("Z").
  *   Therefore, if don't need under-line of graph, please set false.
  * setOriginalBehavior:
  *   In Animation::SetCurrentTime spec, even if current time of animation is over
  *   the endTime, the progress is changed. Likewise, in case the time is less than 0.
  *   If set true, prevent the time to make the same animation behavior as the original.
  * setMinProgressThreshold:
  *   SummaryGraphHelper searches and creates the summary graph until the progress
@@ -384,17 +355,17 @@ SummaryGraphHelper.prototype = {
    * Set animation fill mode.
    * @param {String} fill - "both", "forwards", "backwards" or "both"
    */
   setFillMode: function (fill) {
     this.animation.effect.timing.fill = fill;
   },
 
   /**
-   * Set true if need to close path in appendShapePath.
+   * Set true if need to close path in appendPathElement.
    * @param {bool} isClosePathNeeded - true: close, false: open.
    */
   setClosePathNeeded: function (isClosePathNeeded) {
     this.isClosePathNeeded = isClosePathNeeded;
   },
 
   /**
    * SummaryGraphHelper searches and creates the summary graph untill the progress
@@ -435,25 +406,24 @@ SummaryGraphHelper.prototype = {
    *                 [{x: {Number} time, y: {Number} progress}, ...]
    */
   createPathSegments: function (startTime, endTime) {
     return createPathSegments(startTime, endTime,
                               this.minSegmentDuration, this.minProgressThreshold, this);
   },
 
   /**
-   * Append path element as shape. Also, this method appends two segment
-   * that are {start x, 0} and {end x, 0} to make shape.
+   * Append path element.
    * @param {Element} parentEl - Parent element of this appended path element.
    * @param {Array} pathSegments - Path segments. Please see createPathSegments.
    * @param {String} cls - Class name.
    * @return {Element} path element.
    */
-  appendShapePath: function (parentEl, pathSegments, cls) {
-    return appendShapePath(parentEl, pathSegments, cls, this.isClosePathNeeded);
+  appendPathElement: function (parentEl, pathSegments, cls) {
+    return appendPathElement(parentEl, pathSegments, cls, this.isClosePathNeeded);
   },
 
   /**
    * Return current computed timing progress of the animation.
    * @return {float} computed timing progress as float value of Y axis.
    */
   getProgressValue: function () {
     return Math.max(this.animation.effect.getComputedTiming().progress, 0);
@@ -523,60 +493,50 @@ function createPathSegments(startTime, e
     pathSegments.push(currentSegment);
     previousSegment = currentSegment;
   }
 
   return pathSegments;
 }
 
 /**
- * Append path element as shape. Also, this method appends two segment
- * that are {start x, 0} and {end x, 0} to make shape.
- * But does not affect given pathSegments.
+ * Append path element.
  * @param {Element} parentEl - Parent element of this appended path element.
  * @param {Array} pathSegments - Path segments. Please see createPathSegments.
  * @param {String} cls - Class name.
  * @param {bool} isClosePathNeeded - Set true if need to close the path. (default true)
  * @return {Element} path element.
  */
-function appendShapePath(parentEl, pathSegments, cls, isClosePathNeeded = true) {
-  const segments = [
-    { x: pathSegments[0].x, y: 0 },
-    ...pathSegments,
-    { x: pathSegments[pathSegments.length - 1].x, y: 0 }
-  ];
-  return appendPathElement(parentEl, segments, cls, isClosePathNeeded);
-}
+function appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded = true) {
+  // Create path string.
+  let path = `M${ pathSegments[0].x },0`;
+  for (let i = 0; i < pathSegments.length; i++) {
+    const pathSegment = pathSegments[i];
+    if (!pathSegment.easing || pathSegment.easing === "linear") {
+      path += createLinePathString(pathSegment);
+      continue;
+    }
 
-/**
- * Append path element.
- * @param {Element} parentEl - Parent element of this appended path element.
- * @param {Array} pathSegments - Path segments. Please see createPathSegments.
- * @param {String} cls - Class name.
- * @param {bool} isClosePathNeeded - Set true if need to close the path.
- * @return {Element} path element.
- */
-function appendPathElement(parentEl, pathSegments, cls, isClosePathNeeded) {
-  // Create path string.
-  let currentSegment = pathSegments[0];
-  let path = `M${ currentSegment.x },${ currentSegment.y }`;
-  for (let i = 1; i < pathSegments.length; i++) {
-    const currentEasing = currentSegment.easing ? currentSegment.easing : "linear";
-    const nextSegment = pathSegments[i];
-    if (currentEasing === "linear") {
-      path += createLinePathString(nextSegment);
-    } else if (currentEasing.startsWith("steps")) {
-      path += createStepsPathString(currentSegment, nextSegment);
-    } else if (currentEasing.startsWith("frames")) {
-      path += createFramesPathString(currentSegment, nextSegment);
+    if (i + 1 === pathSegments.length) {
+      // We already create steps or cubic-bezier path string in previous.
+      break;
+    }
+
+    const nextPathSegment = pathSegments[i + 1];
+    let createPathFunction;
+    if (pathSegment.easing.startsWith("steps")) {
+      createPathFunction = createStepsPathString;
+    } else if (pathSegment.easing.startsWith("frames")) {
+      createPathFunction = createFramesPathString;
     } else {
-      path += createCubicBezierPathString(currentSegment, nextSegment);
+      createPathFunction = createCubicBezierPathString;
     }
-    currentSegment = nextSegment;
+    path += createPathFunction(pathSegment, nextPathSegment);
   }
+  path += ` L${ pathSegments[pathSegments.length - 1].x },0`;
   if (isClosePathNeeded) {
     path += " Z";
   }
   // Append and return the path element.
   return createSVGNode({
     parent: parentEl,
     nodeType: "path",
     attributes: {
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -692,34 +692,16 @@ body {
   stroke: var(--transform-border-color);
 }
 
 .keyframes svg path.color {
   stroke: none;
   height: 100%;
 }
 
-.keyframes svg .hint {
-  stroke-opacity: 0;
-  stroke-linecap: round;
-  stroke-width: 5;
-}
-
-.keyframes svg path.hint {
-  fill: none;
-}
-
-.keyframes svg path.hint:hover {
-  stroke-opacity: 1;
-}
-
-.keyframes svg rect.hint {
-  fill-opacity: .1;
-}
-
 .animation-detail {
   position: relative;
   width: 100%;
   background-color: var(--theme-body-background);
   z-index: 5;
 }
 
 .animation-detail .animation-detail-header {