--- a/devtools/client/inspector/animation/actions/animations.js
+++ b/devtools/client/inspector/animation/actions/animations.js
@@ -3,16 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
UPDATE_ANIMATIONS,
UPDATE_DETAIL_VISIBILITY,
UPDATE_ELEMENT_PICKER_ENABLED,
+ UPDATE_HIGHLIGHTED_NODE,
UPDATE_SELECTED_ANIMATION,
UPDATE_SIDEBAR_SIZE
} = require("./index");
module.exports = {
/**
* Update the list of animation in the animation inspector.
*/
@@ -39,16 +40,26 @@ module.exports = {
updateElementPickerEnabled(elementPickerEnabled) {
return {
type: UPDATE_ELEMENT_PICKER_ENABLED,
elementPickerEnabled,
};
},
/**
+ * Update the highlighted node.
+ */
+ updateHighlightedNode(nodeFront) {
+ return {
+ type: UPDATE_HIGHLIGHTED_NODE,
+ highlightedNode: nodeFront ? nodeFront.actorID : null,
+ };
+ },
+
+ /**
* Update selected animation.
*/
updateSelectedAnimation(selectedAnimation) {
return {
type: UPDATE_SELECTED_ANIMATION,
selectedAnimation,
};
},
--- a/devtools/client/inspector/animation/actions/index.js
+++ b/devtools/client/inspector/animation/actions/index.js
@@ -12,15 +12,18 @@ createEnum([
"UPDATE_ANIMATIONS",
// Update visibility of detail pane.
"UPDATE_DETAIL_VISIBILITY",
// Update state of the picker enabled.
"UPDATE_ELEMENT_PICKER_ENABLED",
+ // Update highlighted node.
+ "UPDATE_HIGHLIGHTED_NODE",
+
// Update selected animation.
"UPDATE_SELECTED_ANIMATION",
// Update sidebar size.
"UPDATE_SIDEBAR_SIZE",
], module.exports);
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -12,16 +12,17 @@ const EventEmitter = require("devtools/s
const App = createFactory(require("./components/App"));
const CurrentTimeTimer = require("./current-time-timer");
const {
updateAnimations,
updateDetailVisibility,
updateElementPickerEnabled,
+ updateHighlightedNode,
updateSelectedAnimation,
updateSidebarSize
} = require("./actions/animations");
const {
isAllAnimationEqual,
hasAnimationIterationCountInfinite,
hasRunningAnimation,
} = require("./utils/utils");
@@ -40,16 +41,17 @@ class AnimationInspector {
this.removeAnimationsCurrentTimeListener =
this.removeAnimationsCurrentTimeListener.bind(this);
this.rewindAnimationsCurrentTime = this.rewindAnimationsCurrentTime.bind(this);
this.selectAnimation = this.selectAnimation.bind(this);
this.setAnimationsCurrentTime = this.setAnimationsCurrentTime.bind(this);
this.setAnimationsPlaybackRate = this.setAnimationsPlaybackRate.bind(this);
this.setAnimationsPlayState = this.setAnimationsPlayState.bind(this);
this.setDetailVisibility = this.setDetailVisibility.bind(this);
+ this.setHighlightedNode = this.setHighlightedNode.bind(this);
this.simulateAnimation = this.simulateAnimation.bind(this);
this.simulateAnimationForKeyframesProgressBar =
this.simulateAnimationForKeyframesProgressBar.bind(this);
this.toggleElementPicker = this.toggleElementPicker.bind(this);
this.update = this.update.bind(this);
this.onAnimationStateChanged = this.onAnimationStateChanged.bind(this);
this.onAnimationsCurrentTimeUpdated = this.onAnimationsCurrentTimeUpdated.bind(this);
this.onAnimationsMutation = this.onAnimationsMutation.bind(this);
@@ -85,16 +87,17 @@ class AnimationInspector {
isAnimationsRunning,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
selectAnimation,
setAnimationsCurrentTime,
setAnimationsPlaybackRate,
setAnimationsPlayState,
setDetailVisibility,
+ setHighlightedNode,
simulateAnimation,
simulateAnimationForKeyframesProgressBar,
toggleElementPicker,
} = this;
const target = this.inspector.target;
this.animationsFront = new AnimationsFront(target.client, target.form);
@@ -120,16 +123,17 @@ class AnimationInspector {
onShowBoxModelHighlighterForNode,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
selectAnimation,
setAnimationsCurrentTime,
setAnimationsPlaybackRate,
setAnimationsPlayState,
setDetailVisibility,
+ setHighlightedNode,
setSelectedNode,
simulateAnimation,
simulateAnimationForKeyframesProgressBar,
toggleElementPicker,
}
)
);
this.provider = provider;
@@ -437,16 +441,32 @@ class AnimationInspector {
}
}
setDetailVisibility(isVisible) {
this.inspector.store.dispatch(updateDetailVisibility(isVisible));
}
/**
+ * Highlight the given node with the box model highlighter.
+ * If no node is provided, hide the box model highlighter.
+ *
+ * @param {NodeFront} nodeFront
+ */
+ async setHighlightedNode(nodeFront) {
+ await this.inspector.highlighters.hideBoxModelHighlighter();
+
+ if (nodeFront) {
+ await this.inspector.highlighters.showBoxModelHighlighter(nodeFront);
+ }
+
+ this.inspector.store.dispatch(updateHighlightedNode(nodeFront));
+ }
+
+ /**
* Returns simulatable animation by given parameters.
* The returned animation is implementing Animation interface of Web Animation API.
* https://drafts.csswg.org/web-animations/#the-animation-interface
*
* @param {Array} keyframes
* e.g. [{ opacity: 0 }, { opacity: 1 }]
* @param {Object} effectTiming
* e.g. { duration: 1000, fill: "both" }
--- a/devtools/client/inspector/animation/components/AnimationItem.js
+++ b/devtools/client/inspector/animation/components/AnimationItem.js
@@ -18,16 +18,17 @@ class AnimationItem extends Component {
animation: PropTypes.object.isRequired,
emitEventForTest: PropTypes.func.isRequired,
getAnimatedPropertyMap: PropTypes.func.isRequired,
getNodeFromActor: PropTypes.func.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
selectAnimation: PropTypes.func.isRequired,
selectedAnimation: PropTypes.object.isRequired,
+ setHighlightedNode: PropTypes.func.isRequired,
setSelectedNode: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired,
timeScale: PropTypes.object.isRequired,
};
}
constructor(props) {
super(props);
@@ -56,16 +57,17 @@ class AnimationItem extends Component {
const {
animation,
emitEventForTest,
getAnimatedPropertyMap,
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
selectAnimation,
+ setHighlightedNode,
setSelectedNode,
simulateAnimation,
timeScale,
} = this.props;
const {
isSelected,
} = this.state;
@@ -76,16 +78,17 @@ class AnimationItem extends Component {
},
AnimationTarget(
{
animation,
emitEventForTest,
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
+ setHighlightedNode,
setSelectedNode,
}
),
SummaryGraph(
{
animation,
emitEventForTest,
getAnimatedPropertyMap,
--- a/devtools/client/inspector/animation/components/AnimationList.js
+++ b/devtools/client/inspector/animation/components/AnimationList.js
@@ -15,31 +15,33 @@ class AnimationList extends PureComponen
return {
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
emitEventForTest: PropTypes.func.isRequired,
getAnimatedPropertyMap: PropTypes.func.isRequired,
getNodeFromActor: PropTypes.func.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
selectAnimation: PropTypes.func.isRequired,
+ setHighlightedNode: PropTypes.func.isRequired,
setSelectedNode: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired,
timeScale: PropTypes.object.isRequired,
};
}
render() {
const {
animations,
emitEventForTest,
getAnimatedPropertyMap,
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
selectAnimation,
+ setHighlightedNode,
setSelectedNode,
simulateAnimation,
timeScale,
} = this.props;
return dom.ul(
{
className: "animation-list"
@@ -49,16 +51,17 @@ class AnimationList extends PureComponen
{
animation,
emitEventForTest,
getAnimatedPropertyMap,
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
selectAnimation,
+ setHighlightedNode,
setSelectedNode,
simulateAnimation,
timeScale,
}
)
)
);
}
--- a/devtools/client/inspector/animation/components/AnimationListContainer.js
+++ b/devtools/client/inspector/animation/components/AnimationListContainer.js
@@ -20,16 +20,17 @@ class AnimationListContainer extends Pur
emitEventForTest: PropTypes.func.isRequired,
getAnimatedPropertyMap: PropTypes.func.isRequired,
getNodeFromActor: PropTypes.func.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
selectAnimation: PropTypes.func.isRequired,
setAnimationsCurrentTime: PropTypes.func.isRequired,
+ setHighlightedNode: PropTypes.func.isRequired,
setSelectedNode: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired,
timeScale: PropTypes.object.isRequired,
};
}
render() {
const {
@@ -38,16 +39,17 @@ class AnimationListContainer extends Pur
emitEventForTest,
getAnimatedPropertyMap,
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
removeAnimationsCurrentTimeListener,
selectAnimation,
setAnimationsCurrentTime,
+ setHighlightedNode,
setSelectedNode,
simulateAnimation,
timeScale,
} = this.props;
return dom.div(
{
className: "animation-list-container"
@@ -64,16 +66,17 @@ class AnimationListContainer extends Pur
{
animations,
emitEventForTest,
getAnimatedPropertyMap,
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
selectAnimation,
+ setHighlightedNode,
setSelectedNode,
simulateAnimation,
timeScale,
}
)
);
}
}
--- a/devtools/client/inspector/animation/components/AnimationTarget.js
+++ b/devtools/client/inspector/animation/components/AnimationTarget.js
@@ -1,32 +1,34 @@
/* 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 { Component } = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { translateNodeFrontToGrip } = require("devtools/client/inspector/shared/utils");
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
const { Rep } = REPS;
const ElementNode = REPS.ElementNode;
class AnimationTarget extends Component {
static get propTypes() {
return {
animation: PropTypes.object.isRequired,
emitEventForTest: PropTypes.func.isRequired,
getNodeFromActor: PropTypes.func.isRequired,
+ highlightedNode: PropTypes.string.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
- setSelectedNode: PropTypes.func.isRequired,
+ setHighlightedNode: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
nodeFront: null,
@@ -39,17 +41,18 @@ class AnimationTarget extends Component
componentWillReceiveProps(nextProps) {
if (this.props.animation.actorID !== nextProps.animation.actorID) {
this.updateNodeFront(nextProps.animation);
}
}
shouldComponentUpdate(nextProps, nextState) {
- return this.state.nodeFront !== nextState.nodeFront;
+ return this.state.nodeFront !== nextState.nodeFront ||
+ this.props.highlightedNode !== nextState.highlightedNode;
}
async updateNodeFront(animation) {
const { emitEventForTest, getNodeFromActor } = this.props;
// Try and get it from the playerFront directly.
let nodeFront = animation.animationTargetNodeFront;
@@ -69,41 +72,53 @@ class AnimationTarget extends Component
this.setState({ nodeFront });
emitEventForTest("animation-target-rendered");
}
render() {
const {
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
- setSelectedNode,
+ highlightedNode,
+ setHighlightedNode,
} = this.props;
const { nodeFront } = this.state;
if (!nodeFront) {
return dom.div(
{
className: "animation-target"
}
);
}
+ const isHighlighted = nodeFront.actorID === highlightedNode;
+
return dom.div(
{
- className: "animation-target"
+ className: "animation-target" +
+ (isHighlighted ? " highlighting" : ""),
},
Rep(
{
defaultRep: ElementNode,
mode: MODE.TINY,
object: translateNodeFrontToGrip(nodeFront),
onDOMNodeMouseOut: () => onHideBoxModelHighlighter(),
onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(nodeFront),
- onInspectIconClick: () => setSelectedNode(nodeFront,
- { reason: "animation-panel" }),
+ onInspectIconClick: (_, e) => {
+ e.stopPropagation();
+ setHighlightedNode(isHighlighted ? null : nodeFront);
+ }
}
)
);
}
}
-module.exports = AnimationTarget;
+const mapStateToProps = state => {
+ return {
+ highlightedNode: state.animations.highlightedNode,
+ };
+};
+
+module.exports = connect(mapStateToProps)(AnimationTarget);
--- a/devtools/client/inspector/animation/components/App.js
+++ b/devtools/client/inspector/animation/components/App.js
@@ -30,21 +30,23 @@ class App extends Component {
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
rewindAnimationsCurrentTime: PropTypes.func.isRequired,
selectAnimation: PropTypes.func.isRequired,
setAnimationsCurrentTime: PropTypes.func.isRequired,
setAnimationsPlaybackRate: PropTypes.func.isRequired,
setAnimationsPlayState: PropTypes.func.isRequired,
setDetailVisibility: PropTypes.func.isRequired,
+ setHighlightedNode: PropTypes.func.isRequired,
setSelectedNode: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired,
simulateAnimationForKeyframesProgressBar: PropTypes.func.isRequired,
timeScale: PropTypes.object.isRequired,
toggleElementPicker: PropTypes.func.isRequired,
+ toggleLockingHighlight: PropTypes.func.isRequired,
};
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.animations.length !== 0 || nextProps.animations.length !== 0;
}
render() {
@@ -61,16 +63,17 @@ class App extends Component {
onShowBoxModelHighlighterForNode,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime,
selectAnimation,
setAnimationsCurrentTime,
setAnimationsPlaybackRate,
setAnimationsPlayState,
setDetailVisibility,
+ setHighlightedNode,
setSelectedNode,
simulateAnimation,
simulateAnimationForKeyframesProgressBar,
timeScale,
toggleElementPicker,
} = this.props;
return dom.div(
@@ -117,16 +120,17 @@ class App extends Component {
emitEventForTest,
getAnimatedPropertyMap,
getNodeFromActor,
onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode,
removeAnimationsCurrentTimeListener,
selectAnimation,
setAnimationsCurrentTime,
+ setHighlightedNode,
setSelectedNode,
simulateAnimation,
timeScale,
}
),
vert: false,
})
]
--- a/devtools/client/inspector/animation/reducers/animations.js
+++ b/devtools/client/inspector/animation/reducers/animations.js
@@ -3,26 +3,28 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
UPDATE_ANIMATIONS,
UPDATE_DETAIL_VISIBILITY,
UPDATE_ELEMENT_PICKER_ENABLED,
+ UPDATE_HIGHLIGHTED_NODE,
UPDATE_SELECTED_ANIMATION,
UPDATE_SIDEBAR_SIZE,
} = require("../actions/index");
const TimeScale = require("../utils/timescale");
const INITIAL_STATE = {
animations: [],
detailVisibility: false,
elementPickerEnabled: false,
+ highlightedNode: null,
selectedAnimation: null,
sidebarSize: {
height: 0,
width: 0,
},
timeScale: null,
};
@@ -52,16 +54,22 @@ const reducers = {
},
[UPDATE_ELEMENT_PICKER_ENABLED](state, { elementPickerEnabled }) {
return Object.assign({}, state, {
elementPickerEnabled
});
},
+ [UPDATE_HIGHLIGHTED_NODE](state, { highlightedNode }) {
+ return Object.assign({}, state, {
+ highlightedNode
+ });
+ },
+
[UPDATE_SELECTED_ANIMATION](state, { selectedAnimation }) {
const detailVisibility = !!selectedAnimation;
return Object.assign({}, state, {
detailVisibility,
selectedAnimation
});
},
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -384,16 +384,50 @@ class HighlightersOverlay {
this.state.grid.options);
this.gridHighlighterShown = null;
// Erase grid highlighter state.
this.state.grid = {};
}
/**
+ * Show the box model highlighter for the given node.
+ *
+ * @param {NodeFront} node
+ * The NodeFront of the element to highlight.
+ * @param {Object} options
+ * Object used for passing options to the box model highlighter.
+ */
+ async showBoxModelHighlighter(node, options) {
+ const highlighter = await this._getHighlighter("BoxModelHighlighter");
+ if (!highlighter) {
+ return;
+ }
+
+ const isShown = await highlighter.show(node, options);
+ if (!isShown) {
+ return;
+ }
+
+ this.boxModelHighlighterShown = node;
+ }
+
+ /**
+ * Hide the box model highlighter.
+ */
+ async hideBoxModelHighlighter() {
+ if (!this.boxModelHighlighterShown || !this.highlighters.BoxModelHighlighter) {
+ return;
+ }
+
+ await this.highlighters.BoxModelHighlighter.hide();
+ this.boxModelHighlighterShown = null;
+ }
+
+ /**
* Toggle the geometry editor highlighter for the given element.
*
* @param {NodeFront} node
* The NodeFront of the element to highlight.
*/
async toggleGeometryHighlighter(node) {
if (node == this.geometryEditorHighlighterShown) {
await this.hideGeometryEditor();
@@ -866,16 +900,17 @@ class HighlightersOverlay {
this._hideHighlighterIfDeadNode(this.shapesHighlighterShown,
this.hideShapesHighlighter);
}
/**
* Clear saved highlighter shown properties on will-navigate.
*/
onWillNavigate() {
+ this.boxModelHighlighterShown = null;
this.flexboxHighlighterShown = null;
this.geometryEditorHighlighterShown = null;
this.gridHighlighterShown = null;
this.hoveredHighlighterShown = null;
this.selectorHighlighterShown = null;
this.shapesHighlighterShown = null;
this.destroyEditors();
}
@@ -921,16 +956,17 @@ class HighlightersOverlay {
this._lastHovered = null;
this.inspector = null;
this.highlighterUtils = null;
this.supportsHighlighters = null;
this.state = null;
+ this.boxModelHighlighterShown = null;
this.flexboxHighlighterShown = null;
this.geometryEditorHighlighterShown = null;
this.gridHighlighterShown = null;
this.hoveredHighlighterShown = null;
this.selectorHighlighterShown = null;
this.shapesHighlighterShown = null;
this.destroyed = true;
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -231,26 +231,34 @@ select.playback-rate-selector.devtools-b
height: 100%;
padding-left: 4px;
width: var(--sidebar-width);
}
/* Reps component */
.animation-target .objectBox {
display: flex;
- width: 100%;
+ max-width: 100%;
}
.animation-target .objectBox .attrName {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
+.animation-target .objectBox:hover .open-inspector {
+ background-color: var(--comment-node-color);
+}
+
+.animation-target.highlighting .objectBox .open-inspector {
+ background-color: var(--theme-highlight-blue);
+}
+
/* Summary Graph */
.animation-summary-graph {
cursor: pointer;
height: 100%;
padding-top: 5px;
position: relative;
width: calc(100% - var(--sidebar-width) - var(--graph-right-offset));
}