Bug 1444489 - Part VI, Enlarge the size of controls on mobile draft
authorTimothy Guan-tin Chien <timdream@gmail.com>
Fri, 09 Mar 2018 17:00:51 -0800
changeset 770605 b9b48bccf1128227005cbed42c658e24ee6a71e7
parent 770604 9e6a5f9fbde7bd3cf590bc3f8e0736fce170361d
child 770606 365b6076f7ff697cac7d950fe61d506671db43f1
push id103447
push usertimdream@gmail.com
push dateWed, 21 Mar 2018 15:57:15 +0000
bugs1444489
milestone61.0a1
Bug 1444489 - Part VI, Enlarge the size of controls on mobile Enlarge controls by around 1.3x, which is the size of the original touch controls. MozReview-Commit-ID: kpgFFIW2hh
browser/base/content/test/static/browser_parsable_css.js
toolkit/content/tests/widgets/test_videocontrols_size.html
toolkit/content/widgets/videocontrols.xml
toolkit/themes/shared/media/videocontrols.css
--- a/browser/base/content/test/static/browser_parsable_css.js
+++ b/browser/base/content/test/static/browser_parsable_css.js
@@ -111,16 +111,18 @@ let propNameWhitelist = [
   // Bug 1442300
   {propName: "--in-content-category-background",
    isFromDevTools: false},
   // These custom properties are retrieved directly from CSSOM
   // in videocontrols.xml to get pre-defined style instead of computed
   // dimensions, which is why they are not referenced by CSS.
   {propName: "--clickToPlay-width",
    isFromDevTools: false},
+  {propName: "--playButton-width",
+   isFromDevTools: false},
   {propName: "--muteButton-width",
    isFromDevTools: false},
   {propName: "--castingButton-width",
    isFromDevTools: false},
   {propName: "--closedCaptionButton-width",
    isFromDevTools: false},
   {propName: "--fullscreenButton-width",
    isFromDevTools: false},
--- a/toolkit/content/tests/widgets/test_videocontrols_size.html
+++ b/toolkit/content/tests/widgets/test_videocontrols_size.html
@@ -28,21 +28,23 @@
 
 <pre id="test">
 <script clas="testbody" type="application/javascript">
   SimpleTest.waitForExplicitFinish();
 
   const videoElems = [...document.getElementsByTagName("video")];
   const testCases = [];
 
-  const buttonWidth = 30;
-  const minSrubberWidth = 48;
-  const minControlBarHeight = 40;
-  const minControlBarWidth = 48;
-  const minClickToPlaySize = 48;
+  const isTouchControl = navigator.appVersion.includes("Android");
+
+  const buttonWidth = isTouchControl ? 40 : 30;
+  const minSrubberWidth = isTouchControl ? 64 : 48;
+  const minControlBarHeight = isTouchControl ? 52 : 40;
+  const minControlBarWidth = isTouchControl ? 58 : 48;
+  const minClickToPlaySize = isTouchControl ? 64 : 48;
 
   function getElementName(elem) {
     return elem.getAttribute("anonid") || elem.getAttribute("class");
   }
 
   function testButton(btn) {
     if (btn.hidden) return;
 
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -34,17 +34,17 @@
 
       <div anonid="controlsOverlay" class="controlsOverlay stackItem">
         <div class="controlsSpacerStack">
           <div anonid="controlsSpacer" class="controlsSpacer stackItem" role="none"></div>
           <div anonid="clickToPlay" class="clickToPlay" hidden="true"></div>
         </div>
         <div anonid="controlBar" class="controlBar" hidden="true">
           <button anonid="playButton"
-                  class="playButton"
+                  class="button playButton"
                   playlabel="&playButton.playLabel;"
                   pauselabel="&playButton.pauseLabel;"
                   tabindex="-1"/>
           <div anonid="scrubberStack" class="scrubberStack progressContainer" role="none">
             <div class="progressBackgroundBar stackItem" role="none">
               <div class="progressStack" role="none">
                 <progress anonid="bufferBar" class="bufferBar" value="0" max="100" tabindex="-1"></progress>
                 <progress anonid="progressBar" class="progressBar" value="0" max="100" tabindex="-1"></progress>
@@ -54,28 +54,28 @@
           </div>
           <span anonid="positionLabel" class="positionLabel" role="presentation"></span>
           <span anonid="durationLabel" class="durationLabel" role="presentation"></span>
           <span anonid="positionDurationBox" class="positionDurationBox" aria-hidden="true">
             &positionAndDuration.nameFormat;
           </span>
           <div anonid="controlBarSpacer" class="controlBarSpacer" hidden="true" role="none"></div>
           <button anonid="muteButton"
-                  class="muteButton"
+                  class="button muteButton"
                   mutelabel="&muteButton.muteLabel;"
                   unmutelabel="&muteButton.unmuteLabel;"
                   tabindex="-1"/>
           <div anonid="volumeStack" class="volumeStack progressContainer" role="none">
             <input type="range" anonid="volumeControl" class="volumeControl" min="0" max="100" step="1" tabindex="-1"/>
           </div>
-          <button anonid="castingButton" class="castingButton"
+          <button anonid="castingButton" class="button castingButton"
                   aria-label="&castingButton.castingLabel;"/>
-          <button anonid="closedCaptionButton" class="closedCaptionButton"/>
+          <button anonid="closedCaptionButton" class="button closedCaptionButton"/>
           <button anonid="fullscreenButton"
-                  class="fullscreenButton"
+                  class="button fullscreenButton"
                   enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
                   exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
         </div>
         <div anonid="textTrackList" class="textTrackList" hidden="true" offlabel="&closedCaption.off;"></div>
       </div>
     </div>
   </xbl:content>
 
@@ -1698,33 +1698,37 @@
         this.castingButton = document.getAnonymousElementByAttribute(binding, "anonid", "castingButton");
         this.closedCaptionButton = document.getAnonymousElementByAttribute(binding, "anonid", "closedCaptionButton");
         this.textTrackList = document.getAnonymousElementByAttribute(binding, "anonid", "textTrackList");
 
         if (this.positionDurationBox) {
           this.durationSpan = this.positionDurationBox.getElementsByTagName("span")[0];
         }
 
+        this.videocontrols.isTouchControls =
+          navigator.appVersion.includes("Android");
+        if (this.videocontrols.isTouchControls) {
+          this.controlsContainer.classList.add("touch");
+        }
+
         this.controlBarComputedStyles = getComputedStyle(this.controlBar);
 
         // Hide and show control in certain order.
         this.prioritizedControls = [
           this.playButton,
           this.muteButton,
           this.fullscreenButton,
           this.castingButton,
           this.closedCaptionButton,
           this.positionDurationBox,
           this.scrubberStack,
           this.durationSpan,
           this.volumeStack
         ];
 
-        this.videocontrols.isTouchControls =
-          navigator.appVersion.includes("Android");
         this.isAudioOnly = (this.video instanceof HTMLAudioElement);
         this.setupInitialState();
         this.setupNewLoadState();
         this.initTextTracks();
 
         // Use the handleEvent() callback for all media events.
         // Only the "error" event listener must capture, so that it can trap error
         // events from <source> children, which don't bubble. But we use capture
@@ -2020,62 +2024,69 @@
 
       hasError() {
         return (this.video.error != null || this.video.networkState == this.video.NETWORK_NO_SOURCE);
       },
 
       handleEvent(aEvent) {
         // If the binding is detached (or has been replaced by a
         // newer instance of the binding), nuke our event-listeners.
-        if (this.binding.randomID != this.randomID) {
+        if (this.videocontrols.randomID != this.randomID) {
           this.terminateEventListeners();
           return;
         }
 
         switch (aEvent.type) {
           case "play":
             this.noControlsOverlay.hidden = true;
             break;
           case "playing":
             this.noControlsOverlay.hidden = true;
             break;
         }
       },
 
       blockedVideoHandler() {
-        if (this.binding.randomID != this.randomID) {
+        if (this.videocontrols.randomID != this.randomID) {
           this.terminateEventListeners();
           return;
         } else if (this.hasError()) {
           this.noControlsOverlay.hidden = true;
           return;
         }
         this.noControlsOverlay.hidden = false;
       },
 
       clickToPlayClickHandler(e) {
-        if (this.binding.randomID != this.randomID) {
+        if (this.videocontrols.randomID != this.randomID) {
           this.terminateEventListeners();
           return;
         } else if (e.button != 0) {
           return;
         }
 
         this.noControlsOverlay.hidden = true;
         this.video.play();
       },
 
       init(binding) {
-        this.binding = binding;
+        this.videocontrols = binding;
         this.randomID = Math.random();
-        this.binding.randomID = this.randomID;
+        this.videocontrols.randomID = this.randomID;
         this.video = binding.parentNode;
+        this.controlsContainer = document.getAnonymousElementByAttribute(binding, "anonid", "controlsContainer");
         this.clickToPlay       = document.getAnonymousElementByAttribute(binding, "anonid", "clickToPlay");
         this.noControlsOverlay = document.getAnonymousElementByAttribute(binding, "anonid", "controlsContainer");
 
+        this.videocontrols.isTouchControls =
+          navigator.appVersion.includes("Android");
+        if (this.videocontrols.isTouchControls) {
+          this.controlsContainer.classList.add("touch");
+        }
+
         let self = this;
         function addListener(elem, eventName, func) {
           let boundFunc = func.bind(self);
           self.controlListeners.push({ item: elem, event: eventName, func: boundFunc });
           elem.addEventListener(eventName, boundFunc, { mozSystemGroup: true });
         }
         addListener(this.clickToPlay, "click", this.clickToPlayClickHandler);
         addListener(this.video, "MozNoControlsBlockedVideo", this.blockedVideoHandler);
--- a/toolkit/themes/shared/media/videocontrols.css
+++ b/toolkit/themes/shared/media/videocontrols.css
@@ -18,35 +18,56 @@ audio > xul|videocontrols {
   text-align: left;
   list-style-image: none !important;
   font: normal normal normal 100%/normal sans-serif !important;
   text-decoration: none !important;
 }
 
 .controlsContainer {
   --clickToPlay-size: 48px;
+  --button-size: 30px;
+  --timer-size: 40px;
+  --timer-long-size: 60px;
+  --track-size: 5px;
+  --thumb-size: 13px;
+  --label-font-size: 13px;
+}
+.controlsContainer.touch {
+  --clickToPlay-size: 64px;
+  --button-size: 40px;
+  --timer-size: 52px;
+  --timer-long-size: 78px;
+  --track-size: 7px;
+  --thumb-size: 16px;
+  --label-font-size: 16px;
 }
 
 /* Some CSS custom properties defined here are referenced by videocontrols.xml in JavaScript */
 .controlBar {
   /* Do not delete: these variables are accessed by JavaScript directly.
      see videocontrols.xml and search for |-width|. */
   --clickToPlay-width: var(--clickToPlay-size);
-  --playButton-width: 30px;
-  --playButton-height: var(--playButton-width);
+  --playButton-width: var(--button-size);
   --scrubberStack-width: 64px;
-  --muteButton-width: 30px;
+  --muteButton-width: var(--button-size);
   --volumeStack-width: 48px;
-  --castingButton-width: 30px;
-  --closedCaptionButton-width: 30px;
-  --fullscreenButton-width: 30px;
-  --positionDurationBox-width: 40px;
-  --durationSpan-width: 40px;
-  --positionDurationBox-width-long: 60px;
-  --durationSpan-width-long: 60px;
+  --castingButton-width: var(--button-size);
+  --closedCaptionButton-width: var(--button-size);
+  --fullscreenButton-width: var(--button-size);
+  --positionDurationBox-width: var(--timer-size);
+  --durationSpan-width: var(--timer-size);
+  --positionDurationBox-width-long: var(--timer-long-size);
+  --durationSpan-width-long: var(--timer-long-size);
+}
+
+.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"],
 .controlBar[hidden] {
   display: none;
 }
 
 .controlBar[size="hidden"] {
@@ -101,49 +122,45 @@ audio > xul|videocontrols {
   justify-content: center;
   align-items: center;
   overflow: hidden;
   height: 40px;
   padding: 0 9px;
   background-color: rgba(26,26,26,.8);
 }
 
-.playButton,
-.muteButton,
-.castingButton,
-.closedCaptionButton,
-.fullscreenButton {
+.touch .controlBar {
+  height: 52px;
+}
+
+.controlBar > .button {
   height: 100%;
-  min-width: var(--playButton-width);
-  min-height: var(--playButton-height);
+  min-width: var(--button-size);
+  min-height: var(--button-size);
   padding: 6px;
   border: 0;
   margin: 0;
   background-color: transparent;
   background-repeat: no-repeat;
   background-position: center;
   background-origin: content-box;
   background-clip: content-box;
   -moz-context-properties: fill;
   fill: #ffffff;
 }
 
-.playButton:hover,
-.muteButton:hover,
-.castingButton:hover,
-.closedCaptionButton:hover,
-.fullscreenButton:hover {
+.touch .controlBar > .button {
+  background-size: 24px 24px;
+}
+
+.controlBar > .button:hover {
   fill: #48a0f7;
 }
 
-.playButton:hover:active,
-.muteButton:hover:active,
-.castingButton:hover:active,
-.closedCaptionButton:hover:active,
-.fullscreenButton:hover:active {
+.controlBar > .button:hover:active {
   fill: #2d89e6;
 }
 
 .playButton {
   background-image: url(chrome://global/skin/media/pauseButton.svg);
 }
 .playButton[paused] {
   background-image: url(chrome://global/skin/media/playButton.svg);
@@ -188,18 +205,18 @@ audio > xul|videocontrols {
 }
 
 .controlBarSpacer {
   flex-grow: 1;
 }
 
 .volumeControl::-moz-range-thumb,
 .scrubber::-moz-range-thumb {
-  height: 13px;
-  width: 13px;
+  height: var(--thumb-size);
+  width: var(--thumb-size);
   border: none;
   border-radius: 50%;
   background-color: #ffffff;
   filter: drop-shadow(0px 0px 2px rgba(0,0,0,0.65));
 }
 
 .volumeControl::-moz-focus-outer,
 .scrubber::-moz-focus-outer {
@@ -211,17 +228,17 @@ audio > xul|videocontrols {
   flex-direction: column;
   justify-content: center;
   align-items: center;
 }
 
 .progressStack {
   position: relative;
   width: 100%;
-  height: 5px;
+  height: var(--track-size);
 }
 
 .scrubberStack {
   /* minus margin to get basis of required width */
   min-width: calc(var(--scrubberStack-width) - 18px);
   flex-basis: calc(var(--scrubberStack-width) - 18px);
   flex-grow: 2;
   flex-shrink: 0;
@@ -244,17 +261,17 @@ audio > xul|videocontrols {
 .volumeControl {
   bottom: 0;
   left: 0;
   position: absolute;
   width: 100%;
   height: 100%;
   padding: 0;
   border: 0;
-  border-radius: 2.5px;
+  border-radius: calc(var(--track-size) / 2);
   margin: 0;
   background: none;
   background-color: transparent;
 }
 
 .bufferBar,
 .volumeBackground {
   background-color: rgba(0,0,0,0.7);
@@ -262,17 +279,17 @@ audio > xul|videocontrols {
 
 .bufferBar::-moz-progress-bar,
 .progressBar::-moz-progress-bar,
 .volumeBackground::-moz-meter-bar {
   height: 100%;
   padding: 0;
   margin: 0;
   border: 0;
-  border-radius: 2.5px;
+  border-radius: calc(var(--track-size) / 2);
   background: none;
 }
 
 .scrubber:hover::-moz-range-thumb,
 .volumeControl:hover::-moz-range-thumb {
   background-color: #48a0f7;
 }
 
@@ -283,56 +300,60 @@ audio > xul|videocontrols {
 
 .scrubber::-moz-range-track,
 .scrubber::-moz-range-progress {
   background-color: transparent;
 }
 
 .volumeControl::-moz-range-progress,
 .volumeControl::-moz-range-track {
-  height: 5px;
-  border-radius: 2.5px;
+  height: var(--track-size);
+  border-radius: calc(var(--track-size) / 2);
 }
 
 .volumeControl::-moz-range-progress {
   background-color: #ffffff;
 }
 
 .volumeControl::-moz-range-track {
   background-color: rgba(0,0,0,0.7);
 }
 
 
 .bufferBar::-moz-progress-bar {
   background-color: rgba(255,255,255,0.3);
-  border-radius: 2.5px;
+  border-radius: calc(var(--track-size) / 2);
 }
 
 .progressBar::-moz-progress-bar {
   background-color: #00b6f0;
 }
 
 .textTrackList {
   position: absolute;
   right: 5px;
   bottom: 45px;
   max-width: 80%;
   border: 1px solid #000000;
   border-radius: 2.5px;
   padding: 5px 0;
   vertical-align: middle;
-  font-size: 12px;
   background-color: #000000;
   opacity: 0.7;
 }
 
+.touch .textTrackList {
+  bottom: 58px;
+}
+
 .textTrackList > .textTrackItem {
   display: block;
   width: 100%;
-  height: 30px;
+  height: var(--button-size);
+  font-size: var(--label-font-size);
   padding: 2px 10px;
   border: none;
   margin: 0;
   white-space: nowrap;
   overflow: hidden;
   text-align: left;
   text-overflow: ellipsis;
   color: #ffffff;
@@ -353,17 +374,17 @@ audio > xul|videocontrols {
 }
 
 .positionDurationBox {
   text-align: center;
   padding-inline-start: 1px;
   padding-inline-end: 9px;
   white-space: nowrap;
   font: message-box;
-  font-size: 13px;
+  font-size: var(--label-font-size);
   font-size-adjust: 0.55;
   color: #ffffff;
 }
 
 %ifdef XP_MACOSX
 .positionDurationBox {
   font-size-adjust: unset;
   font-family: "Helvetica Neue", "Helvetica", sans-serif;