Bug 1285591 - fix accessibility in devtools autocomplete using suggestion list clone;r=bgrins

Devtools autocomplete popups are hosted in a different document from the input
being autocompleted. To allow accessibility tools such as screen readers to still
make sense of this widget, a clone of the suggestion list is now inserted in the same
document as the input, and the aria-activedescendant attribute is updated on the input
accordingly.

MozReview-Commit-ID: 8rFjF6nvEyU
This commit is contained in:
Julian Descottes
2016-07-22 17:35:03 +02:00
parent 7b99658449
commit e49ad837f4
4 changed files with 97 additions and 4 deletions

View File

@@ -61,7 +61,11 @@ function AutocompletePopup(toolbox, options = {}) {
this._list = this._document.createElementNS(HTML_NS, "ul");
this._list.setAttribute("flex", "1");
this._list.setAttribute("seltype", "single");
// The list clone will be inserted in the same document as the anchor, and will receive
// a copy of the main list innerHTML to allow screen readers to access the list.
this._listClone = this._document.createElementNS(HTML_NS, "ul");
this._listClone.className = "devtools-autocomplete-list-aria-clone";
if (options.listId) {
this._list.setAttribute("id", options.listId);
@@ -122,6 +126,10 @@ AutocompletePopup.prototype = {
openPopup: function (anchor, xOffset = 0, yOffset = 0, index) {
this.__maxLabelLength = -1;
this._updateSize();
// Retrieve the anchor's document active element to add accessibility metadata.
this._activeElement = anchor.ownerDocument.activeElement;
this._tooltip.show(anchor, {
x: xOffset,
y: yOffset,
@@ -159,6 +167,9 @@ AutocompletePopup.prototype = {
this._tooltip.once("hidden", () => {
this.emit("popup-closed");
});
this._clearActiveDescendant();
this._activeElement = null;
this._tooltip.hide();
},
@@ -187,6 +198,7 @@ AutocompletePopup.prototype = {
}
this._list.remove();
this._listClone.remove();
this._tooltip.destroy();
this._document = null;
this._list = null;
@@ -322,6 +334,9 @@ AutocompletePopup.prototype = {
element.classList.add("autocomplete-selected");
this._scrollElementIntoViewIfNeeded(element);
this._setActiveDescendant(element.id);
} else {
this._clearActiveDescendant();
}
this._selectedIndex = index;
@@ -352,6 +367,41 @@ AutocompletePopup.prototype = {
}
},
/**
* Update the aria-activedescendant attribute on the current active element for
* accessibility.
*
* @param {String} id
* The id (as in DOM id) of the currently selected autocomplete suggestion
*/
_setActiveDescendant: function (id) {
if (!this._activeElement) {
return;
}
// Make sure the list clone is in the same document as the anchor.
let anchorDoc = this._activeElement.ownerDocument;
if (!this._listClone.parentNode || this._listClone.ownerDocument !== anchorDoc) {
anchorDoc.documentElement.appendChild(this._listClone);
}
// Update the clone content to match the current list content.
this._listClone.innerHTML = this._list.innerHTML;
this._activeElement.setAttribute("aria-activedescendant", id);
},
/**
* Clear the aria-activedescendant attribute on the current active element.
*/
_clearActiveDescendant: function () {
if (!this._activeElement) {
return;
}
this._activeElement.removeAttribute("aria-activedescendant");
},
/**
* Append an item into the autocomplete list.
*