Bug 1242852 - (part 2) making inspector toolbar keyboard accessible. r=gl

MozReview-Commit-ID: BmLtydkQao7
---
 devtools/client/inspector/breadcrumbs.js           | 41 ++++++++++
 devtools/client/inspector/inspector-search.js      |  6 ++
 devtools/client/inspector/test/browser.ini         |  3 +
 .../browser_inspector_breadcrumbs_keyboard_trap.js | 79 ++++++++++++++++++
 .../test/browser_inspector_search_keyboard_trap.js | 93 ++++++++++++++++++++++
 devtools/client/inspector/test/head.js             | 17 ++++
 6 files changed, 239 insertions(+)
 create mode 100644 devtools/client/inspector/test/browser_inspector_breadcrumbs_keyboard_trap.js
 create mode 100644 devtools/client/inspector/test/browser_inspector_search_keyboard_trap.js

--HG--
extra : rebase_source : 5399de18ce0f5385088e17c247ffe251ccdf7c63
This commit is contained in:
Yura Zenevich 2016-04-12 11:53:54 -04:00
Родитель 428478e041
Коммит 5d862fb3dc
6 изменённых файлов: 239 добавлений и 0 удалений

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

@ -10,6 +10,7 @@ const {Cu, Ci} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const Services = require("Services"); const Services = require("Services");
const promise = require("promise"); const promise = require("promise");
const FocusManager = Services.focus;
const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms
const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data; const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
@ -72,6 +73,7 @@ HTMLBreadcrumbs.prototype = {
this.container.addEventListener("keypress", this, true); this.container.addEventListener("keypress", this, true);
this.container.addEventListener("mouseover", this, true); this.container.addEventListener("mouseover", this, true);
this.container.addEventListener("mouseleave", this, true); this.container.addEventListener("mouseleave", this, true);
this.container.addEventListener("focus", this, true);
// We will save a list of already displayed nodes in this array. // We will save a list of already displayed nodes in this array.
this.nodeHierarchy = []; this.nodeHierarchy = [];
@ -290,6 +292,24 @@ HTMLBreadcrumbs.prototype = {
this.handleMouseOver(event); this.handleMouseOver(event);
} else if (event.type == "mouseleave") { } else if (event.type == "mouseleave") {
this.handleMouseLeave(event); this.handleMouseLeave(event);
} else if (event.type == "focus") {
this.handleFocus(event);
}
},
/**
* Focus event handler. When breadcrumbs container gets focus, if there is an
* already selected breadcrumb, move focus to it.
* @param {DOMEvent} event.
*/
handleFocus: function(event) {
let control = this.container.querySelector(
".breadcrumbs-widget-item[checked]");
if (control && control !== event.target) {
// If we already have a selected breadcrumb and focus target is not it,
// move focus to selected breadcrumb.
event.preventDefault();
control.focus();
} }
}, },
@ -379,6 +399,26 @@ HTMLBreadcrumbs.prototype = {
whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT
}); });
break; break;
case this.chromeWin.KeyEvent.DOM_VK_TAB:
// Tabbing when breadcrumbs or its contents are focused should move
// focus to next/previous focusable element relative to breadcrumbs
// themselves.
let elm, type;
if (event.shiftKey) {
elm = this.container;
type = FocusManager.MOVEFOCUS_BACKWARD;
} else {
// To move focus to next element following the breadcrumbs, relative
// element needs to be the last element in breadcrumbs' subtree.
let last = this.container.lastChild;
while (last && last.lastChild) {
last = last.lastChild;
}
elm = last;
type = FocusManager.MOVEFOCUS_FORWARD;
}
FocusManager.moveFocus(this.chromeWin, elm, type, 0);
break;
} }
return navigate.then(node => this.navigateTo(node)); return navigate.then(node => this.navigateTo(node));
@ -403,6 +443,7 @@ HTMLBreadcrumbs.prototype = {
this.container.removeEventListener("keypress", this, true); this.container.removeEventListener("keypress", this, true);
this.container.removeEventListener("mouseover", this, true); this.container.removeEventListener("mouseover", this, true);
this.container.removeEventListener("mouseleave", this, true); this.container.removeEventListener("mouseleave", this, true);
this.container.removeEventListener("focus", this, true);
this.empty(); this.empty();
this.separators.remove(); this.separators.remove();

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

@ -288,6 +288,12 @@ SelectorAutocompleter.prototype = {
this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1; this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
this.searchBox.value = this.searchPopup.selectedItem.label; this.searchBox.value = this.searchPopup.selectedItem.label;
this.hidePopup(); this.hidePopup();
} else if (!this.searchPopup.isOpen && event.keyCode === event.DOM_VK_TAB) {
// When tab is pressed with focus on searchbox and closed popup,
// do not prevent the default to avoid a keyboard trap and move focus
// to next/previous element.
this.emit("processing-done");
return;
} }
break; break;

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

