Bug 1362146 - Rewrite media controls adjustment that always show audio controls instead of hiding entirely when the given height is smaller than 40px and avoid recursively calling reflow handling. draft
authorRay Lin <ralin@mozilla.com>
Sat, 03 Jun 2017 23:51:25 +0800
changeset 588745 c4a764852b6b2218e61cf428ed79eb3cc59c2f83
parent 588052 aeb3d0ca558f034cbef1c5a68bd07dd738611494
child 631663 067407dc519ebd80430b9bc97b2aad90729adb78
push id62139
push userbmo:ralin@mozilla.com
push dateSun, 04 Jun 2017 10:42:51 +0000
bugs1362146
milestone55.0a1
Bug 1362146 - Rewrite media controls adjustment that always show audio controls instead of hiding entirely when the given height is smaller than 40px and avoid recursively calling reflow handling. MozReview-Commit-ID: Gp0nOAWeEeY
toolkit/content/widgets/videocontrols.xml
toolkit/themes/shared/media/videocontrols.css
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -383,29 +383,17 @@
             isAdjustableControl: {
               value: true
             },
             isWanted: {
               value: true,
               writable: true
             },
             resized: {
-              value: false,
-              writable: true
-            },
-            resizedHandler: {
-              value: () => {
-                let width = control.clientWidth;
-
-                if (width === 0) {
-                  return;
-                }
-
-                control.minWidth = width;
-              },
+              value: true,
               writable: true
             },
             hideByAdjustment: {
               set: (v) => {
                 if (v) {
                   control.setAttribute("hidden", "true");
                 } else {
                   control.removeAttribute("hidden");
@@ -421,26 +409,23 @@
             }
           });
         }
         // Cannot get minimal width of flexible scrubber and clickToPlay.
         // Rewrite to empirical value for now.
         this.scrubberStack.minWidth = 64;
         this.volumeStack.minWidth = 48;
         this.clickToPlay.minWidth = 48;
-
-        if (this.positionDurationBox) {
-          this.positionDurationBox.resizedHandler = () => {
-            let durationWidth = this.durationSpan.hideByAdjustment ? 0 : this.durationSpan.clientWidth;
-
-            this.positionDurationBox.minWidth = this.positionDurationBox.clientWidth - durationWidth;
-          };
-
-          this.positionDurationBox.resized = true;
-        }
+        this.scrubberStack.resized = false;
+        this.volumeStack.resized = false;
+        this.clickToPlay.resized = false;
+        // durationLabel and positionLabel are no longer be needed for
+        // desktop media controls adjustment.
+        this.durationLabel.resized = false;
+        this.positionLabel.resized = false;
 
         this.adjustControlSize();
         this.controlBar.hidden = true;
 
         // Can only update the volume controls once we've computed
         // _volumeControlWidth, since the volume slider implementation
         // depends on it.
         this.updateVolumeControls();
@@ -1172,36 +1157,16 @@
         }
 
         if (this.videocontrols.isTouchControls) {
           this.scrubber.dragStateChanged(false);
         }
         element.hidden = true;
       },
 
-      onVideoControlsResized() {
-        // Do not adjust again if resize event is caused by adjustControls(). Audio
-        // might be resized by adjustControls(), so we should skip this handler if
-        // current size is the same as the adjusted size.
-        let {width, height} = this.video.getBoundingClientRect();
-        if (width === this.adjustedVideoSize.width && height === this.adjustedVideoSize.height) {
-          return;
-        }
-
-        // For the controls which haven't got correct computed size yet, force
-        // them to re-cache their minWidth when the media is resized (reflow).
-        this.layoutControls.forEach(control => {
-          if (control) {
-            control.resized = control.resized || (control.minWidth === 0);
-          }
-        });
-
-        this.adjustControlSize();
-      },
-
       _triggeredByControls: false,
 
       startPlay() {
         this._triggeredByControls = true;
         this.hideClickToPlay();
         this.video.play();
       },
 
