Bug 470628 - Part 1: CSS, JS, XBL, and locale changes (with fall-back support). r=dolske ui-r=shorlander f=fryn
authorJared Wein <jwein@mozilla.com>
Wed, 23 Nov 2011 14:42:18 -0500
changeset 80615 87390d4620e2dc0dcc348aab043eb50a37b6a4b6
parent 80614 192f8762963f94dd988911202261a312f942fd21
child 80616 c5c8d300a9d3652013b3ce76398df6c821d80909
push id339
push userjwein@mozilla.com
push dateWed, 23 Nov 2011 19:43:45 +0000
treeherderfx-team@c5c8d300a9d3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske, shorlander
bugs470628
milestone11.0a1
Bug 470628 - Part 1: CSS, JS, XBL, and locale changes (with fall-back support). r=dolske ui-r=shorlander f=fryn
browser/base/content/nsContextMenu.js
browser/locales/en-US/chrome/browser/browser.dtd
mobile/xul/themes/core/touchcontrols.css
toolkit/content/widgets/videocontrols.xml
toolkit/locales/en-US/chrome/global/videocontrols.dtd
toolkit/themes/pinstripe/global/media/videocontrols.css
toolkit/themes/winstripe/global/media/videocontrols.css
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -413,17 +413,17 @@ nsContextMenu.prototype = {
     var onMedia = (this.onVideo || this.onAudio);
     // Several mutually exclusive items... play/pause, mute/unmute, show/hide
     this.showItem("context-media-play",  onMedia && (this.target.paused || this.target.ended));
     this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended);
     this.showItem("context-media-mute",   onMedia && !this.target.muted);
     this.showItem("context-media-unmute", onMedia && this.target.muted);
     this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
     this.showItem("context-media-hidecontrols", onMedia && this.target.controls);
