Bug 1449532 - Part II, Use Web Animation API to animate video control transition r=Gijs
authorTimothy Guan-tin Chien <timdream@gmail.com>
Fri, 30 Mar 2018 00:21:03 +0800
changeset 411645 62e9d302879c70dd6e379c7620d642ad011feb39
parent 411644 c6e628dc585f3d7f91016f7b3f87e790c0a7a8a6
child 411646 64486670492f5c9cc2e890c5931284bb6a85d194
push id33764
push usercsabou@mozilla.com
push dateWed, 04 Apr 2018 17:53:18 +0000
treeherdermozilla-central@90eb45ff0a64 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1449532, 1374007, 1319301
milestone61.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 1449532 - Part II, Use Web Animation API to animate video control transition r=Gijs Web Animation API should give us deterministic timing when the transition ends or aborts. Additional clean-ups: - Make sure hidden status is always set/get from the hidden property, instead of the hidden attribute. - Remove the unused isControlBarHidden property. - controlsSpacer no longer has a background color (removed in bug 1374007), therefore it no longer needs a transition and there is no need to test its state with the test added in bug 1319301. - Fix a logic error at hideByAdjustment property, revealed by the changed transition timing, in which adjustControlSize() would show the controlBar set hidden by the transition. MozReview-Commit-ID: DB2cgQcUEXi
toolkit/content/moz.build
toolkit/content/tests/widgets/mochitest.ini
toolkit/content/tests/widgets/test_bug1319301.html
toolkit/content/tests/widgets/test_videocontrols_vtt.html
toolkit/content/widgets/videocontrols.xml
toolkit/themes/shared/media/videocontrols.css
--- a/toolkit/content/moz.build
+++ b/toolkit/content/moz.build
@@ -177,18 +177,16 @@ with Files('tests/reftests/*videocontrol
     BUG_COMPONENT = ('Toolkit', 'Video/Audio Controls')
 
 with Files('tests/unit/**'):
     BUG_COMPONENT = ('Toolkit', 'General')
 
 
 with Files('tests/widgets/*audiocontrols*'):
     BUG_COMPONENT = ('Toolkit', 'Video/Audio Controls')
-with Files('tests/widgets/*1319301*'):
-    BUG_COMPONENT = ('Toolkit', 'Video/Audio Controls')
 with Files('tests/widgets/*898940*'):
     BUG_COMPONENT = ('Toolkit', 'Video/Audio Controls')
 
 with Files('tests/widgets/*contextmenu*'):
     BUG_COMPONENT = ('Firefox', 'Menus')
 
 with Files('tests/widgets/*editor*'):
     BUG_COMPONENT = ('Core', 'XP Toolkit/Widgets: XUL')
--- a/toolkit/content/tests/widgets/mochitest.ini
+++ b/toolkit/content/tests/widgets/mochitest.ini
@@ -36,13 +36,12 @@ skip-if = toolkit == 'android'
 [test_videocontrols_audio_direction.html]
 [test_videocontrols_jsdisabled.html]
 skip-if = toolkit == 'android' # bug 1272646
 [test_videocontrols_standalone.html]
 skip-if = toolkit == 'android' # bug 1075573
 [test_videocontrols_video_direction.html]
 skip-if = os == 'win'
 [test_videocontrols_video_noaudio.html]
-[test_bug1319301.html]
 [test_bug898940.html]
 [test_videocontrols_error.html]
 [test_videocontrols_orientation.html]
 run-if = toolkit == 'android'
deleted file mode 100644
--- a/toolkit/content/tests/widgets/test_bug1319301.html
+++ /dev/null
@@ -1,42 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test - bug 1319301</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <video id="video" controls preload="auto"></video>
-</div>
-
-<pre id="test">
-<script clas="testbody" type="application/javascript">
-  const video = document.getElementById("video");
-  const controlsSpacer = getAnonElementWithinVideoByAttribute(video, "anonid", "controlsSpacer");
-
-  add_task(async function setup() {
-    await new Promise(resolve => window.addEventListener("load", resolve));
-    await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
-  });
-
-  add_task(async function play_fadeout() {
-    isnot(controlsSpacer.getAttribute("fadeout"), "true", "controlsSpacer should not fadeout before playing");
-
-    await new Promise(resolve => {
-      video.addEventListener("canplaythrough", video.play);
-      video.addEventListener("play", () => SimpleTest.executeSoon(resolve));
-      video.src = "seek_with_sound.ogg";
-    });
-
-    is(controlsSpacer.getAttribute("fadeout"), "true", "controlsSpacer should fadeout once video starts playing");
-  });
-</script>
-</pre>
-</body>
-</html>
--- a/toolkit/content/tests/widgets/test_videocontrols_vtt.html
+++ b/toolkit/content/tests/widgets/test_videocontrols_vtt.html
@@ -28,83 +28,83 @@
     await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
     await new Promise(resolve => {
       video.src = "seek_with_sound.ogg";
       video.addEventListener("loadedmetadata", resolve);
     });
   });
 
   add_task(async function check_inital_state() {
-    is(ccBtn.getAttribute("hidden"), "true", "CC button should hide");
+    ok(ccBtn.hidden, "CC button should hide");
   });
 
   add_task(async function check_unsupported_type_added() {
     video.addTextTrack("descriptions", "English", "en");
     video.addTextTrack("chapters", "English", "en");
     video.addTextTrack("metadata", "English", "en");
 
     await new Promise(SimpleTest.executeSoon);
-    is(ccBtn.getAttribute("hidden"), "true", "CC button should hide if no supported tracks provided");
+    ok(ccBtn.hidden, "CC button should hide if no supported tracks provided");
   });
 
   add_task(async function check_cc_button_present() {
     const sub = video.addTextTrack("subtitles", "English", "en");
     sub.mode = "disabled";
 
     await new Promise(SimpleTest.executeSoon);
-    is(ccBtn.hasAttribute("hidden"), false, "CC button should show");
+    ok(!ccBtn.hidden, "CC button should show");
     is(ccBtn.hasAttribute("enabled"), false, "CC button should be disabled");
   });
 
   add_task(async function check_cc_button_be_enabled() {
     const subtitle = video.addTextTrack("subtitles", "English", "en");
     subtitle.mode = "showing";
 
     await new Promise(SimpleTest.executeSoon);
-    is(ccBtn.getAttribute("enabled"), "true", "CC button should be enabled");
+    ok(ccBtn.hasAttribute("enabled"), "CC button should be enabled");
     subtitle.mode = "disabled";
   });
 
   add_task(async function check_cpations_type() {
     const caption = video.addTextTrack("captions", "English", "en");
     caption.mode = "showing";
 
     await new Promise(SimpleTest.executeSoon);
-    is(ccBtn.getAttribute("enabled"), "true", "CC button should be enabled");
+    ok(ccBtn.hasAttribute("enabled"), "CC button should be enabled");
   });
 
   add_task(async function check_track_ui_state() {
     synthesizeMouseAtCenter(ccBtn, {});
 
     await new Promise(SimpleTest.executeSoon);
-    is(ttList.hasAttribute("hidden"), false, "Texttrack menu should show up");
-    is(ttList.lastChild.getAttribute("on"), "true", "The last added item should be highlighted");
+    ok(!ttList.hidden, "Texttrack menu should show up");
+    ok(ttList.lastChild.hasAttribute("on"), "The last added item should be highlighted");
   });
 
   add_task(async function check_select_texttrack() {
     const tt = ttList.children[1];
 
-    isnot(tt.getAttribute("on"), "true", "Item should be off before click");
+    ok(!tt.hasAttribute("on"), "Item should be off before click");
     synthesizeMouseAtCenter(tt, {});
 
     await new Promise(SimpleTest.executeSoon);
-    is(tt.getAttribute("on"), "true", "Selected item should be enabled");
-    is(ttList.getAttribute("hidden"), "true", "Should hide texttrack menu once clicked on an item");
+    ok(tt.hasAttribute("on"), "Selected item should be enabled");
+    ok(ttList.hidden, "Should hide texttrack menu once clicked on an item");
   });
 
   add_task(async function check_change_texttrack_mode() {
     const tts = [...video.textTracks];
 
     tts.forEach(tt => tt.mode = "hidden");
     await new Promise(SimpleTest.executeSoon);
-    is(ccBtn.hasAttribute("enabled"), false, "CC button should be disabled");
+    ok(!ccBtn.hasAttribute("enabled"), "CC button should be disabled");
 
     // enable the last text track.
     tts[tts.length - 1].mode = "showing";
     await new Promise(SimpleTest.executeSoon);
-    is(ccBtn.hasAttribute("enabled"), true, "CC button should be enabled");
-    is(ttList.lastChild.getAttribute("on"), "true", "The last item should be highlighted");
+    ok(ccBtn.hasAttribute("enabled"), "CC button should be enabled");
+    ok(ttList.lastChild.hasAttribute("on"), "The last item should be highlighted");
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -133,22 +133,16 @@
           this.video.style.height = this.controlBarMinHeight + "px";
           this.video.style.width = "66%";
         } else {
           this.video.style.removeProperty("height");
           this.video.style.removeProperty("width");
         }
       },
 
