Bug 1431573 - Part 4: Implement time label. r=gl

MozReview-Commit-ID: Cg6A4hNLXnO
This commit is contained in:
Daisuke Akatsuka 2018-03-13 16:45:19 +09:00
Родитель 49b1360194
Коммит 162722dd52
10 изменённых файлов: 219 добавлений и 6 удалений

Просмотреть файл

@ -19,16 +19,23 @@ const {
updateSelectedAnimation, updateSelectedAnimation,
updateSidebarSize updateSidebarSize
} = require("./actions/animations"); } = require("./actions/animations");
const { isAllAnimationEqual } = require("./utils/utils"); const {
isAllAnimationEqual,
hasPlayingAnimation,
} = require("./utils/utils");
class AnimationInspector { class AnimationInspector {
constructor(inspector, win) { constructor(inspector, win) {
this.inspector = inspector; this.inspector = inspector;
this.win = win; this.win = win;
this.addAnimationsCurrentTimeListener =
this.addAnimationsCurrentTimeListener.bind(this);
this.getAnimatedPropertyMap = this.getAnimatedPropertyMap.bind(this); this.getAnimatedPropertyMap = this.getAnimatedPropertyMap.bind(this);
this.getComputedStyle = this.getComputedStyle.bind(this); this.getComputedStyle = this.getComputedStyle.bind(this);
this.getNodeFromActor = this.getNodeFromActor.bind(this); this.getNodeFromActor = this.getNodeFromActor.bind(this);
this.removeAnimationsCurrentTimeListener =
this.removeAnimationsCurrentTimeListener.bind(this);
this.rewindAnimationsCurrentTime = this.rewindAnimationsCurrentTime.bind(this); this.rewindAnimationsCurrentTime = this.rewindAnimationsCurrentTime.bind(this);
this.selectAnimation = this.selectAnimation.bind(this); this.selectAnimation = this.selectAnimation.bind(this);
this.setAnimationsPlayState = this.setAnimationsPlayState.bind(this); this.setAnimationsPlayState = this.setAnimationsPlayState.bind(this);
@ -36,6 +43,7 @@ class AnimationInspector {
this.simulateAnimation = this.simulateAnimation.bind(this); this.simulateAnimation = this.simulateAnimation.bind(this);
this.toggleElementPicker = this.toggleElementPicker.bind(this); this.toggleElementPicker = this.toggleElementPicker.bind(this);
this.update = this.update.bind(this); this.update = this.update.bind(this);
this.onAnimationsCurrentTimeUpdated = this.onAnimationsCurrentTimeUpdated.bind(this);
this.onElementPickerStarted = this.onElementPickerStarted.bind(this); this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
this.onElementPickerStopped = this.onElementPickerStopped.bind(this); this.onElementPickerStopped = this.onElementPickerStopped.bind(this);
this.onSidebarResized = this.onSidebarResized.bind(this); this.onSidebarResized = this.onSidebarResized.bind(this);
@ -58,10 +66,13 @@ class AnimationInspector {
} = this.inspector.getPanel("boxmodel").getComponentProps(); } = this.inspector.getPanel("boxmodel").getComponentProps();
const { const {
addAnimationsCurrentTimeListener,
emit: emitEventForTest, emit: emitEventForTest,
getAnimatedPropertyMap, getAnimatedPropertyMap,
getComputedStyle, getComputedStyle,
getNodeFromActor, getNodeFromActor,
isAnimationsRunning,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime, rewindAnimationsCurrentTime,
selectAnimation, selectAnimation,
setAnimationsPlayState, setAnimationsPlayState,
@ -73,6 +84,8 @@ class AnimationInspector {
const target = this.inspector.target; const target = this.inspector.target;
this.animationsFront = new AnimationsFront(target.client, target.form); this.animationsFront = new AnimationsFront(target.client, target.form);
this.animationsCurrentTimeListeners = [];
const provider = createElement(Provider, const provider = createElement(Provider,
{ {
id: "newanimationinspector", id: "newanimationinspector",
@ -81,12 +94,15 @@ class AnimationInspector {
}, },
App( App(
{ {
addAnimationsCurrentTimeListener,
emitEventForTest, emitEventForTest,
getAnimatedPropertyMap, getAnimatedPropertyMap,
getComputedStyle, getComputedStyle,
getNodeFromActor, getNodeFromActor,
isAnimationsRunning,
onHideBoxModelHighlighter, onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode, onShowBoxModelHighlighterForNode,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime, rewindAnimationsCurrentTime,
selectAnimation, selectAnimation,
setAnimationsPlayState, setAnimationsPlayState,
@ -123,6 +139,8 @@ class AnimationInspector {
this.simulatedElement = null; this.simulatedElement = null;
} }
this.stopAnimationsCurrentTimeTimer();
this.inspector = null; this.inspector = null;
this.win = null; this.win = null;
} }
@ -131,6 +149,10 @@ class AnimationInspector {
return this.inspector.store.getState().animations; return this.inspector.store.getState().animations;
} }
addAnimationsCurrentTimeListener(listener) {
this.animationsCurrentTimeListeners.push(listener);
}
/** /**
* Return a map of animated property from given animation actor. * Return a map of animated property from given animation actor.
* *
@ -201,6 +223,12 @@ class AnimationInspector {
this.inspector.sidebar.getCurrentTabID() === "newanimationinspector"; this.inspector.sidebar.getCurrentTabID() === "newanimationinspector";
} }
onAnimationsCurrentTimeUpdated(currentTime) {
for (const listener of this.animationsCurrentTimeListeners) {
listener(currentTime);
}
}
onElementPickerStarted() { onElementPickerStarted() {
this.inspector.store.dispatch(updateElementPickerEnabled(true)); this.inspector.store.dispatch(updateElementPickerEnabled(true));
} }
@ -222,10 +250,16 @@ class AnimationInspector {
this.inspector.store.dispatch(updateSidebarSize(size)); this.inspector.store.dispatch(updateSidebarSize(size));
} }
removeAnimationsCurrentTimeListener(listener) {
this.animationsCurrentTimeListeners =
this.animationsCurrentTimeListeners.filter(l => l !== listener);
}
async rewindAnimationsCurrentTime() { async rewindAnimationsCurrentTime() {
const animations = this.state.animations; const animations = this.state.animations;
await this.animationsFront.setCurrentTimes(animations, 0, true); await this.animationsFront.setCurrentTimes(animations, 0, true);
this.updateAnimations(animations); await this.updateAnimations(animations);
this.onAnimationsCurrentTimeUpdated(0);
} }
selectAnimation(animation) { selectAnimation(animation) {
@ -288,6 +322,19 @@ class AnimationInspector {
return this.simulatedAnimation; return this.simulatedAnimation;
} }
stopAnimationsCurrentTimeTimer() {
if (this.currentTimeTimer) {
this.currentTimeTimer.destroy();
this.currentTimeTimer = null;
}
}
startAnimationsCurrentTimeTimer() {
const currentTimeTimer = new CurrentTimeTimer(this);
currentTimeTimer.start();
this.currentTimeTimer = currentTimeTimer;
}
toggleElementPicker() { toggleElementPicker() {
this.inspector.toolbox.highlighterUtils.togglePicker(); this.inspector.toolbox.highlighterUtils.togglePicker();
} }
@ -325,6 +372,8 @@ class AnimationInspector {
} }
updateState(animations) { updateState(animations) {
this.stopAnimationsCurrentTimeTimer();
this.inspector.store.dispatch(updateAnimations(animations)); this.inspector.store.dispatch(updateAnimations(animations));
// If number of displayed animations is one, we select the animation automatically. // If number of displayed animations is one, we select the animation automatically.
// But if selected animation is in given animations, ignores. // But if selected animation is in given animations, ignores.
@ -334,6 +383,45 @@ class AnimationInspector {
!animations.find(animation => animation.actorID === selectedAnimation.actorID)) { !animations.find(animation => animation.actorID === selectedAnimation.actorID)) {
this.selectAnimation(animations.length === 1 ? animations[0] : null); this.selectAnimation(animations.length === 1 ? animations[0] : null);
} }
if (hasPlayingAnimation(animations)) {
this.startAnimationsCurrentTimeTimer();
}
}
}
class CurrentTimeTimer {
constructor(animationInspector) {
const timeScale = animationInspector.state.timeScale;
this.baseCurrentTime = timeScale.documentCurrentTime - timeScale.minStartTime;
this.startTime = animationInspector.win.performance.now();
this.animationInspector = animationInspector;
this.next = this.next.bind(this);
}
destroy() {
this.stop();
this.animationInspector = null;
}
next() {
if (this.doStop) {
return;
}
const { onAnimationsCurrentTimeUpdated, win } = this.animationInspector;
const currentTime = this.baseCurrentTime + win.performance.now() - this.startTime;
onAnimationsCurrentTimeUpdated(currentTime);
win.requestAnimationFrame(this.next);
}
start() {
this.next();
}
stop() {
this.doStop = true;
} }
} }

Просмотреть файл

@ -12,8 +12,6 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
const AnimationList = createFactory(require("./AnimationList")); const AnimationList = createFactory(require("./AnimationList"));
const AnimationListHeader = createFactory(require("./AnimationListHeader")); const AnimationListHeader = createFactory(require("./AnimationListHeader"));
const TimeScale = require("../utils/timescale");
class AnimationListContainer extends PureComponent { class AnimationListContainer extends PureComponent {
static get propTypes() { static get propTypes() {
return { return {
@ -26,6 +24,7 @@ class AnimationListContainer extends PureComponent {
selectAnimation: PropTypes.func.isRequired, selectAnimation: PropTypes.func.isRequired,
setSelectedNode: PropTypes.func.isRequired, setSelectedNode: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired, simulateAnimation: PropTypes.func.isRequired,
timeScale: PropTypes.object.isRequired,
}; };
} }
@ -40,8 +39,8 @@ class AnimationListContainer extends PureComponent {
selectAnimation, selectAnimation,
setSelectedNode, setSelectedNode,
simulateAnimation, simulateAnimation,
timeScale,
} = this.props; } = this.props;
const timeScale = new TimeScale(animations);
return dom.div( return dom.div(
{ {

Просмотреть файл

@ -8,13 +8,16 @@ const { createFactory, PureComponent } = require("devtools/client/shared/vendor/
const dom = require("devtools/client/shared/vendor/react-dom-factories"); const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const CurrentTimeLabel = createFactory(require("./CurrentTimeLabel"));
const PauseResumeButton = createFactory(require("./PauseResumeButton")); const PauseResumeButton = createFactory(require("./PauseResumeButton"));
const RewindButton = createFactory(require("./RewindButton")); const RewindButton = createFactory(require("./RewindButton"));
class AnimationToolbar extends PureComponent { class AnimationToolbar extends PureComponent {
static get propTypes() { static get propTypes() {
return { return {
addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
animations: PropTypes.arrayOf(PropTypes.object).isRequired, animations: PropTypes.arrayOf(PropTypes.object).isRequired,
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
rewindAnimationsCurrentTime: PropTypes.func.isRequired, rewindAnimationsCurrentTime: PropTypes.func.isRequired,
setAnimationsPlayState: PropTypes.func.isRequired, setAnimationsPlayState: PropTypes.func.isRequired,
}; };
@ -22,7 +25,9 @@ class AnimationToolbar extends PureComponent {
render() { render() {
const { const {
addAnimationsCurrentTimeListener,
animations, animations,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime, rewindAnimationsCurrentTime,
setAnimationsPlayState, setAnimationsPlayState,
} = this.props; } = this.props;
@ -41,6 +46,12 @@ class AnimationToolbar extends PureComponent {
animations, animations,
setAnimationsPlayState, setAnimationsPlayState,
} }
),
CurrentTimeLabel(
{
addAnimationsCurrentTimeListener,
removeAnimationsCurrentTimeListener,
}
) )
); );
} }

Просмотреть файл

@ -18,6 +18,7 @@ const SplitBox = createFactory(require("devtools/client/shared/components/splitt
class App extends PureComponent { class App extends PureComponent {
static get propTypes() { static get propTypes() {
return { return {
addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
animations: PropTypes.arrayOf(PropTypes.object).isRequired, animations: PropTypes.arrayOf(PropTypes.object).isRequired,
detailVisibility: PropTypes.bool.isRequired, detailVisibility: PropTypes.bool.isRequired,
emitEventForTest: PropTypes.func.isRequired, emitEventForTest: PropTypes.func.isRequired,
@ -26,12 +27,14 @@ class App extends PureComponent {
getNodeFromActor: PropTypes.func.isRequired, getNodeFromActor: PropTypes.func.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired, onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired, onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
rewindAnimationsCurrentTime: PropTypes.func.isRequired, rewindAnimationsCurrentTime: PropTypes.func.isRequired,
selectAnimation: PropTypes.func.isRequired, selectAnimation: PropTypes.func.isRequired,
setAnimationsPlayState: PropTypes.func.isRequired, setAnimationsPlayState: PropTypes.func.isRequired,
setDetailVisibility: PropTypes.func.isRequired, setDetailVisibility: PropTypes.func.isRequired,
setSelectedNode: PropTypes.func.isRequired, setSelectedNode: PropTypes.func.isRequired,
simulateAnimation: PropTypes.func.isRequired, simulateAnimation: PropTypes.func.isRequired,
timeScale: PropTypes.object.isRequired,
toggleElementPicker: PropTypes.func.isRequired, toggleElementPicker: PropTypes.func.isRequired,
}; };
} }
@ -42,6 +45,7 @@ class App extends PureComponent {
render() { render() {
const { const {
addAnimationsCurrentTimeListener,
animations, animations,
detailVisibility, detailVisibility,
emitEventForTest, emitEventForTest,
@ -50,12 +54,14 @@ class App extends PureComponent {
getNodeFromActor, getNodeFromActor,
onHideBoxModelHighlighter, onHideBoxModelHighlighter,
onShowBoxModelHighlighterForNode, onShowBoxModelHighlighterForNode,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime, rewindAnimationsCurrentTime,
selectAnimation, selectAnimation,
setAnimationsPlayState, setAnimationsPlayState,
setDetailVisibility, setDetailVisibility,
setSelectedNode, setSelectedNode,
simulateAnimation, simulateAnimation,
timeScale,
toggleElementPicker, toggleElementPicker,
} = this.props; } = this.props;
@ -68,7 +74,9 @@ class App extends PureComponent {
[ [
AnimationToolbar( AnimationToolbar(
{ {
addAnimationsCurrentTimeListener,
animations, animations,
removeAnimationsCurrentTimeListener,
rewindAnimationsCurrentTime, rewindAnimationsCurrentTime,
setAnimationsPlayState, setAnimationsPlayState,
} }
@ -98,6 +106,7 @@ class App extends PureComponent {
selectAnimation, selectAnimation,
setSelectedNode, setSelectedNode,
simulateAnimation, simulateAnimation,
timeScale,
} }
), ),
vert: false, vert: false,
@ -117,6 +126,7 @@ const mapStateToProps = state => {
return { return {
animations: state.animations.animations, animations: state.animations.animations,
detailVisibility: state.animations.detailVisibility, detailVisibility: state.animations.detailVisibility,
timeScale: state.animations.timeScale,
}; };
}; };

Просмотреть файл

@ -0,0 +1,83 @@
/* 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 { 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");
class CurrentTimeLabel extends PureComponent {
static get propTypes() {
return {
addAnimationsCurrentTimeListener: PropTypes.func.isRequired,
removeAnimationsCurrentTimeListener: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
const { addAnimationsCurrentTimeListener } = props;
this.onCurrentTimeUpdated = this.onCurrentTimeUpdated.bind(this);
this.state = {
currentTime: 0,
};
addAnimationsCurrentTimeListener(this.onCurrentTimeUpdated);
}
componentWillUnmount() {
const { removeAnimationsCurrentTimeListener } = this.props;
removeAnimationsCurrentTimeListener(this.onCurrentTimeUpdated);
}
onCurrentTimeUpdated(currentTime) {
this.setState({ currentTime });
}
render() {
const { currentTime } = this.state;
return dom.label(
{
className: "current-time-label",
},
formatStopwatchTime(currentTime)
);
}
}
/**
* Format a timestamp (in ms) as a mm:ss.mmm string.
*
* @param {Number} time
* @return {String}
*/
function formatStopwatchTime(time) {
// Format falsy values as 0
if (!time) {
return "00:00.000";
}
let milliseconds = parseInt(time % 1000, 10);
let seconds = parseInt((time / 1000) % 60, 10);
let minutes = parseInt((time / (1000 * 60)), 10);
let pad = (nb, max) => {
if (nb < max) {
return new Array((max + "").length - (nb + "").length + 1).join("0") + nb;
}
return nb;
};
minutes = pad(minutes, 10);
seconds = pad(seconds, 10);
milliseconds = pad(milliseconds, 100);
return `${minutes}:${seconds}.${milliseconds}`;
}
module.exports = CurrentTimeLabel;

Просмотреть файл

@ -9,6 +9,7 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { getStr } = require("../utils/l10n"); const { getStr } = require("../utils/l10n");
const { hasPlayingAnimation } = require("../utils/utils");
class PauseResumeButton extends PureComponent { class PauseResumeButton extends PureComponent {
static get propTypes() { static get propTypes() {
@ -43,7 +44,7 @@ class PauseResumeButton extends PureComponent {
updateState() { updateState() {
const { animations } = this.props; const { animations } = this.props;
const isPlaying = animations.some(({state}) => state.playState === "running"); const isPlaying = hasPlayingAnimation(animations);
this.setState({ isPlaying }); this.setState({ isPlaying });
} }

Просмотреть файл

@ -24,6 +24,7 @@ DevToolsModules(
'AnimationTimelineTickList.js', 'AnimationTimelineTickList.js',
'AnimationToolbar.js', 'AnimationToolbar.js',
'App.js', 'App.js',
'CurrentTimeLabel.js',
'KeyframesProgressTickItem.js', 'KeyframesProgressTickItem.js',
'KeyframesProgressTickList.js', 'KeyframesProgressTickList.js',
'NoAnimationPanel.js', 'NoAnimationPanel.js',

Просмотреть файл

@ -12,6 +12,8 @@ const {
UPDATE_SIDEBAR_SIZE, UPDATE_SIDEBAR_SIZE,
} = require("../actions/index"); } = require("../actions/index");
const TimeScale = require("../utils/timescale");
const INITIAL_STATE = { const INITIAL_STATE = {
animations: [], animations: [],
detailVisibility: false, detailVisibility: false,
@ -21,12 +23,14 @@ const INITIAL_STATE = {
height: 0, height: 0,
width: 0, width: 0,
}, },
timeScale: null,
}; };
const reducers = { const reducers = {
[UPDATE_ANIMATIONS](state, { animations }) { [UPDATE_ANIMATIONS](state, { animations }) {
return Object.assign({}, state, { return Object.assign({}, state, {
animations, animations,
timeScale: new TimeScale(animations),
}); });
}, },

Просмотреть файл

@ -24,6 +24,8 @@ class TimeScale {
constructor(animations) { constructor(animations) {
this.minStartTime = Infinity; this.minStartTime = Infinity;
this.maxEndTime = 0; this.maxEndTime = 0;
this.documentCurrentTime = 0;
for (const animation of animations) { for (const animation of animations) {
this.addAnimation(animation.state); this.addAnimation(animation.state);
} }
@ -38,6 +40,7 @@ class TimeScale {
addAnimation(state) { addAnimation(state) {
let { let {
delay, delay,
documentCurrentTime,
duration, duration,
endDelay = 0, endDelay = 0,
iterationCount, iterationCount,
@ -67,6 +70,8 @@ class TimeScale {
const length = toRate(delay) + rateRelativeDuration + toRate(minZero(endDelay)); const length = toRate(delay) + rateRelativeDuration + toRate(minZero(endDelay));
const endTime = previousStartTime + length; const endTime = previousStartTime + length;
this.maxEndTime = Math.max(this.maxEndTime, endTime); this.maxEndTime = Math.max(this.maxEndTime, endTime);
this.documentCurrentTime = Math.max(this.documentCurrentTime, documentCurrentTime);
} }
/** /**

Просмотреть файл

@ -69,6 +69,16 @@ function isAllAnimationEqual(animationsA, animationsB) {
return true; return true;
} }
/**
* Check wether the animations are running at least one.
*
* @param {Array} animations.
* @return {Boolean} true: playing
*/
function hasPlayingAnimation(animations) {
return animations.some(({state}) => state.playState === "running");
}
/** /**
* Check the equality given states as effect timing. * Check the equality given states as effect timing.
* *
@ -88,5 +98,6 @@ function isTimingEffectEqual(stateA, stateB) {
} }
exports.findOptimalTimeInterval = findOptimalTimeInterval; exports.findOptimalTimeInterval = findOptimalTimeInterval;
exports.hasPlayingAnimation = hasPlayingAnimation;
exports.isAllAnimationEqual = isAllAnimationEqual; exports.isAllAnimationEqual = isAllAnimationEqual;
exports.isTimingEffectEqual = isTimingEffectEqual; exports.isTimingEffectEqual = isTimingEffectEqual;