Bug 1456857 - Part 1: Pause/Play animations respectively. r?pbro,r?birtles
MozReview-Commit-ID: LNf65lh9ZqK
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -424,18 +424,32 @@ class AnimationInspector {
} finally {
this.setAnimationStateChangedListenerEnabled(true);
}
await this.updateState([...animations]);
}
async setAnimationsPlayState(doPlay) {
+ if (typeof this.hasPausePlaySome === "undefined") {
+ this.hasPausePlaySome =
+ await this.inspector.target.actorHasMethod("animations", "pauseSome");
+ }
+
try {
- if (doPlay) {
+ // If the server does not support pauseSome/playSome function, (which happens
+ // when connected to server older than FF62), use pauseAll/playAll instead.
+ // See bug 1456857.
+ if (this.hasPausePlaySome) {
+ if (doPlay) {
+ await this.animationsFront.playSome(this.state.animations);
+ } else {
+ await this.animationsFront.pauseSome(this.state.animations);
+ }
+ } else if (doPlay) {
await this.animationsFront.playAll();
} else {
await this.animationsFront.pauseAll();
}
await this.updateAnimations(this.state.animations);
} catch (e) {
// Expected if we've already been destroyed or other node have been selected
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -811,65 +811,82 @@ exports.AnimationsActor = protocol.Actor
this.allAnimationsPaused = false;
}
},
/**
* Pause all animations in the current tabActor's frames.
*/
pauseAll: function() {
- let readyPromises = [];
// Until the WebAnimations API provides a way to play/pause via the document
// timeline, we have to iterate through the whole DOM to find all players.
for (let player of
this.getAllAnimations(this.tabActor.window.document, true)) {
- player.pause();
- readyPromises.push(player.ready);
+ this.pauseSync(player);
}
this.allAnimationsPaused = true;
- return Promise.all(readyPromises);
},
/**
* Play all animations in the current tabActor's frames.
* This method only returns when animations have left their pending states.
*/
playAll: function() {
// Until the WebAnimations API provides a way to play/pause via the document
// timeline, we have to iterate through the whole DOM to find all players.
for (let player of
this.getAllAnimations(this.tabActor.window.document, true)) {
- // Play animation using startTime. Because the Animation.Play() method recalculates
- // startTime and currentTime on the procedure, but we need to keep the currentTime.
- player.startTime =
- player.timeline.currentTime - player.currentTime / player.playbackRate;
+ this.playSync(player);
}
this.allAnimationsPaused = false;
},
toggleAll: function() {
if (this.allAnimationsPaused) {
- return this.playAll();
+ this.playAll();
+ } else {
+ this.pauseAll();
}
- return this.pauseAll();
},
/**
* Toggle (play/pause) several animations at the same time.
* @param {Array} players A list of AnimationPlayerActor objects.
* @param {Boolean} shouldPause If set to true, the players will be paused,
* otherwise they will be played.
*/
toggleSeveral: function(players, shouldPause) {
return Promise.all(players.map(player => {
return shouldPause ? player.pause() : player.play();
}));
},
/**
+ * Pause given animations.
+ *
+ * @param {Array} actors A list of AnimationPlayerActor.
+ */
+ pauseSome: function(actors) {
+ for (const { player } of actors) {
+ this.pauseSync(player);
+ }
+ },
+
+ /**
+ * Play given animations.
+ *
+ * @param {Array} actors A list of AnimationPlayerActor.
+ */
+ playSome: function(actors) {
+ for (const { player } of actors) {
+ this.playSync(player);
+ }
+ },
+
+ /**
* Set the current time of several animations at the same time.
* @param {Array} players A list of AnimationPlayerActor.
* @param {Number} time The new currentTime.
* @param {Boolean} shouldPause Should the players be paused too.
* @param {Object} options
* - relativeToCreatedTime: Set current path with createdTime.
*/
setCurrentTimes: function(players, time, shouldPause, options) {
@@ -899,10 +916,42 @@ exports.AnimationsActor = protocol.Actor
* Set the playback rate of several animations at the same time.
* @param {Array} players A list of AnimationPlayerActor.
* @param {Number} rate The new rate.
*/
setPlaybackRates: function(players, rate) {
return Promise.all(
players.map(player => player.setPlaybackRate(rate))
);
- }
+ },
+
+ /**
+ * Pause given player synchronously.
+ *
+ * @param {Object} player
+ */
+ pauseSync(player) {
+ // Gecko includes an optimization that means that if the animation is play-pending
+ // and we set the startTime to null, the change will be ignored and the animation
+ // will continue to be play-pending. This violates the spec but until the spec is
+ // clarified[1] on this point we work around this by ensuring the animation's
+ // startTime is set to something non-null before setting it to null.
+ // [1] https://github.com/w3c/csswg-drafts/issues/2691
+ this.playSync(player);
+ player.startTime = null;
+ },
+
+ /**
+ * Play given player synchronously.
+ *
+ * @param {Object} player
+ */
+ playSync(player) {
+ if (!player.playbackRate) {
+ // We can not play with playbackRate zero.
+ return;
+ }
+
+ // Play animation in a synchronous fashion by setting the start time directly.
+ const currentTime = player.currentTime || 0;
+ player.startTime = player.timeline.currentTime - currentTime / player.playbackRate;
+ },
});
--- a/devtools/shared/specs/animation.js
+++ b/devtools/shared/specs/animation.js
@@ -132,16 +132,28 @@ const animationsSpec = generateActorSpec
},
toggleSeveral: {
request: {
players: Arg(0, "array:animationplayer"),
shouldPause: Arg(1, "boolean")
},
response: {}
},
+ pauseSome: {
+ request: {
+ players: Arg(0, "array:animationplayer"),
+ },
+ response: {}
+ },
+ playSome: {
+ request: {
+ players: Arg(0, "array:animationplayer"),
+ },
+ response: {}
+ },
setCurrentTimes: {
request: {
players: Arg(0, "array:animationplayer"),
time: Arg(1, "number"),
shouldPause: Arg(2, "boolean"),
relativeToCreatedTime: Option(3, "boolean"),
},
response: {}