-      get isControlBarHidden() {
-        return this.controlBar.hidden ||
-               this.controlBar.hideByAdjustment ||
-               this.controlBar.getAttribute("fadeout") === "true";
-      },
-
       suppressError: false,
 
       setupStatusFader(immediate) {
         // Since the play button will be showing, we don't want to
         // show the throbber behind it. The throbber here will
         // only show if needed after the play button has been pressed.
         if (!this.clickToPlay.hidden) {
           this.startFadeOut(this.statusOverlay, true);
@@ -293,31 +287,46 @@
             modifier: {
               value: "",
               writable: true
             },
             isWanted: {
               value: true,
               writable: true
             },
-            hideByAdjustment: {
+            hidden: {
               set: (v) => {
-                if (v) {
-                  control.setAttribute("hidden", "true");
-                } else {
-                  control.removeAttribute("hidden");
-                }
-
+                control._isHiddenExplicitly = v;
+                control._updateHiddenAttribute();
+              },
+              get: () => control.hasAttribute("hidden")
+            },
+            hiddenByAdjustment: {
+              set: (v) => {
                 control._isHiddenByAdjustment = v;
+                control._updateHiddenAttribute();
               },
               get: () => control._isHiddenByAdjustment
             },
             _isHiddenByAdjustment: {
               value: false,
               writable: true
+            },
+            _isHiddenExplicitly: {
+              value: false,
+              writable: true
+            },
+            _updateHiddenAttribute: {
+              value: () => {
+                if (control._isHiddenExplicitly || control._isHiddenByAdjustment) {
+                  control.setAttribute("hidden", "");
+                } else {
+                  control.removeAttribute("hidden");
+                }
+              }
             }
           });
         }
         this.adjustControlSize();
 
         // Can only update the volume controls once we've computed
         // _volumeControlWidth, since the volume slider implementation
         // depends on it.
