зеркало из 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 | 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:
Родитель
428478e041
Коммит
5d862fb3dc
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче