From ee9e873e226918026ef035dc37f2219b4da875b1 Mon Sep 17 00:00:00 2001 From: James Teh Date: Fri, 29 Aug 2014 11:36:50 +1000 Subject: [PATCH] Bug 977267 - [a11y] Make autocomplete suggestions accessible to screen readers. r=bgrins --- browser/devtools/shared/autocomplete-popup.js | 26 +++++++++++++++++++ ...ebconsole_bug_585991_autocomplete_popup.js | 19 ++++++++++++++ browser/devtools/webconsole/webconsole.xul | 3 ++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/browser/devtools/shared/autocomplete-popup.js b/browser/devtools/shared/autocomplete-popup.js index fd2252dd4e36..b2828ac8396e 100644 --- a/browser/devtools/shared/autocomplete-popup.js +++ b/browser/devtools/shared/autocomplete-popup.js @@ -65,6 +65,8 @@ function AutocompletePopup(aDocument, aOptions = {}) if (!aOptions.onKeypress) { this._panel.setAttribute("ignorekeys", "true"); } + // Stop this appearing as an alert to accessibility. + this._panel.setAttribute("role", "presentation"); let mainPopupSet = this._document.getElementById("mainPopupSet"); if (mainPopupSet) { @@ -106,6 +108,7 @@ function AutocompletePopup(aDocument, aOptions = {}) if (this.onKeypress) { this._list.addEventListener("keypress", this.onKeypress, false); } + this._itemIdCounter = 0; } exports.AutocompletePopup = AutocompletePopup; @@ -148,6 +151,8 @@ AutocompletePopup.prototype = { */ hidePopup: function AP_hidePopup() { + // Return accessibility focus to the input. + this._document.activeElement.removeAttribute("aria-activedescendant"); this._panel.hidePopup(); }, @@ -295,6 +300,23 @@ AutocompletePopup.prototype = { this._list.ensureIndexIsVisible(this._list.selectedIndex); }, + /** + * Update accessibility appropriately when the selected item is changed. + * + * @private + */ + _updateAriaActiveDescendant: function AP__updateAriaActiveDescendant() + { + if (!this._list.selectedItem) { + // Return accessibility focus to the input. + this._document.activeElement.removeAttribute("aria-activedescendant"); + return; + } + // Focus this for accessibility so users know about the selected item. + this._document.activeElement.setAttribute("aria-activedescendant", + this._list.selectedItem.id); + }, + /** * Clear all the items from the autocomplete list. */ @@ -340,6 +362,7 @@ AutocompletePopup.prototype = { if (this.isOpen && this._list.ensureIndexIsVisible) { this._list.ensureIndexIsVisible(this._list.selectedIndex); } + this._updateAriaActiveDescendant(); }, /** @@ -362,6 +385,7 @@ AutocompletePopup.prototype = { if (this.isOpen) { this._list.ensureIndexIsVisible(this._list.selectedIndex); } + this._updateAriaActiveDescendant(); }, /** @@ -383,6 +407,8 @@ AutocompletePopup.prototype = { appendItem: function AP_appendItem(aItem) { let listItem = this._document.createElementNS(XUL_NS, "richlistitem"); + // Items must have an id for accessibility. + listItem.id = this._panel.id + "_item_" + this._itemIdCounter++; if (this.direction) { listItem.setAttribute("dir", this.direction); } diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_popup.js b/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_popup.js index 2aa2edeb286d..0dc9141b18cf 100644 --- a/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_popup.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_popup.js @@ -22,7 +22,14 @@ function consoleOpened(HUD) { let popup = HUD.jsterm.autocompletePopup; + let input = popup._document.activeElement; + function getActiveDescendant() { + return input.ownerDocument.getElementById( + input.getAttribute("aria-activedescendant")); + } + ok(!popup.isOpen, "popup is not open"); + ok(!input.hasAttribute("aria-activedescendant"), "no aria-activedescendant"); popup._panel.addEventListener("popupshown", function() { popup._panel.removeEventListener("popupshown", arguments.callee, false); @@ -30,6 +37,8 @@ function consoleOpened(HUD) { ok(popup.isOpen, "popup is open"); is(popup.itemCount, 0, "no items"); + ok(!input.hasAttribute("aria-activedescendant"), + "no aria-activedescendant"); popup.setItems(items); @@ -43,31 +52,37 @@ function consoleOpened(HUD) { is(popup.selectedIndex, 2, "Index of the first item from bottom is selected."); is(popup.selectedItem, items[2], "First item from bottom is selected"); + ok(getActiveDescendant().selected, "aria-activedescendant is correct"); popup.selectedIndex = 1; is(popup.selectedIndex, 1, "index 1 is selected"); is(popup.selectedItem, items[1], "item1 is selected"); + ok(getActiveDescendant().selected, "aria-activedescendant is correct"); popup.selectedItem = items[2]; is(popup.selectedIndex, 2, "index 2 is selected"); is(popup.selectedItem, items[2], "item2 is selected"); + ok(getActiveDescendant().selected, "aria-activedescendant is correct"); is(popup.selectPreviousItem(), items[1], "selectPreviousItem() works"); is(popup.selectedIndex, 1, "index 1 is selected"); is(popup.selectedItem, items[1], "item1 is selected"); + ok(getActiveDescendant().selected, "aria-activedescendant is correct"); is(popup.selectNextItem(), items[2], "selectPreviousItem() works"); is(popup.selectedIndex, 2, "index 2 is selected"); is(popup.selectedItem, items[2], "item2 is selected"); + ok(getActiveDescendant().selected, "aria-activedescendant is correct"); ok(popup.selectNextItem(), "selectPreviousItem() works"); is(popup.selectedIndex, 0, "index 0 is selected"); is(popup.selectedItem, items[0], "item0 is selected"); + ok(getActiveDescendant().selected, "aria-activedescendant is correct"); items.push({label: "label3", value: "value3"}); popup.appendItem(items[3]); @@ -76,15 +91,19 @@ function consoleOpened(HUD) { popup.selectedIndex = 3; is(popup.selectedItem, items[3], "item3 is selected"); + ok(getActiveDescendant().selected, "aria-activedescendant is correct"); popup.removeItem(items[2]); is(popup.selectedIndex, 2, "index2 is selected"); is(popup.selectedItem, items[3], "item3 is still selected"); + ok(getActiveDescendant().selected, "aria-activedescendant is correct"); is(popup.itemCount, items.length - 1, "item2 removed"); popup.clearItems(); is(popup.itemCount, 0, "items cleared"); + ok(!input.hasAttribute("aria-activedescendant"), + "no aria-activedescendant"); popup.hidePopup(); finishTest(); diff --git a/browser/devtools/webconsole/webconsole.xul b/browser/devtools/webconsole/webconsole.xul index 39ecd52e39ce..a7ce9e6cf6ea 100644 --- a/browser/devtools/webconsole/webconsole.xul +++ b/browser/devtools/webconsole/webconsole.xul @@ -183,7 +183,8 @@ function goUpdateConsoleCommands() { + multiline="true" rows="1" tabindex="0" + aria-autocomplete="list"/>