Bug 1254408 - Part1 Expose animation performance information in DevTools r=pbro
[mq]: Expose_animation_performance_information_in_DevTools
MozReview-Commit-ID: FHC41UoFEUl
--- 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.
*/