Bug 1486278 - Workaround Bug 1495350 in video controls r=Gijs,birtles
authorTimothy Guan-tin Chien <timdream@gmail.com>
Tue, 02 Oct 2018 19:11:31 +0000
changeset 494979 4392b5198fb7773f6148d2caedff82da5f527bfe
parent 494978 cfa50dc1f61600b08d966210d0319b052ac86627
child 495004 0d4e73bc2cd705d7a021c75a0e8aeb174ab4db59
child 495005 5e5d6abb39afe0a23555db526a5bbbf3d59b4cd9
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, birtles
bugs1486278, 1495350
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1486278 - Workaround Bug 1495350 in video controls r=Gijs,birtles For compositor animations, we don't guarantee the finished promise callback to run in the first frame after the animation finishes. By setting fill: both, we set the animation to fill until the main thread has a chance to catch up. A filling animation has to be cancelled explicitly, otherwise the value of the animating style will be locked at the last frame of the animation. Differential Revision: https://phabricator.services.mozilla.com/D7317
toolkit/content/widgets/videocontrols.js
toolkit/content/widgets/videocontrols.xml
--- a/toolkit/content/widgets/videocontrols.js
+++ b/toolkit/content/widgets/videocontrols.js
@@ -1108,36 +1108,41 @@ this.VideoControlsImplPageWidget = class
         clickToPlay: {
           keyframes: [
             { transform: "scale(3)", opacity: 0 },
             { transform: "scale(1)", opacity: 0.55 },
           ],
           options: {
             easing: "ease",
             duration: 400,
+             // The fill mode here and below is a workaround to avoid flicker
+             // due to bug 1495350.
+             fill: "both",
           },
         },
         controlBar: {
           keyframes: [
             { opacity: 0 },
             { opacity: 1 },
           ],
           options: {
             easing: "ease",
             duration: 200,
+            fill: "both",
           },
         },
         statusOverlay: {
           keyframes: [
             { opacity: 0 },
             { opacity: 0, offset: .72 }, // ~750ms into animation
             { opacity: 1 },
           ],
           options: {
             duration: 1050,
+            fill: "both",
           },
         },
       },
 
       startFade(element, fadeIn, immediate = false) {
         let animationProp =
           this.animationProps[element.id];
         if (!animationProp) {
@@ -1198,17 +1203,17 @@ this.VideoControlsImplPageWidget = class
             animation.cancel();
             finishedPromise = Promise.resolve();
           } else {
             switch (animation.playState) {
               case "idle":
               case "finished":
                 // There is no animation currently playing.
                 // Schedule a new animation with the desired playback direction.
-                animation.updatePlaybackRate(fadeIn ? 1 : -1);
+                animation.playbackRate = fadeIn ? 1 : -1;
                 animation.play();
                 break;
               case "running":
                 // Allow the animation to play from its current position in
                 // reverse to finish.
                 animation.reverse();
                 break;
               case "pause":
@@ -1217,24 +1222,31 @@ this.VideoControlsImplPageWidget = class
                 throw new Error("Unknown Animation playState: " + animation.playState);
             }
             finishedPromise = animation.finished;
           }
         } else { // immediate
           animation.cancel();
           finishedPromise = Promise.resolve();
         }
-        finishedPromise.then(() => {
+        finishedPromise.then(animation => {
           if (element == this.controlBar) {
             this.onControlBarAnimationFinished();
           }
           element.classList.remove(fadeIn ? "fadein" : "fadeout");
           if (!fadeIn) {
             element.hidden = true;
           }
+          if (animation) {
+            // Explicitly clear the animation effect so that filling animations
+            // stop overwriting stylesheet styles. Remove when bug 1495350 is
+            // fixed and animations are no longer filling animations.
+            // This also stops them from accumulating (See bug 1253476).
+            animation.cancel();
+          }
         }, () => { /* Do nothing on rejection */ });
       },
 
       _triggeredByControls: false,
 
       startPlay() {
         this._triggeredByControls = true;
         this.hideClickToPlay();
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -1099,36 +1099,41 @@
         clickToPlay: {
           keyframes: [
             { transform: "scale(3)", opacity: 0 },
             { transform: "scale(1)", opacity: 0.55 },
           ],
           options: {
             easing: "ease",
             duration: 400,
+            // The fill mode here and below is a workaround to avoid flicker
+            // due to bug 1495350.
+            fill: "both",
           },
         },
         controlBar: {
           keyframes: [
             { opacity: 0 },
             { opacity: 1 },
           ],
           options: {
             easing: "ease",
             duration: 200,
+            fill: "both",
           },
         },
         statusOverlay: {
           keyframes: [
             { opacity: 0 },
             { opacity: 0, offset: .72 }, // ~750ms into animation
             { opacity: 1 },
           ],
           options: {
             duration: 1050,
+            fill: "both",
           },
         },
       },
 
       startFade(element, fadeIn, immediate = false) {
         // Bug 493523, the scrubber doesn't call valueChanged while hidden,
         // so our dependent state (eg, timestamp in the thumb) will be stale.
         // As a workaround, update it manually when it first becomes unhidden.
@@ -1193,17 +1198,17 @@
             animation.cancel();
             finishedPromise = Promise.resolve();
           } else {
             switch (animation.playState) {
               case "idle":
               case "finished":
                 // There is no animation currently playing.
                 // Schedule a new animation with the desired playback direction.
-                animation.updatePlaybackRate(fadeIn ? 1 : -1);
+                animation.playbackRate = fadeIn ? 1 : -1;
                 animation.play();
                 break;
               case "running":
                 // Allow the animation to play from its current position in
                 // reverse to finish.
                 animation.reverse();
                 break;
               case "pause":
@@ -1211,24 +1216,31 @@
                 throw new Error("Unknown Animation playState: " + animation.playState);
             }
             finishedPromise = animation.finished;
           }
         } else { // immediate
           animation.cancel();
           finishedPromise = Promise.resolve();
         }
-        finishedPromise.then(() => {
+        finishedPromise.then(animation => {
           if (element == this.controlBar) {
             this.onControlBarAnimationFinished();
           }
           element.classList.remove(fadeIn ? "fadein" : "fadeout");
           if (!fadeIn) {
             element.hidden = true;
           }
+          if (animation) {
+            // Explicitly clear the animation effect so that filling animations
+            // stop overwriting stylesheet styles. Remove when bug 1495350 is
+            // fixed and animations are no longer filling animations.
+            // This also stops them from accumulating (See bug 1253476).
+            animation.cancel();
+          }
         }, () => { /* Do nothing on rejection */ });
       },
 
       _triggeredByControls: false,
 
       startPlay() {
         this._triggeredByControls = true;
         this.hideClickToPlay();