-    this.showItem("context-video-fullscreen", this.onVideo);
+    this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.mozFullScreenElement == null);
     var statsShowing = this.onVideo && this.target.wrappedJSObject.mozMediaStatisticsShowing;
     this.showItem("context-video-showstats", this.onVideo && this.target.controls && !statsShowing);
     this.showItem("context-video-hidestats", this.onVideo && this.target.controls && statsShowing);
 
     // Disable them when there isn't a valid media source loaded.
     if (onMedia) {
       var hasError = this.target.error != null ||
                      this.target.networkState == this.target.NETWORK_NO_SOURCE;
@@ -854,20 +854,25 @@ nsContextMenu.prototype = {
     canvas.width = video.videoWidth;
     canvas.height = video.videoHeight;
     var ctxDraw = canvas.getContext("2d");
     ctxDraw.drawImage(video, 0, 0);
     saveImageURL(canvas.toDataURL("image/jpeg", ""), name, "SaveImageTitle", true, false, document.documentURIObject);
   },
 
   fullScreenVideo: function () {
-    this.target.pause();
-
-    openDialog("chrome://browser/content/fullscreen-video.xhtml",
-               "", "chrome,centerscreen,dialog=no", this.target);
+    let video = this.target;
+    if (document.mozFullScreenEnabled)
+      video.mozRequestFullScreen();
+    else {
+      // Fallback for the legacy full-screen video implementation.
+      video.pause();
+      openDialog("chrome://browser/content/fullscreen-video.xhtml",
+                  "", "chrome,centerscreen,dialog=no", video);
+    }
   },
 
   // Change current window to the URL of the background image.
   viewBGImage: function(e) {
     urlSecurityCheck(this.bgImageURL,
                      this.browser.contentPrincipal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
     var doc = this.target.ownerDocument;
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -424,32 +424,44 @@ can reach it easily. -->
 <!ENTITY copyAudioURLCmd.label        "Copy Audio Location">
 <!ENTITY copyAudioURLCmd.accesskey    "o">
 <!ENTITY copyEmailCmd.label           "Copy Email Address">
 <!ENTITY copyEmailCmd.accesskey       "E">
 <!ENTITY thisFrameMenu.label              "This Frame">
 <!ENTITY thisFrameMenu.accesskey          "h">
 
 <!-- Media (video/audio) controls -->
+<!-- LOCALIZATION NOTE: The access keys for "Play" and
+"Pause" are the same because the two context-menu
+items are mutually exclusive. -->
 <!ENTITY mediaPlay.label             "Play">
 <!ENTITY mediaPlay.accesskey         "P">
 <!ENTITY mediaPause.label            "Pause">
 <!ENTITY mediaPause.accesskey        "P">
+<!-- LOCALIZATION NOTE: The access keys for "Mute" and
+"Unmute" are the same because the two context-menu
+items are mutually exclusive. -->
 <!ENTITY mediaMute.label             "Mute">
 <!ENTITY mediaMute.accesskey         "M">
 <!ENTITY mediaUnmute.label           "Unmute">
 <!ENTITY mediaUnmute.accesskey       "m">
+<!-- LOCALIZATION NOTE: The access keys for "Show Controls" and
+"Hide Controls" are the same because the two context-menu
+items are mutually exclusive. -->
 <!ENTITY mediaShowControls.label     "Show Controls">
 <!ENTITY mediaShowControls.accesskey "C">
 <!ENTITY mediaHideControls.label     "Hide Controls">
 <!ENTITY mediaHideControls.accesskey "C">
 <!ENTITY videoFullScreen.label       "Full Screen">
 <!ENTITY videoFullScreen.accesskey   "F">
 <!ENTITY videoSaveImage.label        "Save Snapshot As…">
 <!ENTITY videoSaveImage.accesskey    "S">
+<!-- LOCALIZATION NOTE: The access keys for "Show Statistics" and
+"Hide Statistics" are the same because the two context-menu
+items are mutually exclusive. -->
 <!ENTITY videoShowStats.label        "Show Statistics">
 <!ENTITY videoShowStats.accesskey    "t">
 <!ENTITY videoHideStats.label        "Hide Statistics">
 <!ENTITY videoHideStats.accesskey    "t">
 
 <!-- LOCALIZATION NOTE :
 fullZoomEnlargeCmd.commandkey3, fullZoomReduceCmd.commandkey2 and
 fullZoomResetCmd.commandkey2 are alternative acceleration keys for zoom.
--- a/mobile/xul/themes/core/touchcontrols.css
+++ b/mobile/xul/themes/core/touchcontrols.css
@@ -43,16 +43,21 @@
 .muteButton {
   background: url("chrome://browser/skin/images/mute-hdpi.png") no-repeat center;
 }
 
 .muteButton[muted="true"] {
   background: url("chrome://browser/skin/images/unmute-hdpi.png") no-repeat center;
 }
 
+/* This button is hidden until bug 704229 is fixed. */
+.fullscreenButton {
+  display: none;
+}
+
 /* bars */
 .scrubberStack {
   width: 100%;
   min-height: 32px;
   max-height: 32px;
   padding: 0px 8px;
   margin: 0px;
 }
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -281,16 +281,19 @@
                     </vbox>
                     <button class="muteButton"
                             mutelabel="&muteButton.muteLabel;"
                             unmutelabel="&muteButton.unmuteLabel;"/>
                     <stack class="volumeStack" hidden="true" fadeout="true">
                         <box class="volumeBackgroundBar"/>
                         <scale class="volumeControl" orient="vertical" dir="reverse" movetoclick="true"/>
                     </stack>
+                    <button class="fullscreenButton"
+                            enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
+                            exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
                 </hbox>
             </vbox>
         </stack>
     </xbl:content>
 
     <implementation>
 
         <constructor>
@@ -315,16 +318,17 @@
                 positionLabel  : null,
                 scrubberThumb  : null,
                 scrubber       : null,
                 progressBar    : null,
                 bufferBar      : null,
                 statusOverlay  : null,
                 controlsSpacer : null,
                 stats          : {},
+                fullscreenButton : null,
 
                 randomID : 0,
                 videoEvents : ["play", "pause", "ended", "volumechange", "loadeddata",
                                "loadstart", "timeupdate", "progress",
                                "playing", "waiting", "canplay", "canplaythrough",
                                "seeking", "seeked", "emptied", "loadedmetadata",
                                "error", "suspend", "stalled"],
 
@@ -369,16 +373,18 @@
                  */
                 setupInitialState : function() {
                     this.randomID = Math.random();
                     this.videocontrols.randomID = this.randomID;
 
                     this.setPlayButtonState(this.video.paused);
                     this.setMuteButtonState(this.video.muted);
 
+                    this.setFullscreenButtonState();
+
                     var volume = this.video.muted ? 0 : Math.round(this.video.volume * 100);
                     this.volumeControl.value = volume;
 
                     var duration = Math.round(this.video.duration * 1000); // in ms
                     var currentTime = Math.round(this.video.currentTime * 1000); // in ms
                     this.log("Initial playback position is at " + currentTime + " of " + duration);
                     // It would be nice to retain maxCurrentTimeSeen, but it would be difficult
                     // to determine if the media source changed while we were detached.
@@ -415,16 +421,17 @@
                     }
 
                     // An event handler for |onresize| should be added when bug 227495 is fixed.
                     let controlBarWasHidden = this.controlBar.getAttribute("hidden") == "true";
                     this.controlBar.removeAttribute("hidden");
                     this._playButtonWidth = this.playButton.clientWidth;
                     this._durationLabelWidth = this.durationLabel.clientWidth;
                     this._muteButtonWidth = this.muteButton.clientWidth;
+                    this._fullscreenButtonWidth = this.fullscreenButton.clientWidth;
                     this._controlBarHeight = this.controlBar.clientHeight;
                     if (controlBarWasHidden)
                       this.controlBar.setAttribute("hidden", "true");
                     this.adjustControlSize();
 
                     this._handleCustomEventsBound = this.handleCustomEvents.bind(this);
                     this.video.addEventListener("media-showStatistics", this._handleCustomEventsBound, false, true);
                 },
@@ -595,16 +602,17 @@
                 terminateEventListeners : function () {
                     if (this.statsInterval) {
                         clearInterval(this.statsInterval);
                         this.statsInterval = null;
                     }
                     for each (var event in this.videoEvents)
                         this.video.removeEventListener(event, this, false);
                     this.video.removeEventListener("media-showStatistics", this._handleCustomEventsBound, false);
+                    this.video.ownerDocument.removeEventListener("mozfullscreenchange", this.setFullscreenButtonState, false);
                     delete this._handleCustomEventsBound;
                     this.log("--- videocontrols terminated ---");
                 },
 
                 formatTime : function(aTime) {
                     // Format the duration as "h:mm:ss" or "m:ss"
                     aTime = Math.round(aTime / 1000);
                     let hours = Math.floor(aTime / 3600);
@@ -821,26 +829,58 @@
                 toggleMute : function () {
                     this.video.muted = !this.video.muted;
 
                     // We'll handle style changes in the event listener for
                     // the "volumechange" event, same as if content script was
                     // controlling volume.
                 },
 
+                isVideoInFullScreen : function () {
+                    return document.mozFullScreenElement != null;
+                },
+
+                toggleFullscreen : function () {
+                    this.isVideoInFullScreen() ?
+                        document.mozCancelFullScreen() :
+                        this.video.mozRequestFullScreen();
+                },
+
+                setFullscreenButtonState : function () {
+                    if (this.isAudioOnly || !document.mozFullScreenEnabled) {
+                        this.fullscreenButton.hidden = true;
+                        return;
+                    }
+
+                    var attrName = this.isVideoInFullScreen() ? "enterfullscreenlabel" : "exitfullscreenlabel";
+                    var value = this.fullscreenButton.getAttribute(attrName);
+                    this.fullscreenButton.setAttribute("aria-label", value);
+
+                    if (this.isVideoInFullScreen())
+                        this.fullscreenButton.setAttribute("fullscreened", "true");
+                    else
+                        this.fullscreenButton.removeAttribute("fullscreened");
+                },
+
                 setPlayButtonState : function(aPaused) {
-                  this.playButton.setAttribute("paused", aPaused);
+                  if (aPaused)
+                      this.playButton.setAttribute("paused", "true");
+                  else
+                      this.playButton.removeAttribute("paused");
 
                   var attrName = aPaused ? "playlabel" : "pauselabel";
                   var value = this.playButton.getAttribute(attrName);
                   this.playButton.setAttribute("aria-label", value);
                 },
 
                 setMuteButtonState : function(aMuted) {
-                  this.muteButton.setAttribute("muted", aMuted);
+                  if (aMuted)
+                      this.muteButton.setAttribute("muted", "true");
+                  else
+                      this.muteButton.removeAttribute("muted");
 
                   var attrName = aMuted ? "unmutelabel" : "mutelabel";
                   var value = this.muteButton.getAttribute(attrName);
                   this.muteButton.setAttribute("aria-label", value);
                 },
 
                 _getComputedPropertyValueAsInt : function(element, property) {
                   let value = window.getComputedStyle(element, null).getPropertyValue(property);
@@ -1055,31 +1095,33 @@
                 log : function (msg) {
                     if (this.debug)
                         dump("videoctl: " + msg + "\n");
                 },
 
                 _playButtonWidth : 0,
                 _durationLabelWidth : 0,
                 _muteButtonWidth : 0,
+                _fullscreenButtonWidth : 0,
                 _controlBarHeight : 0,
                 adjustControlSize : function adjustControlSize() {
                     if (this.isAudioOnly)
                       return;
 
                     let videoHeight = this.video.clientHeight;
                     let videoWidth = this.video.clientWidth;
 
                     // The scrubber has |flex=1|, therefore |minScrubberWidth|
                     // was generated by empirical testing.
                     let minScrubberWidth = 25;
                     let minWidthAllControls = this._playButtonWidth +
                                               minScrubberWidth +
                                               this._durationLabelWidth +
-                                              this._muteButtonWidth;
+                                              this._muteButtonWidth +
+                                              this._fullscreenButtonWidth;
                     let minHeightForControlBar = this._controlBarHeight;
                     let minWidthOnlyPlayPause = this._playButtonWidth + this._muteButtonWidth;
 
                     let size = "normal";
                     if (videoHeight < minHeightForControlBar)
                         size = "hidden";
                     else if (videoWidth < minWidthOnlyPlayPause)
                         size = "hidden";
@@ -1103,16 +1145,17 @@
                     this.bufferBar     = document.getAnonymousElementByAttribute(binding, "class", "bufferBar");
                     this.scrubber      = document.getAnonymousElementByAttribute(binding, "class", "scrubber");
                     this.scrubberThumb = document.getAnonymousElementByAttribute(this.scrubber, "class", "scale-thumb");
                     this.durationLabel = document.getAnonymousElementByAttribute(binding, "class", "durationLabel");
                     this.positionLabel = document.getAnonymousElementByAttribute(binding, "class", "positionLabel");
                     this.statusOverlay = document.getAnonymousElementByAttribute(binding, "class", "statusOverlay");
                     this.statsOverlay  = document.getAnonymousElementByAttribute(binding, "class", "statsOverlay");
                     this.controlsSpacer = document.getAnonymousElementByAttribute(binding, "class", "controlsSpacer");
+                    this.fullscreenButton = document.getAnonymousElementByAttribute(binding, "class", "fullscreenButton");
 
                     this.statsTable       = document.getAnonymousElementByAttribute(binding, "class", "statsTable");
                     this.stats.filename   = document.getAnonymousElementByAttribute(binding, "class", "statFilename");
                     this.stats.size       = document.getAnonymousElementByAttribute(binding, "class", "statSize");
                     this.stats.activity   = document.getAnonymousElementByAttribute(binding, "class", "statActivity");
                     this.stats.volume     = document.getAnonymousElementByAttribute(binding, "class", "statVolume");
                     this.stats.channels   = document.getAnonymousElementByAttribute(binding, "class", "statChannels");
                     this.stats.sampRate   = document.getAnonymousElementByAttribute(binding, "class", "statSampleRate");
@@ -1143,24 +1186,26 @@
                     // from the <source> children, which don't bubble.
                     for each (var event in this.videoEvents)
                         this.video.addEventListener(event, this, (event == "error") ? true : false);
 
                     var self = this;
                     this.muteButton.addEventListener("command", function() { self.toggleMute(); }, false);
                     this.playButton.addEventListener("command", function() { self.togglePause(); }, false);
                     this.controlsSpacer.addEventListener("click", function(e) { if (e.button == 0) { self.togglePause(); } }, false);
+                    this.fullscreenButton.addEventListener("command", function() { self.toggleFullscreen(); }, false );
                     if (!this.isAudioOnly) {
                       this.muteButton.addEventListener("mouseover",  function(e) { self.onVolumeMouseInOut(e); }, false);
                       this.muteButton.addEventListener("mouseout",   function(e) { self.onVolumeMouseInOut(e); }, false);
                       this.volumeStack.addEventListener("mouseover", function(e) { self.onVolumeMouseInOut(e); }, false);
                       this.volumeStack.addEventListener("mouseout",  function(e) { self.onVolumeMouseInOut(e); }, false);
                     }
 
                     this.videocontrols.addEventListener("transitionend", function(e) { self.onTransitionEnd(e); }, false);
+                    this.video.ownerDocument.addEventListener("mozfullscreenchange", this.setFullscreenButtonState, false);
 
                     // Make the <video> element keyboard accessible.
                     this.video.setAttribute("tabindex", 0);
                     this.video.addEventListener("keypress", function (e) { self.keyHandler(e) }, false);
 
                     this.log("--- videocontrols initialized ---");
                 }
             }) ]]>
