Bug 1254408 - Part1 Expose animation performance information in DevTools r=pbro draft
authorRyo Motozawa <motozawa@mozilla-japan.org>
Fri, 22 Apr 2016 20:17:58 +0900
changeset 355300 61fdf5a587f28524785d02828c3bce19853952e4
parent 355133 0891f0fa044cba28024849803e170ed7700e01e0
child 355301 47a42f5c1318e237f9e42c9294a2302fd9a10972
push id16262
push userbmo:motoryo1@gmail.com
push dateFri, 22 Apr 2016 11:18:24 +0000
reviewerspbro
bugs1254408
milestone48.0a1
Bug 1254408 - Part1 Expose animation performance information in DevTools r=pbro [mq]: Expose_animation_performance_information_in_DevTools MozReview-Commit-ID: FHC41UoFEUl
devtools/client/animationinspector/components/animation-details.js
devtools/client/animationinspector/components/animation-time-block.js
devtools/client/animationinspector/components/animation-timeline.js
devtools/client/locales/en-US/animationinspector.properties
devtools/client/themes/animationinspector.css
devtools/client/themes/images/animation-fast-track.svg
devtools/server/actors/animation.js
--- a/devtools/client/animationinspector/components/animation-details.js
+++ b/devtools/client/animationinspector/components/animation-details.js
@@ -52,16 +52,39 @@ AnimationDetails.prototype = {
     }
     this.keyframeComponents = [];
 
     while (this.containerEl.firstChild) {
       this.containerEl.firstChild.remove();
     }
   },
 