@ -43,6 +43,8 @@ support-files =
[browser_inspector_breadcrumbs.js] [browser_inspector_breadcrumbs.js]
[browser_inspector_breadcrumbs_highlight_hover.js] [browser_inspector_breadcrumbs_highlight_hover.js]
[browser_inspector_breadcrumbs_keybinding.js] [browser_inspector_breadcrumbs_keybinding.js]
[browser_inspector_breadcrumbs_keyboard_trap.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_inspector_breadcrumbs_menu.js] [browser_inspector_breadcrumbs_menu.js]
[browser_inspector_breadcrumbs_mutations.js] [browser_inspector_breadcrumbs_mutations.js]
[browser_inspector_delete-selected-node-01.js] [browser_inspector_delete-selected-node-01.js]
@ -121,6 +123,7 @@ skip-if = (e10s && debug) # Bug 1250058 - Docshell leak on debug e10s
[browser_inspector_search-05.js] [browser_inspector_search-05.js]
[browser_inspector_search-06.js] [browser_inspector_search-06.js]
[browser_inspector_search-07.js] [browser_inspector_search-07.js]
[browser_inspector_search_keyboard_trap.js]
[browser_inspector_search-reserved.js] [browser_inspector_search-reserved.js]
[browser_inspector_search-selection.js] [browser_inspector_search-selection.js]
[browser_inspector_select-docshell.js] [browser_inspector_select-docshell.js]

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

@ -0,0 +1,79 @@
/* 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";
// Test ability to tab to and away from breadcrumbs using keyboard.
const TEST_URL = URL_ROOT + "doc_inspector_breadcrumbs.html";
/**
* Test data has the format of:
* {
* desc {String} description for better logging
* focused {Boolean} flag, indicating if breadcrumbs contain focus
* key {String} key event's key
* options {?Object} optional event data such as shiftKey, etc
* }
*/
const TEST_DATA = [
{
desc: "Move the focus away from breadcrumbs to a next focusable element",
focused: false,
key: "VK_TAB",
options: { }
},
{
desc: "Move the focus back to the breadcrumbs",
focused: true,
key: "VK_TAB",
options: { shiftKey: true }
},
{
desc: "Move the focus back away from breadcrumbs to a previous focusable element",
focused: false,
key: "VK_TAB",
options: { shiftKey: true }
},
{
desc: "Move the focus back to the breadcrumbs",
focused: true,
key: "VK_TAB",
options: { }
}
];
add_task(function*() {
let { toolbox, inspector } = yield openInspectorForURL(TEST_URL);
let doc = inspector.panelDoc;
yield selectNode("#i2", inspector);
info("Clicking on the corresponding breadcrumbs node to focus it");
let container = doc.getElementById("inspector-breadcrumbs");
let button = container.querySelector("button[checked]");
let onHighlight = toolbox.once("node-highlight");
button.click();
yield onHighlight;
// Ensure a breadcrumb is focused.
is(doc.activeElement, button, "Focus is on selected breadcrumb");
for (let { desc, focused, key, options } of TEST_DATA) {
info(desc);
let onUpdated;
if (!focused) {
onUpdated = inspector.once("breadcrumbs-navigation-cancelled");
}
EventUtils.synthesizeKey(key, options);
if (focused) {
is(doc.activeElement, button, "Focus is on selected breadcrumb");
} else {
yield onUpdated;
ok(!containsFocus(doc, container), "Focus is outside of breadcrumbs");
}
}
});

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

@ -0,0 +1,93 @@
/* 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";
// Test ability to tab to and away from inspector search using keyboard.
const TEST_URL = URL_ROOT + "doc_inspector_search.html";
/**
* Test data has the format of:
* {
* desc {String} description for better logging
* focused {Boolean} flag, indicating if search box contains focus
* keys: {Array} list of keys that include key code and optional
* event data (shiftKey, etc)
* }
*
*/
const TEST_DATA = [
{
desc: "Move focus to a next focusable element",
focused: false,
keys: [
{
key: "VK_TAB",
options: { }
}
]
},
{
desc: "Move focus back to searchbox",
focused: true,
keys: [
{
key: "VK_TAB",
options: { shiftKey: true }
}
]
},
{
desc: "Open popup and then tab away (2 times) to the a next focusable element",
focused: false,
keys: [
{
key: "d",
options: { }
},
{
key: "VK_TAB",
options: { }
},
{
key: "VK_TAB",
options: { }
}
]
},
{
desc: "Move focus back to searchbox",
focused: true,
keys: [
{
key: "VK_TAB",
options: { shiftKey: true }
}
]
}
];
add_task(function*() {
let { inspector } = yield openInspectorForURL(TEST_URL);
let { searchBox } = inspector;
let doc = inspector.panelDoc;
yield selectNode("#b1", inspector);
yield focusSearchBoxUsingShortcut(inspector.panelWin);
// Ensure a searchbox is focused.
ok(containsFocus(doc, searchBox), "Focus is in a searchbox");
for (let { desc, focused, keys } of TEST_DATA) {
info(desc);
for (let { key, options } of keys) {
let done = !focused ?
inspector.searchSuggestions.once("processing-done") : Promise.resolve();
EventUtils.synthesizeKey(key, options);
yield done;
}
is(containsFocus(doc, searchBox), focused, "Focus is set correctly");
}
});

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

@ -655,3 +655,20 @@ function waitForClipboard(setup, expected) {
SimpleTest.waitForClipboard(expected, setup, def.resolve, def.reject); SimpleTest.waitForClipboard(expected, setup, def.resolve, def.reject);
return def.promise; return def.promise;
} }
/**
* Checks if document's active element is within the given element.
* @param {HTMLDocument} doc document with active element in question
* @param {DOMNode} container element tested on focus containment
* @return {Boolean}
*/
function containsFocus(doc, container) {
let elm = doc.activeElement;
while (elm) {
if (elm === container) {
return true;
}
elm = elm.parentNode;
}
return false;
}