@@ -1212,16 +1257,19 @@
                         <progressmeter class="bufferBar"/>
                         <progressmeter class="progressBar" max="10000"/>
                         <scale class="scrubber" movetoclick="true"/>
                     </stack>
                     <vbox class="durationBox">
                         <label class="positionLabel" role="presentation"/>
                         <label class="durationLabel" role="presentation"/>
                     </vbox>
+                    <button class="fullscreenButton"
+                            enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
+                            exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
                 </vbox>
             </vbox>
         </stack>
     </xbl:content>
 
     <implementation>
 
         <field readonly="true" name="isTouchControl">true</field>
--- a/toolkit/locales/en-US/chrome/global/videocontrols.dtd
+++ b/toolkit/locales/en-US/chrome/global/videocontrols.dtd
@@ -1,12 +1,14 @@
 <!ENTITY playButton.playLabel "Play">
 <!ENTITY playButton.pauseLabel "Pause">
 <!ENTITY muteButton.muteLabel "Mute">
 <!ENTITY muteButton.unmuteLabel "Unmute">
+<!ENTITY fullscreenButton.enterfullscreenlabel "Full Screen">
+<!ENTITY fullscreenButton.exitfullscreenlabel "Exit Full Screen">
 
 <!ENTITY stats.media "Media">
 <!ENTITY stats.size "Size">
 <!ENTITY stats.activity "Activity">
 <!ENTITY stats.activityPaused "Paused">
 <!ENTITY stats.activityPlaying "Playing">
 <!ENTITY stats.activityEnded "Ended">
 <!ENTITY stats.activitySeeking "(seeking)">