@@ -337,17 +346,16 @@
         // (Note: the |controls| attribute is already handled via layout/style/html.css)
         var shouldShow = !this.dynamicControls ||
                          (this.video.paused &&
                          !this.video.autoplay);
         // Hide the overlay if the video time is non-zero or if an error occurred to workaround bug 718107.
         let shouldClickToPlayShow = shouldShow && !this.isAudioOnly &&
                                     this.video.currentTime == 0 && !this.hasError();
         this.startFade(this.clickToPlay, shouldClickToPlayShow, true);
-        this.startFade(this.controlsSpacer, shouldClickToPlayShow, true);
         this.startFade(this.controlBar, shouldShow, true);
       },
 
       get dynamicControls() {
         // Don't fade controls for <audio> elements.
         var enabled = !this.isAudioOnly;
 
         // Allow tests to explicitly suppress the fading of controls.
@@ -393,18 +401,18 @@
        * is completed.
        */
       SHOW_THROBBER_TIMEOUT_MS: 250,
       _showThrobberTimer: null,
       _delayShowThrobberWhileResumingVideoDecoder() {
         this._showThrobberTimer = setTimeout(() => {
           this.statusIcon.setAttribute("type", "throbber");
           // Show the throbber immediately since we have waited for SHOW_THROBBER_TIMEOUT_MS.
-          // We don't want to wait for another transition-delay(750ms) and the
-          // transition-duration(300ms).
+          // We don't want to wait for another animation delay(750ms) and the
+          // animation duration(300ms).
           this.setupStatusFader(true);
         }, this.SHOW_THROBBER_TIMEOUT_MS);
       },
       _cancelShowThrobberWhileResumingVideoDecoder() {
         if (this._showThrobberTimer) {
           clearTimeout(this._showThrobberTimer);
           this._showThrobberTimer = null;
         }
@@ -424,17 +432,16 @@
           case "play":
             this.setPlayButtonState(false);
             this.setupStatusFader();
             if (!this._triggeredByControls && this.dynamicControls && this.videocontrols.isTouchControls) {
               this.startFadeOut(this.controlBar);
             }
             if (!this._triggeredByControls) {
               this.clickToPlay.hidden = true;
-              this.controlsSpacer.setAttribute("fadeout", "true");
             }
             this._triggeredByControls = false;
             break;
           case "pause":
             // Little white lie: if we've internally paused the video
             // while dragging the scrubber, don't change the button state.
             if (!this.scrubber.isDragging) {
               this.setPlayButtonState(true);
@@ -964,81 +971,131 @@
           this.adjustControlSize();
 
           // Keep the controls visible if the click-to-play is visible.
           if (!this.clickToPlay.hidden) {
             return;
           }
 
           this.startFadeOut(this.controlBar, false);
-          this.textTrackList.setAttribute("hidden", "true");
+          this.textTrackList.hidden = true;
           clearTimeout(this._showControlsTimeout);
           Utils._controlsHiddenByTimeout = false;
         }
       },
 
       startFadeIn(element, immediate) {
         this.startFade(element, true, immediate);
       },
 
       startFadeOut(element, immediate) {
         this.startFade(element, false, immediate);
       },
 
-      startFade(element, fadeIn, immediate) {
-        if (element.classList.contains("controlBar") && fadeIn) {
-          // 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.
-          if (element.hidden) {
-            this.scrubber.value = this.video.currentTime * 1000;
+      animationMap: new WeakMap(),
+
+      animationProps: {
+        clickToPlay: {
+          keyframes: [
+            { transform: "scale(3)", opacity: 0 },
+            { transform: "scale(1)", opacity: 0.55 }
+          ],
+          options: {
+            duration: 400
+          }
+        },
+        controlBar: {
+          keyframes: [
+            { opacity: 0 },
+            { opacity: 1 }
+          ],
+          options: {
+            duration: 200
+          }
+        },
+        statusOverlay: {
+          keyframes: [
+            { opacity: 0 },
+            { opacity: 0, offset: .72 }, // ~750ms into animation
+            { opacity: 1 }
+          ],
+          options: {
+            duration: 1050
           }
         }
+      },
 
-        if (immediate) {
-          element.setAttribute("immediate", true);
-        } else {
-          element.removeAttribute("immediate");
+      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.
+        if (element == this.controlBar && fadeIn && element.hidden) {
+          this.scrubber.value = this.video.currentTime * 1000;
+        }
+
+        let animationProp =
+          this.animationProps[element.getAttribute("anonid")];
+        if (!animationProp) {
+          throw new Error("Element " + element.getAttribute("anonid") +
+            " has no transition. Toggle the hidden property directly.");
+        }
+
+        let animation = this.animationMap.get(element);
+        if (!animation) {
+          animation = new Animation(new KeyframeEffect(
+            element, animationProp.keyframes, animationProp.options));
+
+          this.animationMap.set(element, animation);
         }
 
         if (fadeIn) {
           // hidden state should be controlled by adjustControlSize
-          if (!(element.isAdjustableControl && element.hideByAdjustment)) {
-            element.hidden = false;
+          if (element.isAdjustableControl && element.hiddenByAdjustment) {
+            return;
+          }
+
+          // No need to fade in again if the element is visible and not fading out
+          if (!element.hidden && !element.classList.contains("fadeout")) {
+            return;
           }
-          // force style resolution, so that transition begins
-          // when we remove the attribute.
-          element.clientTop;
-          element.removeAttribute("fadeout");
-          if (element.classList.contains("controlBar")) {
-            this.controlsSpacer.removeAttribute("hideCursor");
+
+          // Unhide
+          element.hidden = false;
+        } else {
+          // No need to fade out if the element is already no visible.
+          if (element.hidden) {
+            return;
           }
-        } else {
-          element.setAttribute("fadeout", true);
-          if (element.classList.contains("controlBar") && !this.hasError() &&
+
+          if (element == this.controlBar && !this.hasError() &&
               document.mozFullScreenElement == this.video) {
             this.controlsSpacer.setAttribute("hideCursor", true);
           }
         }
-      },
 
-      onTransitionEnd(event) {
-        // Ignore events for things other than opacity changes.
-        if (event.propertyName != "opacity") {
-          return;
+        element.classList.toggle("fadeout", !fadeIn);
+        element.classList.toggle("fadein", fadeIn);
+        let finishedPromise;
+        if (!immediate) {
+          animation.playbackRate = fadeIn ? 1 : -1;
+          animation.play();
+          finishedPromise = animation.finished;
+        } else {
+          animation.cancel();
+          finishedPromise = Promise.resolve();
         }
-
-        var element = event.originalTarget;
-
-        // Nothing to do when a fade *in* finishes.
-        if (!element.hasAttribute("fadeout")) {
-          return;
-        }
-
-        element.hidden = true;
+        finishedPromise.then(() => {
+          if (element == this.controlBar) {
+            this.onControlBarAnimationFinished();
+          }
+          element.classList.remove(fadeIn ? "fadein" : "fadeout");
+          if (!fadeIn) {
+            element.hidden = true;
+          }
+        }, () => { /* Do nothing on rejection */ });
       },
 
       _triggeredByControls: false,
 
       startPlay() {
         this._triggeredByControls = true;
         this.hideClickToPlay();
         this.video.play();
@@ -1186,25 +1243,19 @@
         let videoWidth = this.video.clientWidth;
 
         // The play button will animate to 3x its size. This
         // shows the animation unless the video is too small
         // to show 2/3 of the animation.
         let animationScale = 2;
         let animationMinSize = this.clickToPlay.minWidth * animationScale;
 
-        if (animationMinSize > videoWidth ||
-            animationMinSize > (videoHeight - this.controlBarMinHeight)) {
-          this.clickToPlay.setAttribute("immediate", "true");
-          this.clickToPlay.hidden = true;
-        } else {
-          this.clickToPlay.removeAttribute("immediate");
-        }
-        this.clickToPlay.setAttribute("fadeout", "true");
-        this.controlsSpacer.setAttribute("fadeout", "true");
+        let immediate = (animationMinSize > videoWidth ||
+            animationMinSize > (videoHeight - this.controlBarMinHeight));
+        this.startFadeOut(this.clickToPlay, immediate);
       },
 
       setPlayButtonState(aPaused) {
         if (aPaused) {
           this.playButton.setAttribute("paused", "true");
         } else {
           this.playButton.removeAttribute("paused");
         }
@@ -1481,34 +1532,34 @@
         for (let tt of this.overlayableTextTracks) {
           if (tt.index === index) {
             tt.mode = "showing";
           } else {
             tt.mode = "disabled";
           }
         }
 
-        this.textTrackList.setAttribute("hidden", "true");
+        this.textTrackList.hidden = true;
       },
 
-      onControlBarTransitioned() {
-        this.textTrackList.setAttribute("hidden", "true");
+      onControlBarAnimationFinished() {
+        this.textTrackList.hidden = true;
         this.video.dispatchEvent(new CustomEvent("controlbarchange"));
         this.adjustControlSize();
       },
 
       toggleCasting() {
         this.videocontrols.dispatchEvent(new CustomEvent("VideoBindingCast"));
       },
 
       toggleClosedCaption() {
-        if (this.textTrackList.hasAttribute("hidden")) {
-          this.textTrackList.removeAttribute("hidden");
+        if (this.textTrackList.hidden) {
+          this.textTrackList.hidden = false;
         } else {
-          this.textTrackList.setAttribute("hidden", "true");
+          this.textTrackList.hidden = true;
         }
       },
 
       onTextTrackAdd(trackEvent) {
         this.addNewTextTrack(trackEvent.track);
         this.setClosedCaptionButtonState();
       },
 
@@ -1600,24 +1651,24 @@
         let videoHeight = this.isAudioOnly ? this.controlBarMinHeight : givenHeight;
         let videocontrolsWidth = this.videocontrols.clientWidth;
 
         let widthUsed = minControlBarPaddingWidth;
         let preventAppendControl = false;
 
         for (let control of this.prioritizedControls) {
           if (!control.isWanted) {
-            control.hideByAdjustment = true;
+            control.hiddenByAdjustment = true;
             continue;
           }
 
-          control.hideByAdjustment = preventAppendControl ||
+          control.hiddenByAdjustment = preventAppendControl ||
           widthUsed + control.minWidth > videoWidth;
 
-          if (control.hideByAdjustment) {
+          if (control.hiddenByAdjustment) {
             preventAppendControl = true;
           } else {
             widthUsed += control.minWidth;
           }
         }
 
         // Use flexible spacer to separate controls when scrubber is hidden.
         // As long as muteButton hidden, which means only play button presents,
@@ -1642,34 +1693,34 @@
             this.controlBar.style.width = `${videoWidth}px`;
           }
           return;
         }
 
         if (videoHeight < this.controlBarMinHeight ||
             widthUsed === minControlBarPaddingWidth) {
           this.controlBar.setAttribute("size", "hidden");
-          this.controlBar.hideByAdjustment = true;
+          this.controlBar.hiddenByAdjustment = true;
         } else {
           this.controlBar.removeAttribute("size");
-          this.controlBar.hideByAdjustment = false;
+          this.controlBar.hiddenByAdjustment = false;
         }
 
         // Adjust clickToPlayButton size.
         const minVideoSideLength = Math.min(videoWidth, videoHeight);
         const clickToPlayViewRatio = 0.15;
         const clickToPlayScaledSize = Math.max(
         this.clickToPlay.minWidth, minVideoSideLength * clickToPlayViewRatio);
 
         if (clickToPlayScaledSize >= videoWidth ||
            (clickToPlayScaledSize + this.controlBarMinHeight / 2 >= videoHeight / 2 )) {
-          this.clickToPlay.hideByAdjustment = true;
+          this.clickToPlay.hiddenByAdjustment = true;
         } else {
           if (this.clickToPlay.hidden && !this.video.played.length && this.video.paused) {
-            this.clickToPlay.hideByAdjustment = false;
+            this.clickToPlay.hiddenByAdjustment = false;
           }
           this.clickToPlay.style.width = `${clickToPlayScaledSize}px`;
           this.clickToPlay.style.height = `${clickToPlayScaledSize}px`;
         }
       },
 
       init(binding) {
         this.video = binding.parentNode;
@@ -1774,18 +1825,16 @@
         // On touch videocontrols, tapping controlsSpacer should show/hide
         // the control bar, instead of playing the video or toggle fullscreen.
         if (!this.videocontrols.isTouchControls) {
           addListener(this.controlsSpacer, "click", this.clickToPlayClickHandler);
           addListener(this.controlsSpacer, "dblclick", this.toggleFullscreen);
         }
 
         addListener(this.videocontrols, "resizevideocontrols", this.adjustControlSize);
-        addListener(this.videocontrols, "transitionend", this.onTransitionEnd);
-        addListener(this.controlBar, "transitionend", this.onControlBarTransitioned);
         // See comment at onFullscreenChange on bug 718107.
         // addListener(this.video.ownerDocument, "fullscreenchange", this.onFullscreenChange);
         addListener(this.video, "keypress", this.keyHandler, {capture: true});
         // Prevent any click event within media controls from dispatching through to video.
         addListener(this.videocontrols, "click", function(event) {
           event.stopPropagation();
         }, {mozSystemGroup: false});
         addListener(this.videocontrols, "dragstart", function(event) {
@@ -1818,17 +1867,17 @@
       controlsTimeout: 5000,
 
       get Utils() {
         return this.videocontrols.Utils;
       },
 
       get visible() {
         return !this.Utils.controlBar.hasAttribute("fadeout") &&
-               !(this.Utils.controlBar.getAttribute("hidden") == "true");
+               !(this.Utils.controlBar.hidden);
       },
 
       _firstShow: false,
       get firstShow() { return this._firstShow; },
       set firstShow(val) {
         this._firstShow = val;
         this.Utils.controlBar.setAttribute("firstshow", val);
       },
--- a/toolkit/themes/shared/media/videocontrols.css
+++ b/toolkit/themes/shared/media/videocontrols.css
@@ -60,17 +60,17 @@ audio > xul|videocontrols {
 
 .touch .controlBar {
   /* Do not delete: these variables are accessed by JavaScript directly.
      see videocontrols.xml and search for |-width|. */
   --scrubberStack-width: 84px;
   --volumeStack-width: 64px;
 }
 
-.controlsContainer [hidden="true"],
+.controlsContainer [hidden],
 .controlBar[hidden] {
   display: none;
 }
 
 .controlBar[size="hidden"] {
   display: none;
 }
 
@@ -444,51 +444,16 @@ audio > xul|videocontrols {
 .controlsSpacerStack:hover > .clickToPlay[fadeout] {
   opacity: 0;
 }
 
 .controlBar[fullscreen-unavailable] .fullscreenButton {
   display: none;
 }
 
-/* CSS Transitions */
-.clickToPlay {
-  transition-property: transform, opacity;
-  transition-duration: 400ms, 400ms;
-}
-
-.controlsSpacer[fadeout] {
-  opacity: 0;
-}
-
-.clickToPlay[fadeout] {
-  transform: scale(3);
-  opacity: 0;
-}
-
-.clickToPlay[fadeout][immediate] {
-  transition-property: opacity, background-size;
-  transition-duration: 0s, 0s;
-}
-.controlBar:not([immediate]) {
-  transition-property: opacity;
-  transition-duration: 200ms;
-}
-.controlBar[fadeout] {
-  opacity: 0;
-}
-.volumeStack:not([immediate]) {
-  transition-property: opacity, margin-top;
-  transition-duration: 200ms, 200ms;
-}
-.statusOverlay:not([immediate]) {
-  transition-property: opacity;
-  transition-duration: 300ms;
-  transition-delay: 750ms;
-}
 .statusOverlay[fadeout],
 .statusOverlay[error] + .controlsOverlay > .controlsSpacerStack {
   opacity: 0;
 }
 
 /* Error description formatting */
 .errorLabel {
   padding: 0 10px;