зеркало из https://github.com/mozilla/gecko-dev.git
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 | 76 ++++++++++++++++++ .../test/browser_inspector_search_keyboard_trap.js | 93 ++++++++++++++++++++++ devtools/client/inspector/test/head.js | 17 ++++ 6 files changed, 236 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 : b546e19b0ce67b2c421bb1eef19c1b9919500692
This commit is contained in:
Родитель
d2529111e8
Коммит
f2d7b7dbab
|
@ -10,6 +10,7 @@ const {Cu, Ci} = require("chrome");
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const Services = require("Services");
|
||||
const promise = require("promise");
|
||||
const FocusManager = Services.focus;
|
||||
|
||||
const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms
|
||||
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("mouseover", 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.
|
||||
this.nodeHierarchy = [];
|
||||
|
@ -290,6 +292,24 @@ HTMLBreadcrumbs.prototype = {
|
|||
this.handleMouseOver(event);
|
||||
} else if (event.type == "mouseleave") {
|
||||
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
|
||||
});
|
||||
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));
|
||||
|
@ -403,6 +443,7 @@ HTMLBreadcrumbs.prototype = {
|
|||
this.container.removeEventListener("keypress", this, true);
|
||||
this.container.removeEventListener("mouseover", this, true);
|
||||
this.container.removeEventListener("mouseleave", this, true);
|
||||
this.container.removeEventListener("focus", this, true);
|
||||
|
||||
this.empty();
|
||||
this.separators.remove();
|
||||
|
|
|
@ -288,6 +288,12 @@ SelectorAutocompleter.prototype = {
|
|||
this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
|
||||
this.searchBox.value = this.searchPopup.selectedItem.label;
|
||||
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;
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ support-files =
|
|||
[browser_inspector_breadcrumbs.js]
|
||||
[browser_inspector_breadcrumbs_highlight_hover.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_mutations.js]
|
||||
[browser_inspector_delete-selected-node-01.js]
|
||||
|
@ -120,6 +122,7 @@ skip-if = (e10s && debug) # Bug 1250058 - Docshell leak on debug e10s
|
|||
[browser_inspector_search-05.js]
|
||||
[browser_inspector_search-06.js]
|
||||
[browser_inspector_search-07.js]
|
||||
[browser_inspector_search_keyboard_trap.js]
|
||||
[browser_inspector_search-reserved.js]
|
||||
[browser_inspector_search-selection.js]
|
||||
[browser_inspector_select-docshell.js]
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/* 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 { 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 onUpdated = inspector.once("breadcrumbs-updated");
|
||||
button.click();
|
||||
yield onUpdated;
|
||||
|
||||
// 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);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче