--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -22,16 +22,17 @@ const {
const { isAllAnimationEqual } = require("./utils/utils");
class AnimationInspector {
constructor(inspector, win) {
this.inspector = inspector;
this.win = win;
this.getAnimatedPropertyMap = this.getAnimatedPropertyMap.bind(this);
+ this.getComputedStyle = this.getComputedStyle.bind(this);
this.getNodeFromActor = this.getNodeFromActor.bind(this);
this.selectAnimation = this.selectAnimation.bind(this);
this.setDetailVisibility = this.setDetailVisibility.bind(this);
this.simulateAnimation = this.simulateAnimation.bind(this);
this.toggleElementPicker = this.toggleElementPicker.bind(this);
this.update = this.update.bind(this);
this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
this.onElementPickerStopped = this.onElementPickerStopped.bind(this);
@@ -52,16 +53,17 @@ class AnimationInspector {
const {
onHideBoxModelHighlighter,
} = this.inspector.getPanel("boxmodel").getComponentProps();
const {
emit: emitEventForTest,
getAnimatedPropertyMap,
+ getComputedStyle,
getNodeFromActor,
selectAnimation,
setDetailVisibility,
simulateAnimation,
toggleElementPicker,
} = this;
const target = this.inspector.target;
@@ -72,16 +74,17 @@ class AnimationInspector {
id: "newanimationinspector",
key: "newanimationinspector",
store: this.inspector.store
},
App(
{
emitEventForTest,
getAnimatedPropertyMap,
+ getComputedStyle,
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
selectAnimation,
setDetailVisibility,
setSelectedNode,
simulateAnimation,
toggleElementPicker,
@@ -152,16 +155,37 @@ class AnimationInspector {
});
animatedPropertyMap.set(name, keyframes);
}
return animatedPropertyMap;
}
+ /**
+ * Return the computed style of the specified property after setting the given styles
+ * to the simulated element.
+ *
+ * @param {String} property
+ * CSS property name (e.g. text-align).
+ * @param {Object} styles
+ * Map of CSS property name and value.
+ * @return {String}
+ * Computed style of property.
+ */
+ getComputedStyle(property, styles) {
+ this.simulatedElement.style.cssText = "";
+
+ for (let propertyName in styles) {
+ this.simulatedElement.style.setProperty(propertyName, styles[propertyName]);
+ }
+
+ return this.win.getComputedStyle(this.simulatedElement).getPropertyValue(property);
+ }
+
getNodeFromActor(actorID) {
return this.inspector.walker.getNodeFromActor(actorID, ["node"]);
}
isPanelVisible() {
return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
this.inspector.toolbox.currentToolId === "inspector" &&
this.inspector.sidebar.getCurrentTabID() === "newanimationinspector";
--- a/devtools/client/inspector/animation/components/AnimatedPropertyItem.js
+++ b/devtools/client/inspector/animation/components/AnimatedPropertyItem.js
@@ -9,26 +9,28 @@ const dom = require("devtools/client/sha
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const AnimatedPropertyName = createFactory(require("./AnimatedPropertyName"));
const KeyframesGraph = createFactory(require("./keyframes-graph/KeyframesGraph"));
class AnimatedPropertyItem extends PureComponent {
static get propTypes() {
return {
+ getComputedStyle: PropTypes.func.isRequired,
property: PropTypes.string.isRequired,
simulateAnimation: PropTypes.func.isRequired,
state: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
values: PropTypes.array.isRequired,
};
}
render() {
const {
+ getComputedStyle,
property,
simulateAnimation,
state,
type,
values,
} = this.props;
return dom.li(
@@ -38,16 +40,18 @@ class AnimatedPropertyItem extends PureC
AnimatedPropertyName(
{
property,
state,
}
),
KeyframesGraph(
{
+ getComputedStyle,
+ property,
simulateAnimation,
type,
values,
}
)
);
}
}
--- a/devtools/client/inspector/animation/components/AnimatedPropertyList.js
+++ b/devtools/client/inspector/animation/components/AnimatedPropertyList.js
@@ -11,16 +11,17 @@ const PropTypes = require("devtools/clie
const AnimatedPropertyItem = createFactory(require("./AnimatedPropertyItem"));
class AnimatedPropertyList extends PureComponent {
static get propTypes() {
return {
animation: PropTypes.object.isRequired,
emitEventForTest: PropTypes.func.isRequired,
getAnimatedPropertyMap: PropTypes.func.isRequired,
+ getComputedStyle: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
@@ -58,16 +59,17 @@ class AnimatedPropertyList extends PureC
this.setState({ animatedPropertyMap, animationTypes });
emitEventForTest("animation-keyframes-rendered");
}
render() {
const {
+ getComputedStyle,
simulateAnimation,
} = this.props;
const {
animatedPropertyMap,
animationTypes,
} = this.state;
if (!animatedPropertyMap) {
@@ -78,16 +80,17 @@ class AnimatedPropertyList extends PureC
{
className: "animated-property-list"
},
[...animatedPropertyMap.entries()].map(([property, values]) => {
const state = this.getPropertyState(property);
const type = animationTypes[property];
return AnimatedPropertyItem(
{
+ getComputedStyle,
property,
simulateAnimation,
state,
type,
values,
}
);
})
--- a/devtools/client/inspector/animation/components/AnimatedPropertyListContainer.js
+++ b/devtools/client/inspector/animation/components/AnimatedPropertyListContainer.js
@@ -12,38 +12,41 @@ const AnimatedPropertyList = createFacto
const AnimatedPropertyListHeader = createFactory(require("./AnimatedPropertyListHeader"));
class AnimatedPropertyListContainer extends PureComponent {
static get propTypes() {
return {
animation: PropTypes.object.isRequired,
emitEventForTest: PropTypes.func.isRequired,
getAnimatedPropertyMap: PropTypes.func.isRequired,
+ getComputedStyle: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired,
};
}
render() {
const {
animation,
emitEventForTest,
getAnimatedPropertyMap,
+ getComputedStyle,
simulateAnimation,
} = this.props;
return dom.div(
{
className: `animated-property-list-container ${ animation.state.type }`
},
AnimatedPropertyListHeader(),
AnimatedPropertyList(
{
animation,
emitEventForTest,
getAnimatedPropertyMap,
+ getComputedStyle,
simulateAnimation,
}
)
);
}
}
module.exports = AnimatedPropertyListContainer;
--- a/devtools/client/inspector/animation/components/AnimationDetailContainer.js
+++ b/devtools/client/inspector/animation/components/AnimationDetailContainer.js
@@ -14,26 +14,28 @@ const AnimatedPropertyListContainer =
createFactory(require("./AnimatedPropertyListContainer"));
class AnimationDetailContainer extends PureComponent {
static get propTypes() {
return {
animation: PropTypes.object.isRequired,
emitEventForTest: PropTypes.func.isRequired,
getAnimatedPropertyMap: PropTypes.func.isRequired,
+ getComputedStyle: PropTypes.func.isRequired,
setDetailVisibility: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired,
};
}
render() {
const {
animation,
emitEventForTest,
getAnimatedPropertyMap,
+ getComputedStyle,
setDetailVisibility,
simulateAnimation,
} = this.props;
return dom.div(
{
className: "animation-detail-container"
},
@@ -47,16 +49,17 @@ class AnimationDetailContainer extends P
:
null,
animation ?
AnimatedPropertyListContainer(
{
animation,
emitEventForTest,
getAnimatedPropertyMap,
+ getComputedStyle,
simulateAnimation,
}
)
:
null
);
}
}
--- a/devtools/client/inspector/animation/components/App.js
+++ b/devtools/client/inspector/animation/components/App.js
@@ -16,16 +16,17 @@ const SplitBox = createFactory(require("
class App extends PureComponent {
static get propTypes() {
return {
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
detailVisibility: PropTypes.bool.isRequired,
emitEventForTest: PropTypes.func.isRequired,
getAnimatedPropertyMap: PropTypes.func.isRequired,
+ getComputedStyle: PropTypes.func.isRequired,
getNodeFromActor: PropTypes.func.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
selectAnimation: PropTypes.func.isRequired,
setDetailVisibility: PropTypes.func.isRequired,
setSelectedNode: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired,
toggleElementPicker: PropTypes.func.isRequired,
@@ -37,16 +38,17 @@ class App extends PureComponent {
}
render() {
const {
animations,
detailVisibility,
emitEventForTest,
getAnimatedPropertyMap,
+ getComputedStyle,
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
selectAnimation,
setDetailVisibility,
setSelectedNode,
simulateAnimation,
toggleElementPicker,
@@ -59,16 +61,17 @@ class App extends PureComponent {
},
animations.length ?
SplitBox({
className: "animation-container-splitter",
endPanel: AnimationDetailContainer(
{
emitEventForTest,
getAnimatedPropertyMap,
+ getComputedStyle,
setDetailVisibility,
simulateAnimation,
}
),
endPanelControl: true,
initialHeight: "50%",
splitterSize: 1,
startPanel: AnimationListContainer(
--- a/devtools/client/inspector/animation/components/keyframes-graph/ComputedStylePath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/ComputedStylePath.js
@@ -70,17 +70,17 @@ class ComputedStylePath extends PureComp
const propertyName = this.getPropertyName();
const offsetDistance = endValue.offset - startValue.offset;
const duration = offsetDistance * totalDuration;
const keyframes = [startValue, endValue].map((keyframe, index) => {
return {
offset: index,
easing: keyframe.easing,
- [propertyName]: this.getPropertyValue(keyframe),
+ [getJsPropertyName(propertyName)]: this.getPropertyValue(keyframe),
};
});
const effect = {
duration,
fill: "forwards",
};
const simulatedAnimation = simulateAnimation(keyframes, effect, true);
const simulatedElement = simulatedAnimation.effect.target;
@@ -146,9 +146,27 @@ class ComputedStylePath extends PureComp
let d = `M${ segments[0].x },0 `;
d += toPathString(segments);
d += `L${ segments[segments.length - 1].x },0 Z`;
return dom.path({ d, style });
}
}
+/**
+ * Convert given CSS property name to JavaScript CSS name.
+ *
+ * @param {String} cssPropertyName
+ * CSS property name (e.g. background-color).
+ * @return {String}
+ * JavaScript CSS property name (e.g. backgroundColor).
+ */
+function getJsPropertyName(cssPropertyName) {
+ if (cssPropertyName == "float") {
+ return "cssFloat";
+ }
+ // https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
+ return cssPropertyName.replace(/-([a-z])/gi, (str, group) => {
+ return group.toUpperCase();
+ });
+}
+
module.exports = ComputedStylePath;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/keyframes-graph/DiscretePath.js
@@ -0,0 +1,66 @@
+/* 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 dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+const ComputedStylePath = require("./ComputedStylePath");
+
+class DiscretePath extends ComputedStylePath {
+ static get propTypes() {
+ return {
+ property: PropTypes.string.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = this.propToState(props);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState(this.propToState(nextProps));
+ }
+
+ getPropertyName() {
+ return this.props.property;
+ }
+
+ getPropertyValue(keyframe) {
+ return keyframe.value;
+ }
+
+ propToState({ property, getComputedStyle, values }) {
+ const discreteValues = [];
+
+ for (const keyframe of values) {
+ const style = getComputedStyle(property, { [property]: keyframe.value });
+
+ if (!discreteValues.includes(style)) {
+ discreteValues.push(style);
+ }
+ }
+
+ return { discreteValues };
+ }
+
+ toSegmentValue(computedStyle) {
+ const { discreteValues } = this.state;
+ return discreteValues.indexOf(computedStyle) / (discreteValues.length - 1);
+ }
+
+ render() {
+ return dom.g(
+ {
+ className: "discrete-path",
+ },
+ super.renderGraph()
+ );
+ }
+}
+
+module.exports = DiscretePath;
--- a/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraph.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraph.js
@@ -8,35 +8,41 @@ const { createFactory, PureComponent } =
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const KeyframesGraphPath = createFactory(require("./KeyframesGraphPath"));
class KeyframesGraph extends PureComponent {
static get propTypes() {
return {
+ getComputedStyle: PropTypes.func.isRequired,
+ property: PropTypes.string.isRequired,
simulateAnimation: PropTypes.func.isRequired,
type: PropTypes.string.isRequired,
values: PropTypes.array.isRequired,
};
}
render() {
const {
+ getComputedStyle,
+ property,
simulateAnimation,
type,
values,
} = this.props;
return dom.div(
{
className: "keyframes-graph",
},
KeyframesGraphPath(
{
+ getComputedStyle,
+ property,
simulateAnimation,
type,
values,
}
)
);
}
}
--- a/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraphPath.js
+++ b/devtools/client/inspector/animation/components/keyframes-graph/KeyframesGraphPath.js
@@ -5,26 +5,29 @@
"use strict";
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const ColorPath = createFactory(require("./ColorPath"));
+const DiscretePath = createFactory(require("./DiscretePath"));
const DistancePath = createFactory(require("./DistancePath"));
const {
DEFAULT_GRAPH_HEIGHT,
DEFAULT_KEYFRAMES_GRAPH_DURATION,
} = require("../../utils/graph-helper");
class KeyframesGraphPath extends PureComponent {
static get propTypes() {
return {
+ getComputedStyle: PropTypes.func.isRequired,
+ property: PropTypes.string.isRequired,
simulateAnimation: PropTypes.func.isRequired,
type: PropTypes.string.isRequired,
values: PropTypes.array.isRequired,
};
}
constructor(props) {
super(props);
@@ -37,28 +40,32 @@ class KeyframesGraphPath extends PureCom
componentDidMount() {
this.updateState();
}
getPathComponent(type) {
switch (type) {
case "color" :
return ColorPath;
+ case "discrete" :
+ return DiscretePath;
default :
return DistancePath;
}
}
updateState() {
const thisEl = ReactDOM.findDOMNode(this);
this.setState({ componentWidth: thisEl.parentNode.clientWidth });
}
render() {
const {
+ getComputedStyle,
+ property,
simulateAnimation,
type,
values,
} = this.props;
const { componentWidth } = this.state;
if (!componentWidth) {
return dom.svg();
@@ -71,17 +78,19 @@ class KeyframesGraphPath extends PureCom
className: "keyframes-graph-path",
preserveAspectRatio: "none",
viewBox: `0 -${ DEFAULT_GRAPH_HEIGHT } `
+ `${ DEFAULT_KEYFRAMES_GRAPH_DURATION } ${ DEFAULT_GRAPH_HEIGHT }`,
},
pathComponent(
{
componentWidth,
+ getComputedStyle,
graphHeight: DEFAULT_GRAPH_HEIGHT,
+ property,
simulateAnimation,
totalDuration: DEFAULT_KEYFRAMES_GRAPH_DURATION,
values,
}
)
);
}
}
--- a/devtools/client/inspector/animation/components/keyframes-graph/moz.build
+++ b/devtools/client/inspector/animation/components/keyframes-graph/moz.build
@@ -1,11 +1,12 @@
# 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(
'ColorPath.js',
'ComputedStylePath.js',
+ 'DiscretePath.js',
'DistancePath.js',
'KeyframesGraph.js',
'KeyframesGraphPath.js',
)