Bug 486899 - Keyboard Accessibility on video element (also audio). r=mconnor, a=blocking191
☠☠ backed out by f2ee5c89f9ff ☠ ☠
authorJustin Dolske <dolske@mozilla.com>
Thu, 21 May 2009 23:53:47 -0700
changeset 25689 8387e5800784
parent 25688 03b37937512a
child 25690 41c389eaa495
child 25691 f2ee5c89f9ff
push id1536
push userjdolske@mozilla.com
push dateFri, 22 May 2009 06:55:10 +0000
reviewersmconnor, blocking191
bugs486899
milestone1.9.1pre
Bug 486899 - Keyboard Accessibility on video element (also audio). r=mconnor, a=blocking191
toolkit/content/widgets/videocontrols.css
toolkit/content/widgets/videocontrols.xml
--- a/toolkit/content/widgets/videocontrols.css
+++ b/toolkit/content/widgets/videocontrols.css
@@ -3,11 +3,15 @@
 .scrubber, .volumeControl {
     -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#suppressChangeEvent");
 }
 
 .scrubber .scale-thumb {
     -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#timeThumb");
 }
 
+.playButton, .muteButton {
+    -moz-user-focus: none;
+}
+
 .mediaControlsFrame {
     direction: ltr;
 }
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -795,34 +795,135 @@
                 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.
                 },
 
-                setPlayButtonState: function(aPaused)
-                {
+                setPlayButtonState : function(aPaused) {
                   this.playButton.setAttribute("paused", aPaused);
 
                   var attrName = aPaused ? "playlabel" : "pauselabel";
                   var value = this.playButton.getAttribute(attrName);
                   this.playButton.setAttribute("aria-label", value);
                 },
 
-                setMuteButtonState: function(aMuted)
-                {
+                setMuteButtonState : function(aMuted) {
                   this.muteButton.setAttribute("muted", aMuted);
 
                   var attrName = aMuted ? "unmutelabel" : "mutelabel";
                   var value = this.muteButton.getAttribute(attrName);
                   this.muteButton.setAttribute("aria-label", value);
                 },
 
+                keyHandler : function(event) {
+                    // Ignore keys when content might be providing its own.
+                    if (!this.video.hasAttribute("controls"))
+                        return;
+
+                    var keystroke = "";
+                    if (event.altKey)
+                        keystroke += "alt-";
+                    if (event.shiftKey)
+                        keystroke += "shift-";
+#ifdef XP_MACOSX
+                    if (event.metaKey)
+                        keystroke += "accel-";
+                    if (event.ctrlKey)
+                        keystroke += "control-";
+#else
+                    if (event.metaKey)
+                        keystroke += "meta-";
+                    if (event.ctrlKey)
+                        keystroke += "accel-";
+#endif
+
+                    switch (event.keyCode) {
+                        case KeyEvent.DOM_VK_UP:
+                            keystroke += "upArrow";
+                            break;
+                        case KeyEvent.DOM_VK_DOWN:
+                            keystroke += "downArrow";
+                            break;
+                        case KeyEvent.DOM_VK_LEFT:
+                            keystroke += "leftArrow";
+                            break;
+                        case KeyEvent.DOM_VK_RIGHT:
+                            keystroke += "rightArrow";
+                            break;
+                        case KeyEvent.DOM_VK_HOME:
+                            keystroke += "home";
+                            break;
+                        case KeyEvent.DOM_VK_END:
+                            keystroke += "end";
+                            break;
+                    }
+
+                    if (String.fromCharCode(event.charCode) == ' ')
+                        keystroke += "space";
+
+                    this.log("Got keystroke: " + keystroke);
+                    var oldval, newval;
+
+                    try {
+                        switch (keystroke) {
+                            case "space": /* Play */
+                                this.togglePause();
+                                break;
+                            case "downArrow": /* Volume decrease */
+                                oldval = this.video.volume;
+                                this.video.volume = (oldval < 0.1 ? 0 : oldval - 0.1);
+                                this.video.muted = false;
+                                break;
+                            case "upArrow": /* Volume increase */
+                                oldval = this.video.volume;
+                                this.video.volume = (oldval > 0.9 ? 1 : oldval + 0.1);
+                                this.video.muted = false;
+                                break;
+                            case "accel-downArrow": /* Mute */
+                                this.video.muted = true;
+                                break;
+                            case "accel-upArrow": /* Unmute */
+                                this.video.muted = false;
+                                break;
+                            case "leftArrow": /* Seek back 15 seconds */
+                            case "accel-leftArrow": /* Seek back 10% */
+                                oldval = this.video.currentTime;
+                                if (keystroke == "leftArrow")
+                                    newval = oldval - 15;
+                                else
+                                    newval = oldval - (this.video.duration || this.maxCurrentTimeSeen) / 10;
+                                this.video.currentTime = (newval >= 0 ? newval : 0);
+                                break;
+                            case "rightArrow": /* Seek forward 15 seconds */
+                            case "accel-rightArrow": /* Seek forward 10% */
+                                oldval = this.video.currentTime;
+                                var maxtime = (this.video.duration || this.maxCurrentTimeSeen);
+                                if (keystroke == "rightArrow")
+                                    newval = oldval + 15;
+                                else
+                                    newval = oldval + maxtime / 10;
+                                this.video.currentTime = (newval <= maxtime ? newval : maxtime);
+                                break;
+                            case "home": /* Seek to beginning */
+                                this.video.currentTime = 0;
+                                break;
+                            case "end": /* Seek to end */
+                                this.video.currentTime = (this.video.duration || this.maxCurrentTimeSeen);
+                                break;
+                            default:
+                                return;
+                        }
+                    } catch(e) { /* ignore any exception from setting .currentTime */ }
+
+                    event.preventDefault(); // Prevent page scrolling
+                },
+
                 isEventWithin : function (event, parent1, parent2) {
                     function isDescendant (node) {
                         while (node) {
                             if (node == parent1 || node == parent2)
                                 return true;
                             node = node.parentNode;
                         }
                         return false;
@@ -885,16 +986,20 @@
                     var self = this;
                     this.muteButton.addEventListener("command", function() { self.toggleMute(); }, false);
                     this.playButton.addEventListener("command", function() { self.togglePause(); }, false);
                     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);
 
+                    // 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 ---");
                 }
             }) ]]>
         </field>
 
 
     </implementation>