+  getPerfDataForProperty: function(animation, propertyName) {
+    let warning = "";
+    let className = "";
+    if (animation.state.propertyState) {
+      let isRunningOnCompositor;
+      for (let propState of animation.state.propertyState) {
+        if (propState.property == propertyName) {
+          isRunningOnCompositor = propState.runningOnCompositor;
+          if (typeof propState.warning != "undefined") {
+            warning = propState.warning;
+          }
+          break;
+        }
+      }
+      if (isRunningOnCompositor && warning == "") {
+        className = "oncompositor";
+      } else if (!isRunningOnCompositor && warning != "") {
+        className = "warning";
+      }
+    }
+    return {className, warning};
+  },
+
   /**
    * Get a list of the tracks of the animation actor
    * @return {Object} A list of tracks, one per animated property, each
    * with a list of keyframes
    */
   getTracks: Task.async(function* () {
     let tracks = {};
 
@@ -132,26 +155,29 @@ AnimationDetails.prototype = {
     // Useful for tests to know when the keyframes have been retrieved.
     this.emit("keyframes-retrieved");
 
     for (let propertyName in this.tracks) {
       let line = createNode({
         parent: this.containerEl,
         attributes: {"class": "property"}
       });
-
+      let {warning, className} =
+        this.getPerfDataForProperty(animation, propertyName);
       createNode({
         // text-overflow doesn't work in flex items, so we need a second level
         // of container to actually have an ellipsis on the name.
         // See bug 972664.
         parent: createNode({
           parent: line,
-          attributes: {"class": "name"},
+          attributes: {"class": "name"}
         }),
-        textContent: getCssPropertyName(propertyName)
+        textContent: getCssPropertyName(propertyName),
+        attributes: {"title": warning,
+                     "class": className}
       });
 
       // Add the keyframes diagram for this property.
       let framesWrapperEl = createNode({
         parent: line,
         attributes: {"class": "track-container"}
       });
 
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -170,17 +170,25 @@ AnimationTimeBlock.prototype = {
     if (state.playbackRate !== 1) {
       text += L10N.getStr("player.animationRateLabel") + " ";
       text += state.playbackRate;
       text += "\n";
     }
 
     // Adding a note that the animation is running on the compositor thread if
     // needed.
-    if (state.isRunningOnCompositor) {
+    if (state.propertyState) {
+      if (state.propertyState
+          .every(propState => propState.runningOnCompositor)) {
+        text += L10N.getStr("player.allPropertiesOnCompositorTooltip");
+      } else if (state.propertyState
+                 .some(propState => propState.runningOnCompositor)) {
+        text += L10N.getStr("player.somePropertiesOnCompositorTooltip");
+      }
+    } else if (state.isRunningOnCompositor) {
       text += L10N.getStr("player.runningOnCompositorTooltip");
     }
 
     return text;
   },
 
   onClick: function(e) {
     e.stopPropagation();
--- a/devtools/client/animationinspector/components/animation-timeline.js
+++ b/devtools/client/animationinspector/components/animation-timeline.js
@@ -267,16 +267,31 @@ AnimationsTimeline.prototype = {
     this.emit("timeline-data-changed", {
       isPaused: true,
       isMoving: false,
       isUserDrag: true,
       time: time
     });
   },
 
+  getCompositorStatusClassName: function(state) {
+    let className = state.isRunningOnCompositor
+                    ? " fast-track"
+                    : "";
+
+    if (state.isRunningOnCompositor && state.propertyState) {
+      className +=
+        state.propertyState.some(propState => !propState.runningOnCompositor)
+        ? " some-properties"
+        : " all-properties";
+    }
+
+    return className;
+  },
+
   render: function(animations, documentCurrentTime) {
     this.unrender();
 
     this.animations = animations;
     if (!this.animations.length) {
       return;
     }
 
@@ -284,36 +299,35 @@ AnimationsTimeline.prototype = {
     for (let {state} of animations) {
       TimeScale.addAnimation(state);
     }
 
     this.drawHeaderAndBackground();
 
     for (let animation of this.animations) {
       animation.on("changed", this.onAnimationStateChanged);
-
       // Each line contains the target animated node and the animation time
       // block.
       let animationEl = createNode({
         parent: this.animationsEl,
         nodeType: "li",
         attributes: {
           "class": "animation " +
                    animation.state.type +
-                   (animation.state.isRunningOnCompositor ? " fast-track" : "")
+                   this.getCompositorStatusClassName(animation.state)
         }
       });
 
       // Right below the line is a hidden-by-default line for displaying the
       // inline keyframes.
       let detailsEl = createNode({
         parent: this.animationsEl,
         nodeType: "li",
         attributes: {
-          "class": "animated-properties"
+          "class": "animated-properties " + animation.state.type
         }
       });
 
       let details = new AnimationDetails(this.serverTraits);
       details.init(detailsEl);
       details.on("frame-selected", this.onFrameSelected);
       this.details.push(details);
 
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -83,16 +83,26 @@ player.timeLabel=%Ss
 # animation runs (1× being the default, 2× being twice as fast).
 player.playbackRateLabel=%S×
 
 # LOCALIZATION NOTE (player.runningOnCompositorTooltip):
 # This string is displayed as a tooltip for the icon that indicates that the
 # animation is running on the compositor thread.
 player.runningOnCompositorTooltip=This animation is running on compositor thread
 
+# LOCALIZATION NOTE (player.allPropertiesOnCompositorTooltip):
+# This string is displayed as a tooltip for the icon that indicates that
+# all of animation is running on the compositor thread.
+player.allPropertiesOnCompositorTooltip=All animation properties are optimized
+
+# LOCALIZATION NOTE (player.somePropertiesOnCompositorTooltip):
+# This string is displayed as a tooltip for the icon that indicates that
+# all of animation is not running on the compositor thread.
+player.somePropertiesOnCompositorTooltip=Some animation properties are optimized
+
 # LOCALIZATION NOTE (timeline.rateSelectorTooltip):
 # This string is displayed in the timeline toolbar, as the tooltip of the
 # drop-down list that can be used to change the rate at which the animations
 # run.
 timeline.rateSelectorTooltip=Set the animations playback rates
 
 # LOCALIZATION NOTE (timeline.pauseResumeButtonTooltip):
 # This string is displayed in the timeline toolbar, as the tooltip of the
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -391,17 +391,17 @@ body {
 .animation-timeline .animation .name {
   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;
+  --fast-track-icon-width: 15px;
   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;
@@ -411,23 +411,33 @@ body {
   width: calc(100% - var(--fast-track-icon-width));
 }
 
 .animation-timeline .fast-track .name::after {
   /* Animations running on the compositor have the fast-track background image*/
   content: "";
   display: block;
   position: absolute;
-  top: 0;
+  top: 1px;
   right: 0;
   height: 100%;
   width: var(--fast-track-icon-width);
   z-index: 1;
+}
 
-  background-image: url("images/animation-fast-track.svg");
+.animation-timeline .all-properties .name::after {
+  background-color: white;
+  clip-path: url(images/animation-fast-track.svg#thunderbolt);
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+.animation-timeline .some-properties .name::after {
+  background-color: var(--theme-content-color3);
+  clip-path: url(images/animation-fast-track.svg#thunderbolt);
   background-repeat: no-repeat;
   background-position: center;
 }
 
 .animation-timeline .animation .delay,
 .animation-timeline .animation .end-delay {
   position: absolute;
   height: 100%;
@@ -533,16 +543,42 @@ body {
   align-items: center;
 }
 
 .animation-timeline .animated-properties .name div {
   overflow: hidden;
   text-overflow: ellipsis;
 }
 
+.animated-properties.cssanimation {
+  --background-color: var(--theme-contrast-background);
+}
+
+.animated-properties.csstransition {
+  --background-color: var(--theme-highlight-blue);
+}
+
+.animated-properties.scriptanimation {
+  --background-color: var(--theme-graphs-green);
+}
+
+.animation-timeline .animated-properties .oncompositor::before {
+  content: "";
+  display: inline-block;
+  width: 17px;
+  height: 17px;
+  background-color: var(--background-color);
+  clip-path: url(images/animation-fast-track.svg#thunderbolt);
+  vertical-align: middle;
+}
+
+.animation-timeline .animated-properties .warning {
+  text-decoration: underline dotted;
+}
+
 .animation-timeline .animated-properties .frames {
   /* The frames list is absolutely positioned and the left and width properties
      are dynamically set from javascript to match the animation's startTime and
      duration */
   position: absolute;
   top: 0;
   height: 100%;
   /* Using flexbox to vertically center the frames */
--- a/devtools/client/themes/images/animation-fast-track.svg
+++ b/devtools/client/themes/images/animation-fast-track.svg
@@ -1,6 +1,8 @@
 <!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 12" width="16" height="16">
-  <path d="M5.75 0l-1 5.5 2 .5-3.5 6 1-5-2-.5z" fill="#fff"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
+  <clipPath id="thunderbolt" transform="scale(1.4)">
+    <path d="M5.75 0l-1 5.5 2 .5-3.5 6 1-5-2-.5z"/>
+  </clipPath>
 </svg>
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -244,16 +244,27 @@ var AnimationPlayerActor = ActorClass({
    * Get the animation iterationStart from this player, in ratio.
    * That is offset of starting position of the animation.
    * @return {Number}
    */
   getIterationStart: function() {
     return this.player.effect.getComputedTiming().iterationStart;
   },
 
+  getPropertiesCompositorStatus: function() {
+    let properties = this.player.effect.getProperties();
+    return properties.map(prop => {
+      return {
+        property: prop.property,
+        runningOnCompositor: prop.runningOnCompositor,
+        warning: prop.warning
+      };
+    });
+  },
+
   /**
    * Return the current start of the Animation.
    * @return {Object}
    */
   getState: function() {
     // Remember the startTime each time getState is called, it may be useful
     // when animations get paused. As in, when an animation gets paused, its
     // startTime goes back to null, but the front-end might still be interested
@@ -277,17 +288,20 @@ var AnimationPlayerActor = ActorClass({
       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,
+      isRunningOnCompositor:
+        this.getPropertiesCompositorStatus()
+            .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
     };
   },
 
   /**
@@ -507,16 +521,17 @@ var AnimationPlayerFront = FrontClass(An
       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,
+      propertyState: this._form.propertyState,
       documentCurrentTime: this._form.documentCurrentTime
     };
   },
 
   /**
    * Executed when the AnimationPlayerActor emits a "changed" event. Used to
    * update the local knowledge of the state.
    */