Bug 1242852 - (part 1) making top dev tools toolbar keyboard accessible. r=bgrins

MozReview-Commit-ID: MPMzYnbZOM
---
 devtools/client/framework/test/browser.ini         |  2 +
 .../test/browser_toolbox_keyboard_navigation.js    | 88 ++++++++++++++++++++++
 devtools/client/framework/toolbox.js               | 67 ++++++++++++++++
 devtools/client/themes/toolbars.css                |  3 +
 4 files changed, 160 insertions(+)
 create mode 100644 devtools/client/framework/test/browser_toolbox_keyboard_navigation.js

--HG--
extra : rebase_source : d26a1dc86672a77b095e212ed658ab3fd89c0af2
This commit is contained in:
Yura Zenevich 2016-04-12 11:53:28 -04:00
Родитель d7620bcfda
Коммит 428478e041
4 изменённых файлов: 160 добавлений и 0 удалений

Просмотреть файл

@ -41,6 +41,8 @@ support-files =
[browser_toolbox_highlight.js]
[browser_toolbox_hosts.js]
[browser_toolbox_hosts_size.js]
[browser_toolbox_keyboard_navigation.js]
skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
[browser_toolbox_minimize.js]
skip-if = true # Bug 1177463 - Temporarily hide the minimize button
[browser_toolbox_options.js]

Просмотреть файл

@ -0,0 +1,88 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests keyboard navigation of devtools tabbar.
const TEST_URL =
"data:text/html;charset=utf8,test page for toolbar keyboard navigation";
function containsFocus(aDoc, aElm) {
let elm = aDoc.activeElement;
while (elm) {
if (elm === aElm) { return true; }
elm = elm.parentNode;
}
return false;
}
function testFocus(aDoc, aToolbar, aElm) {
let id = aElm.id;
is(aToolbar.getAttribute("aria-activedescendant"), id,
`Active descendant is set to a new control: ${id}`);
is(aDoc.activeElement.id, id, "New control is focused");
}
add_task(function*() {
info("Create a test tab and open the toolbox");
let toolbox = yield openNewTabAndToolbox(TEST_URL, "webconsole");
let doc = toolbox.doc;
let toolbar = doc.querySelector(".devtools-tabbar");
let toolbarControls = [...toolbar.querySelectorAll(
".devtools-tab, toolbarbutton")].filter(elm =>
!elm.hidden && doc.defaultView.getComputedStyle(elm).getPropertyValue(
"display") !== "none");
// Put the keyboard focus onto the first toolbar control.
toolbarControls[0].focus();
ok(containsFocus(doc, toolbar), "Focus is within the toolbar");
// Move the focus away from toolbar to a next focusable element.
EventUtils.synthesizeKey("VK_TAB", {});
ok(!containsFocus(doc, toolbar), "Focus is outside of the toolbar");
// Move the focus back to the toolbar.
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
ok(containsFocus(doc, toolbar), "Focus is within the toolbar again");
// Move through the toolbar forward using the right arrow key.
for (let i = 0; i < toolbarControls.length; ++i) {
testFocus(doc, toolbar, toolbarControls[i]);
if (i < toolbarControls.length - 1) {
EventUtils.synthesizeKey("VK_RIGHT", {});
}
}
// Move the focus away from toolbar to a next focusable element.
EventUtils.synthesizeKey("VK_TAB", {});
ok(!containsFocus(doc, toolbar), "Focus is outside of the toolbar");
// Move the focus back to the toolbar.
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
ok(containsFocus(doc, toolbar), "Focus is within the toolbar again");
// Move through the toolbar backward using the left arrow key.
for (let i = toolbarControls.length - 1; i >= 0; --i) {
testFocus(doc, toolbar, toolbarControls[i]);
if (i > 0) { EventUtils.synthesizeKey("VK_LEFT", {}); }
}
// Move focus to the 3rd (non-first) toolbar control.
let expectedFocusedControl = toolbarControls[2];
EventUtils.synthesizeKey("VK_RIGHT", {});
EventUtils.synthesizeKey("VK_RIGHT", {});
testFocus(doc, toolbar, expectedFocusedControl);
// Move the focus away from toolbar to a next focusable element.
EventUtils.synthesizeKey("VK_TAB", {});
ok(!containsFocus(doc, toolbar), "Focus is outside of the toolbar");
// Move the focus back to the toolbar, ensure we land on the last active
// descendant control.
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
testFocus(doc, toolbar, expectedFocusedControl);
});

Просмотреть файл

@ -413,6 +413,7 @@ Toolbox.prototype = {
this._addZoomKeys();
this._loadInitialZoom();
}
this._setToolbarKeyboardNavigation();
this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole");
this.webconsolePanel.height = Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF);
@ -906,6 +907,72 @@ Toolbox.prototype = {
}
},
/**
* Sets up keyboard navigation with and within the dev tools toolbar.
*/
_setToolbarKeyboardNavigation() {
let toolbar = this.doc.querySelector(".devtools-tabbar");
// Set and track aria-activedescendant to indicate which control is
// currently focused within the toolbar (for accessibility purposes).
toolbar.addEventListener("focus", event => {
let { target, rangeParent } = event;
let control, controlID = toolbar.getAttribute("aria-activedescendant");
if (controlID) {
control = this.doc.getElementById(controlID);
}
if (rangeParent || !control) {
// If range parent is present, the focused is moved within the toolbar,
// simply updating aria-activedescendant. Or if aria-activedescendant is
// not available, set it to target.
toolbar.setAttribute("aria-activedescendant", target.id);
} else {
// When range parent is not present, we focused into the toolbar, move
// focus to current aria-activedescendant.
event.preventDefault();
control.focus();
}
}, true)
toolbar.addEventListener("keypress", event => {
let { key, target } = event;
let win = this.doc.defaultView;
let elm, type;
if (key === "Tab") {
// Tabbing when toolbar or its contents are focused should move focus to
// next/previous focusable element relative to toolbar itself.
if (event.shiftKey) {
elm = toolbar;
type = Services.focus.MOVEFOCUS_BACKWARD;
} else {
// To move focus to next element following the toolbar, relative
// element needs to be the last element in its subtree.
let last = toolbar.lastChild;
while (last && last.lastChild) {
last = last.lastChild;
}
elm = last;
type = Services.focus.MOVEFOCUS_FORWARD;
}
} else if (key === "ArrowLeft") {
// Using left arrow key inside toolbar should move focus to previous
// toolbar control.
elm = target;
type = Services.focus.MOVEFOCUS_BACKWARD;
} else if (key === "ArrowRight") {
// Using right arrow key inside toolbar should move focus to next
// toolbar control.
elm = target;
type = Services.focus.MOVEFOCUS_FORWARD;
} else {
// Ignore all other keys.
return;
}
event.preventDefault();
Services.focus.moveFocus(win, elm, type, 0);
});
},
/**
* Add buttons to the UI as specified in the devtools.toolbox.toolbarSpec pref
*/

Просмотреть файл

@ -605,6 +605,7 @@
#toolbox-controls > toolbarbutton,
#toolbox-dock-buttons > toolbarbutton {
-moz-appearance: none;
-moz-user-focus: normal;
border: none;
margin: 0 4px;
min-width: 16px;
@ -691,6 +692,7 @@
margin: 0;
width: 32px;
position: relative;
-moz-user-focus: normal;
}
.command-button:hover {
@ -812,6 +814,7 @@
border-width: 0;
-moz-border-start-width: 1px;
-moz-box-align: center;
-moz-user-focus: normal;
}
.theme-dark .devtools-tab {