--- a/toolkit/themes/pinstripe/global/media/videocontrols.css
+++ b/toolkit/themes/pinstripe/global/media/videocontrols.css
@@ -12,33 +12,48 @@
   -moz-appearance: none;
   margin: 0;
   padding: 0;
   min-height: 28px;
   min-width: 28px;
   margin-right: -22px; /* 1/2 of scrubber thumb width, for overhang. */
   position: relative; /* Trick to work around negative margin interfering with clicking on the button. */
 }
-.playButton[paused="true"] {
+.playButton[paused] {
   background: url(chrome://global/skin/media/playButton.png) no-repeat center;
 }
 
 .muteButton {
   background: url(chrome://global/skin/media/muteButton.png) no-repeat center;
   /* Remove the native button appearance and styling */
   -moz-appearance: none;
   margin: 0;
   padding: 0;
   min-height: 28px;
   min-width: 33px;
 }
-.muteButton[muted="true"] {
+.muteButton[muted] {
   background: url(chrome://global/skin/media/unmuteButton.png) no-repeat center;
 }
 
+.fullscreenButton {
+  background-color: transparent;
+  list-style-image: url("chrome://global/skin/media/fullscreenButton.png");
+  -moz-image-region: rect(0px, 16px, 16px, 0px);
+  -moz-appearance: none;
+  margin: 0;
+  padding: 0;
+  min-height: 28px;
+  min-width: 28px;
+}
+
+.fullscreenButton[fullscreened] {
+  -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
 .volumeStack {
   width: 28px;
   height: 70px;
   background-color: rgba(35,31,32,.74);
   /* use negative margin to place stack over the mute button to its left. */
   margin: -70px 3px 28px -31px;
   overflow: hidden; /* crop it when sliding down, don't grow the control bar */
   position: relative; /* Trick to work around negative margin interfering with dragging the thumb. */
--- a/toolkit/themes/winstripe/global/media/videocontrols.css
+++ b/toolkit/themes/winstripe/global/media/videocontrols.css
@@ -13,34 +13,50 @@
   margin: 0;
   padding: 0;
   min-height: 28px;
   min-width: 28px;
   border: none;
   margin-right: -22px; /* 1/2 of scrubber thumb width, for overhang. */
   position: relative; /* Trick to work around negative margin interfering with clicking on the button. */
 }
-.playButton[paused="true"] {
+.playButton[paused] {
   background: url(chrome://global/skin/media/playButton.png) no-repeat center;
 }
 
 .muteButton {
   background: url(chrome://global/skin/media/muteButton.png) no-repeat center;
   /* Remove the native button appearance and styling */
   -moz-appearance: none;
   margin: 0;
   padding: 0;
   min-height: 28px;
   min-width: 33px;
   border: none;
 }
-.muteButton[muted="true"] {
+.muteButton[muted] {
   background: url(chrome://global/skin/media/unmuteButton.png) no-repeat center;
 }
 
+.fullscreenButton {
+  background-color: transparent;
+  list-style-image: url("chrome://global/skin/media/fullscreenButton.png");
+  -moz-image-region: rect(0px, 16px, 16px, 0px);
+  -moz-appearance: none;
+  margin: 0;
+  padding: 0;
+  min-height: 28px;
+  min-width: 28px;
+  border: none;
+}
+
+.fullscreenButton[fullscreened] {
+  -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
 .volumeStack {
   width: 28px;
   height: 70px;
   background-color: rgba(35,31,32,.74);
   /* use negative margin to place stack over the mute button to its left. */
   margin: -70px 3px 28px -31px;
   overflow: hidden; /* crop it when sliding down, don't grow the control bar */
   position: relative; /* Trick to work around negative margin interfering with dragging the thumb. */
@@ -63,17 +79,16 @@
 
 .volumeBackgroundBar {
   /* margin left/right: make bar 8px wide (control width = 28, minus 2 * 10 margin) */
   margin: 0 10px;
   background-color: rgba(255,255,255,.5);
   border-radius: 4px 4px;
 }
 
-
 .durationBox {
   -moz-box-pack: center;
 }
 
 .durationLabel {
   margin-left: -22px; /* 1/2 of scrubber thumb width, for overhang. */
   padding-left: 8px; /* don't bump into the scrubber bar */
   padding-top: 0; /* center vertically with scrubber bar */