Bug 1456857 - Part 1: Pause/Play animations respectively. r?pbro,r?birtles draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Tue, 22 May 2018 17:09:01 +0900
changeset 797969 f07f6c63f27b705df4544ce0f9c6abb36b36ab13
parent 797675 0f27044e62d2049cebcee614243aaed187c6e64a
child 797970 c9e335cddbf92410efbed3833ba7302521af9677
push id110652
push userbmo:dakatsuka@mozilla.com
push dateTue, 22 May 2018 08:13:26 +0000
reviewerspbro, birtles
bugs1456857
milestone62.0a1
Bug 1456857 - Part 1: Pause/Play animations respectively. r?pbro,r?birtles MozReview-Commit-ID: LNf65lh9ZqK
devtools/client/inspector/animation/animation.js
devtools/server/actors/animation.js
devtools/shared/specs/animation.js
--- 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: {}