Bug 1546954 - Clean up Picture-in-Picture keyboard support for the player window. r=JSON_voorhees

This patch does a couple of things, based on trial-and-error, as well as a
conversation with yzen from the Accessibility team.

1. Auto-focuses the playpause button, and puts unpip and then close as the next
   items that can be focused with the keyboard. The underlying <browser> element
   is no longer keyboard focusable.

2. It adds a white outline with a black box-shadow around the focused control
   when using keyboard navigation. That outline and box-shadow do not appear
   when using the mouse only. Keyboard navigation can be aborted by hitting Esc.

3. The controls remain visible while the video is paused.

4. When transitioning from the paused to playing state, we wait
   CONTROLS_FADE_TIMEOUT_MS milliseconds to hide the controls again.

Differential Revision: https://phabricator.services.mozilla.com/D37887

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Mike Conley 2019-07-17 16:18:45 +00:00
parent fa7b029ef3
commit b0d89f3530
3 changed files with 60 additions and 11 deletions

View File

@ -43,7 +43,7 @@ function setIsPlayingState(isPlaying) {
* events for updating state.
*/
let Player = {
WINDOW_EVENTS: ["click", "mouseout", "resize", "unload"],
WINDOW_EVENTS: ["click", "keydown", "mouseout", "resize", "unload"],
mm: null,
/**
* Used for resizing Telemetry to avoid recording an event for every resize
@ -59,6 +59,12 @@ let Player = {
lastScreenY: -1,
id: -1,
/**
* When set to a non-null value, a timer is scheduled to hide the controls
* after CONTROLS_FADE_TIMEOUT_MS milliseconds.
*/
showingTimeout: null,
/**
* Initializes the player browser, and sets up the initial state.
*
@ -93,12 +99,7 @@ let Player = {
// just close the window for now.
browser.addEventListener("oop-browser-crashed", this);
// Show the controls immediately, but set them up to fade out after
// CONTROLS_FADE_TIMEOUT_MS if the mouse isn't hovering them.
this.controls.setAttribute("showing", true);
setTimeout(() => {
this.controls.removeAttribute("showing");
}, CONTROLS_FADE_TIMEOUT_MS);
this.revealControls(false);
Services.telemetry.setEventRecordingEnabled("pictureinpicture", true);
@ -129,6 +130,16 @@ let Player = {
switch (event.type) {
case "click": {
this.onClick(event);
this.controls.removeAttribute("keying");
break;
}
case "keydown": {
if (event.keyCode == KeyEvent.DOM_VK_TAB) {
this.controls.setAttribute("keying", true);
} else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
this.controls.removeAttribute("keying");
}
break;
}
@ -164,9 +175,12 @@ let Player = {
case "playpause": {
if (!this.isPlaying) {
this.mm.sendAsyncMessage("PictureInPicture:Play");
this.revealControls(false);
} else {
this.mm.sendAsyncMessage("PictureInPicture:Pause");
this.revealControls(true);
}
break;
}
@ -230,4 +244,25 @@ let Player = {
args
);
},
/**
* Makes the player controls visible.
*
* @param revealIndefinitely (Boolean)
* If false, this will hide the controls again after
* CONTROLS_FADE_TIMEOUT_MS milliseconds has passed. If true, the controls
* will remain visible until revealControls is called again with
* revealIndefinitely set to false.
*/
revealControls(revealIndefinitely) {
clearTimeout(this.showingTimeout);
this.showingTimeout = null;
this.controls.setAttribute("showing", true);
if (!revealIndefinitely) {
this.showingTimeout = setTimeout(() => {
this.controls.removeAttribute("showing");
}, CONTROLS_FADE_TIMEOUT_MS);
}
},
};

View File

@ -19,15 +19,15 @@
<body>
<div class="player-holder">
<xul:browser type="content" primary="true" remote="true" remoteType="web" id="browser"></xul:browser>
<xul:browser type="content" primary="true" remote="true" remoteType="web" id="browser" tabindex="-1"></xul:browser>
</div>
<div id="controls">
<button id="close" class="control-item"></button>
<button id="close" class="control-item" tabindex="3"></button>
<div id="controls-bottom">
<button id="unpip" class="control-item"></button>
<button id="unpip" class="control-item" tabindex="2"></button>
<div id="gap"></div>
<button id="playpause" class="control-item"></button>
<button id="playpause" class="control-item" tabindex="1" autofocus="true"></button>
</div>
</div>
</body>

View File

@ -8,6 +8,10 @@
--resize-margin: 5px;
}
button::-moz-focus-inner {
border: 0;
}
body {
margin: 0;
}
@ -38,6 +42,11 @@ browser {
border: 0;
}
#controls[keying] button:-moz-focusring {
outline: 2px solid #fff;
box-shadow: 1px 2px 5px #000;
}
#controls-bottom {
display: flex;
position: absolute;
@ -72,10 +81,15 @@ browser {
background-repeat: no-repeat;
}
#controls[keying] .control-item:focus {
border-radius: 0px;
}
#controls:hover .control-item {
opacity: 0.8;
}
#controls[keying] .control-item,
#controls[showing] .control-item,
#controls:hover .control-item:hover {
opacity: 1;