Bug 1431573 - Part 10: Reflect to stop animation. r?gl
MozReview-Commit-ID: DZ4itacGnV4
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -6,27 +6,29 @@
const { AnimationsFront } = require("devtools/shared/fronts/animation");
const { createElement, createFactory } = require("devtools/client/shared/vendor/react");
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const EventEmitter = require("devtools/shared/event-emitter");
const App = createFactory(require("./components/App"));
+const CurrentTimeTimer = require("./current-time-timer");
const {
updateAnimations,
updateDetailVisibility,
updateElementPickerEnabled,
updateSelectedAnimation,
updateSidebarSize
} = require("./actions/animations");
const {
isAllAnimationEqual,
- hasPlayingAnimation,
+ hasAnimationIterationCountInfinite,
+ hasRunningAnimation,
} = require("./utils/utils");
class AnimationInspector {
constructor(inspector, win) {
this.inspector = inspector;
this.win = win;
this.addAnimationsCurrentTimeListener =
@@ -44,16 +46,17 @@ class AnimationInspector {
this.setAnimationsPlayState = this.setAnimationsPlayState.bind(this);
this.setDetailVisibility = this.setDetailVisibility.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.onAnimationsCurrentTimeUpdated = this.onAnimationsCurrentTimeUpdated.bind(this);
+ this.onCurrentTimeTimerUpdated = this.onCurrentTimeTimerUpdated.bind(this);
this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
this.onElementPickerStopped = this.onElementPickerStopped.bind(this);
this.onSidebarResized = this.onSidebarResized.bind(this);
this.onSidebarSelect = this.onSidebarSelect.bind(this);
EventEmitter.decorate(this);
this.emit = this.emit.bind(this);
@@ -256,16 +259,30 @@ class AnimationInspector {
onAnimationsCurrentTimeUpdated(currentTime) {
this.currentTime = currentTime;
for (const listener of this.animationsCurrentTimeListeners) {
listener(currentTime);
}
}
+ /**
+ * This method is called when the current time proceed by CurrentTimeTimer.
+ *
+ * @param {Number} currentTime
+ * @param {Bool} shouldStop
+ */
+ onCurrentTimeTimerUpdated(currentTime, shouldStop) {
+ if (shouldStop) {
+ this.setAnimationsCurrentTime(currentTime, true);
+ } else {
+ this.onAnimationsCurrentTimeUpdated(currentTime);
+ }
+ }
+
onElementPickerStarted() {
this.inspector.store.dispatch(updateElementPickerEnabled(true));
}
onElementPickerStopped() {
this.inspector.store.dispatch(updateElementPickerEnabled(false));
}
@@ -402,17 +419,23 @@ class AnimationInspector {
stopAnimationsCurrentTimeTimer() {
if (this.currentTimeTimer) {
this.currentTimeTimer.destroy();
this.currentTimeTimer = null;
}
}
startAnimationsCurrentTimeTimer() {
- const currentTimeTimer = new CurrentTimeTimer(this);
+ const timeScale = this.state.timeScale;
+ const shouldStopAfterEndTime =
+ !hasAnimationIterationCountInfinite(this.state.animations);
+
+ const currentTimeTimer =
+ new CurrentTimeTimer(timeScale, shouldStopAfterEndTime,
+ this.win, this.onCurrentTimeTimerUpdated);
currentTimeTimer.start();
this.currentTimeTimer = currentTimeTimer;
}
toggleElementPicker() {
this.inspector.toolbox.highlighterUtils.togglePicker();
}
@@ -446,50 +469,15 @@ class AnimationInspector {
await Promise.all(promises);
}
updateState(animations) {
this.stopAnimationsCurrentTimeTimer();
this.inspector.store.dispatch(updateAnimations(animations));
- if (hasPlayingAnimation(animations)) {
+ if (hasRunningAnimation(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;
- }
-}
-
module.exports = AnimationInspector;
--- a/devtools/client/inspector/animation/components/PauseResumeButton.js
+++ b/devtools/client/inspector/animation/components/PauseResumeButton.js
@@ -7,33 +7,33 @@
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");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { KeyCodes } = require("devtools/client/shared/keycodes");
const { getStr } = require("../utils/l10n");
-const { hasPlayingAnimation } = require("../utils/utils");
+const { hasRunningAnimation } = require("../utils/utils");
class PauseResumeButton extends PureComponent {
static get propTypes() {
return {
animations: PropTypes.arrayOf(PropTypes.object).isRequired,
setAnimationsPlayState: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
this.onKeyDown = this.onKeyDown.bind(this);
this.state = {
- isPlaying: false,
+ isRunning: false,
};
}
componentWillMount() {
this.updateState(this.props);
}
componentDidMount() {
@@ -51,43 +51,43 @@ class PauseResumeButton extends PureComp
}
getKeyEventTarget() {
return ReactDOM.findDOMNode(this).closest("#animation-container");
}
onToggleAnimationsPlayState() {
const { setAnimationsPlayState } = this.props;
- const { isPlaying } = this.state;
+ const { isRunning } = this.state;
- setAnimationsPlayState(!isPlaying);
+ setAnimationsPlayState(!isRunning);
}
onKeyDown(e) {
if (e.keyCode === KeyCodes.DOM_VK_SPACE) {
this.onToggleAnimationsPlayState();
e.preventDefault();
}
}
updateState() {
const { animations } = this.props;
- const isPlaying = hasPlayingAnimation(animations);
- this.setState({ isPlaying });
+ const isRunning = hasRunningAnimation(animations);
+ this.setState({ isRunning });
}
render() {
- const { isPlaying } = this.state;
+ const { isRunning } = this.state;
return dom.button(
{
className: "pause-resume-button devtools-button" +
- (isPlaying ? "" : " paused"),
+ (isRunning ? "" : " paused"),
onClick: this.onToggleAnimationsPlayState.bind(this),
- title: isPlaying ?
+ title: isRunning ?
getStr("timeline.resumedButtonTooltip") :
getStr("timeline.pausedButtonTooltip"),
}
);
}
}
module.exports = PauseResumeButton;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/current-time-timer.js
@@ -0,0 +1,75 @@
+/* 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";
+
+/**
+ * In animation inspector, the scrubber and the progress bar moves along the current time
+ * of animation. However, the processing which sync with actual animations is heavy since
+ * we have to communication by the actor. The role of this class is to make the pseudo
+ * current time in animation inspector to proceed.
+ */
+class CurrentTimeTimer {
+ /**
+ * Constructor.
+ *
+ * @param {Object} timeScale
+ * @param {Bool} shouldStopAfterEndTime
+ * If need to stop the timer after animation end time, set true.
+ * @param {window} win
+ * Be used for requestAnimationFrame and performance.
+ * @param {Function} onUpdated
+ * Listener function to get updating.
+ * This function is called with 2 parameters.
+ * 1st: current time
+ * 2nd: if shouldStopAfterEndTime is true and
+ * the current time is over the end time, true is given.
+ */
+ constructor(timeScale, shouldStopAfterEndTime, win, onUpdated) {
+ this.baseCurrentTime = timeScale.documentCurrentTime - timeScale.minStartTime;
+ this.endTime = timeScale.maxEndTime - timeScale.minStartTime;
+ this.timerStartTime = win.performance.now();
+
+ this.shouldStopAfterEndTime = shouldStopAfterEndTime;
+ this.onUpdated = onUpdated;
+ this.win = win;
+ this.next = this.next.bind(this);
+ }
+
+ destroy() {
+ this.stop();
+ this.onUpdated = null;
+ this.win = null;
+ }
+
+ /**
+ * Proceed the pseudo current time.
+ */
+ next() {
+ if (this.doStop) {
+ return;
+ }
+
+ const currentTime =
+ this.baseCurrentTime + this.win.performance.now() - this.timerStartTime;
+
+ if (this.endTime < currentTime && this.shouldStopAfterEndTime) {
+ this.onUpdated(this.endTime, true);
+ return;
+ }
+
+ this.onUpdated(currentTime);
+ this.win.requestAnimationFrame(this.next);
+ }
+
+ start() {
+ this.next();
+ }
+
+ stop() {
+ this.doStop = true;
+ }
+}
+
+module.exports = CurrentTimeTimer;
--- a/devtools/client/inspector/animation/moz.build
+++ b/devtools/client/inspector/animation/moz.build
@@ -7,10 +7,11 @@ DIRS += [
'components',
'reducers',
'utils'
]
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
DevToolsModules(
- 'animation.js'
+ 'animation.js',
+ 'current-time-timer.js'
)
--- a/devtools/client/inspector/animation/utils/utils.js
+++ b/devtools/client/inspector/animation/utils/utils.js
@@ -65,22 +65,33 @@ function isAllAnimationEqual(animationsA
return false;
}
}
return true;
}
/**
+ * Check whether or not the given list of animations has an iteration count of infinite.
+ *
+ * @param {Array} animations.
+ * @return {Boolean} true if there is an animation in the list of animations
+ * whose animation iteration count is infinite.
+ */
+function hasAnimationIterationCountInfinite(animations) {
+ return animations.some(({state}) => !state.iterationCount);
+}
+
+/**
* Check wether the animations are running at least one.
*
* @param {Array} animations.
- * @return {Boolean} true: playing
+ * @return {Boolean} true: running
*/
-function hasPlayingAnimation(animations) {
+function hasRunningAnimation(animations) {
return animations.some(({state}) => state.playState === "running");
}
/**
* Check the equality given states as effect timing.
*
* @param {Object} state of animation.
* @param {Object} same to avobe.
@@ -93,11 +104,12 @@ function isTimingEffectEqual(stateA, sta
stateA.easing === stateB.easing &&
stateA.endDelay === stateB.endDelay &&
stateA.fill === stateB.fill &&
stateA.iterationCount === stateB.iterationCount &&
stateA.iterationStart === stateB.iterationStart;
}
exports.findOptimalTimeInterval = findOptimalTimeInterval;
-exports.hasPlayingAnimation = hasPlayingAnimation;
+exports.hasAnimationIterationCountInfinite = hasAnimationIterationCountInfinite;
+exports.hasRunningAnimation = hasRunningAnimation;
exports.isAllAnimationEqual = isAllAnimationEqual;
exports.isTimingEffectEqual = isTimingEffectEqual;