@@ -1661,63 +1626,85 @@
 
       get isTopLevelSyntheticDocument() {
         let doc = this.video.ownerDocument;
         let win = doc.defaultView;
         return doc.mozSyntheticDocument && win === win.top;
       },
 
       controlBarMinHeight: 40,
-      adjustedVideoSize: {},
       adjustControlSize() {
         if (this.videocontrols.isTouchControls) {
           return;
         }
 
-        let controlHidden = this.isControlBarHidden;
-
-        if (this.layoutControls.some(control => control.resized)) {
-          this.controlBar.hidden = false;
+        this.fullscreenButton.isWanted = !this.controlBar.hasAttribute("fullscreen-unavailable");
+        this.closedCaptionButton.isWanted = this.isClosedCaptionAvailable;
+        this.volumeStack.isWanted = !this.muteButton.hasAttribute("noAudio");
 
-          for (let control of this.layoutControls) {
-            if (control.resized && !control.hideByAdjustment) {
-              control.resizedHandler();
-              control.resized = false;
-            }
+        // For the controls which haven't got correct computed size yet, force
+        // them to re-cache their minWidth when the media is resized (reflow).
+        for (const control of this.layoutControls) {
+          const controlWidth = control.clientWidth;
+
+          if (controlWidth === 0 || !control.resized) {
+            continue;
           }
-
-          this.controlBar.hidden = controlHidden;
+          control.minWidth = controlWidth;
+          control.resized = false;
         }
 
         // Check minWidth of controlBar before adjusting. If the layout information
         // isn't ready yet, the minWidth of controlBar would be undefined or 0, so early
         // return to avoid invalid adjustment.
         if (!this.controlBar.minWidth) {
           return;
         }
 
-        let videoWidth = this.video.clientWidth;
-        let videoHeight = this.video.clientHeight;
-        const minControlBarPaddingWidth = 18;
+        // Duration label is only a bit wider than position label. So we could roughly
+        // treat them as the same wide that also increase the responsiveness tolerance.
+        if (!this.durationSpan.resized) {
+          this.positionDurationBox.minWidth = this.durationSpan.minWidth;
+        }
 
         // Hide and show control in order.
         const prioritizedControls = [
           this.playButton,
           this.muteButton,
           this.fullscreenButton,
           this.closedCaptionButton,
           this.positionDurationBox,
           this.scrubberStack,
           this.durationSpan,
           this.volumeStack
         ];
 
-        this.fullscreenButton.isWanted = !this.controlBar.hasAttribute("fullscreen-unavailable");
-        this.closedCaptionButton.isWanted = this.isClosedCaptionAvailable;
-        this.volumeStack.isWanted = !this.muteButton.hasAttribute("noAudio");
+        const minControlBarPaddingWidth = 18;
+        const minRequiredWidth = prioritizedControls
+          .filter(control => control.isWanted)
+          .reduce((pv, cv) => pv + cv.minWidth, minControlBarPaddingWidth);
+
+        const givenWidth = this.video.clientWidth;
+        const computedControlBarWidth = this.controlBar.clientWidth;
+        const videoWidth = this.isAudioOnly ? (givenWidth || minRequiredWidth) : givenWidth;
+        const videoHeight = this.video.clientHeight;
+        // Notice: Don't flush sytle after this comment to avoid recursively calling synchronous
+        // resize handling.
+
+        // For audio, if the given width is 0 and the audio controls does not expand
+        // to normal set of layouts, we make the entire audio controls transparent instead
+        // of collapsing it to 0 size.
+        if (this.isAudioOnly &&
+            givenWidth === 0 &&
+            computedControlBarWidth === minControlBarPaddingWidth) {
+          this.controlBar.style.opacity = 0;
+          return;
+        } else {
+          this.controlBar.style.opacity = "";
+        }
 
         let widthUsed = minControlBarPaddingWidth;
         let preventAppendControl = false;
 
         for (let control of prioritizedControls) {
           if (!control.isWanted) {
             control.hideByAdjustment = true;
             continue;
@@ -1728,26 +1715,20 @@
 
           if (control.hideByAdjustment) {
             preventAppendControl = true;
           } else {
             widthUsed += control.minWidth;
           }
         }
 
-        if (this.durationSpan.hideByAdjustment) {
-          this.positionDurationBox.resized = true;
-        }
-
-        if (videoHeight < this.controlBarMinHeight ||
-            widthUsed === minControlBarPaddingWidth) {
-          this.controlBar.setAttribute("size", "hidden");
+        if ((!this.isAudioOnly && videoHeight < this.controlBarMinHeight) ||
+            widthUsed <= minControlBarPaddingWidth) {
           this.controlBar.hideByAdjustment = true;
         } else {
-          this.controlBar.removeAttribute("size");
           this.controlBar.hideByAdjustment = false;
         }
 
         // Use flexible spacer to separate controls when scrubber is hidden.
         // As long as muteButton hidden, which means only play button presents,
         // hide spacer and make playButton centered.
         this.controlBarSpacer.hidden = !this.scrubberStack.hidden || this.muteButton.hidden;
 
@@ -1762,18 +1743,16 @@
           this.clickToPlay.hideByAdjustment = true;
         } else {
           if (this.clickToPlay.hidden && !this.video.played.length && this.video.paused) {
             this.clickToPlay.hideByAdjustment = false;
           }
           this.clickToPlay.style.width = `${clickToPlayScaledSize}px`;
           this.clickToPlay.style.height = `${clickToPlayScaledSize}px`;
         }
-        // Record new size after adjustment
-        this.adjustedVideoSize = this.video.getBoundingClientRect();
       },
 
       init(binding) {
         this.video = binding.parentNode;
         this.videocontrols = binding;
 
         this.controlsContainer    = document.getAnonymousElementByAttribute(binding, "anonid", "controlsContainer");
         this.statusIcon    = document.getAnonymousElementByAttribute(binding, "anonid", "statusIcon");
@@ -1846,17 +1825,17 @@
         addListener(this.muteButton, "click", this.toggleMute);
         addListener(this.closedCaptionButton, "click", this.toggleClosedCaption);
         addListener(this.fullscreenButton, "click", this.toggleFullscreen);
         addListener(this.playButton, "click", this.clickToPlayClickHandler);
         addListener(this.clickToPlay, "click", this.clickToPlayClickHandler);
         addListener(this.controlsSpacer, "click", this.clickToPlayClickHandler);
         addListener(this.controlsSpacer, "dblclick", this.toggleFullscreen);
 
-        addListener(this.videocontrols, "resizevideocontrols", this.onVideoControlsResized);
+        addListener(this.videocontrols, "resizevideocontrols", this.adjustControlSize);
         addListener(this.videocontrols, "transitionend", this.onTransitionEnd);
         addListener(this.video.ownerDocument, "mozfullscreenchange", this.onFullscreenChange);
         addListener(this.controlBar, "transitionend", this.onControlBarTransitioned);
         addListener(this.video.ownerDocument, "fullscreenchange", this.onFullscreenChange);
         addListener(this.video, "keypress", this.keyHandler, true);
 
         addListener(this.videocontrols, "dragstart", function(event) {
           event.preventDefault(); // prevent dragging of controls image (bug 517114)
--- a/toolkit/themes/shared/media/videocontrols.css
+++ b/toolkit/themes/shared/media/videocontrols.css
@@ -65,17 +65,17 @@ audio > xul|videocontrols {
 
 .controlsSpacer {
   background-color: rgba(255,255,255,.4);
 }
 
 .controlBar {
   position: relative;
   display: flex;
-  justify-content: center;
+  justify-content: left;
   align-items: center;
   overflow: hidden;
   height: 40px;
   padding: 0 9px;
   background-color: rgba(26,26,26,.8);
 }
 
 .playButton,