Bug 1107695 - Make one-off buttons accessible. r=florian, f=MarcoZ, a=lsblakk
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Mon, 16 Feb 2015 14:23:47 +0000
changeset 250092 4fc445cb5642
parent 250091 4564e0e22a37
child 250093 5a0ed5076b4f
push id4501
push userryanvm@gmail.com
push date2015-02-27 20:57 +0000
treeherdermozilla-beta@2aa1ca037446 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflorian, lsblakk
bugs1107695
milestone37.0
Bug 1107695 - Make one-off buttons accessible. r=florian, f=MarcoZ, a=lsblakk
browser/base/content/urlbarBindings.xml
browser/components/search/content/search.xml
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -998,48 +998,59 @@
         <xul:hbox anonid="search-panel-searchforwith"
                   class="search-panel-current-input">
           <xul:label anonid="searchbar-oneoffheader-before" value="&searchFor.label;"/>
           <xul:label anonid="searchbar-oneoffheader-searchtext" flex="1" crop="end" class="search-panel-input-value"/>
           <xul:label anonid="searchbar-oneoffheader-after" flex="10000" value="&searchWith.label;"/>
         </xul:hbox>
       </xul:deck>
       <xul:description anonid="search-panel-one-offs"
+                       role="group"
                        class="search-panel-one-offs"/>
       <xul:vbox anonid="add-engines"/>
       <xul:button anonid="search-settings"
                   oncommand="BrowserUITelemetry.countSearchSettingsEvent('searchbar');openPreferences('paneSearch')"
                   class="search-setting-button search-panel-header"
                   label="&changeSearchSettings.button;"/>
     </content>
     <implementation>
       <!-- Popup rollup is triggered by native events before the mousedown event
            reaches the DOM. The will be set to true by the popuphiding event and
            false after the mousedown event has been triggered to detect what
            caused rollup. -->
       <field name="_isHiding">false</field>
+      <field name="_bundle">null</field>
+      <property name="bundle" readonly="true">
+        <getter>
+          <![CDATA[
+            if (!this._bundle) {
+              const kBundleURI = "chrome://browser/locale/search.properties";
+              this._bundle = Services.strings.createBundle(kBundleURI);
+            }
+            return this._bundle;
+          ]]>
+        </getter>
+      </property>
 
       <method name="updateHeader">
         <body><![CDATA[
           let currentEngine = Services.search.currentEngine;
           let uri = currentEngine.iconURI;
           if (uri) {
             uri = uri.spec;
             this.setAttribute("src", PlacesUtils.getImageURLForResolution(window, uri));
           }
           else {
             // If the default has just been changed to a provider without icon,
             // avoid showing the icon of the previous default provider.
             this.removeAttribute("src");
           }
 
-          const kBundleURI = "chrome://browser/locale/search.properties";
-          let bundle = Services.strings.createBundle(kBundleURI);
-          let headerText = bundle.formatStringFromName("searchHeader",
-                                                       [currentEngine.name], 1);
+          let headerText = this.bundle.formatStringFromName("searchHeader",
+                                                            [currentEngine.name], 1);
           document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine-name")
                   .setAttribute("value", headerText);
           document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine")
                   .engine = currentEngine;
         ]]></body>
       </method>
     </implementation>
     <handlers>
@@ -1074,27 +1085,38 @@
 
         // Update the 'Search for <keywords> with:" header.
         let headerSearchText =
           document.getAnonymousElementByAttribute(this, "anonid",
                                                   "searchbar-oneoffheader-searchtext");
         let headerPanel =
           document.getAnonymousElementByAttribute(this, "anonid",
                                                   "search-panel-one-offs-header");
+        let list = document.getAnonymousElementByAttribute(this, "anonid",
+                                                           "search-panel-one-offs");
         let textbox = searchbar.textbox;
         let self = this;
         let inputHandler = function() {
           headerSearchText.setAttribute("value", textbox.value);
+          let groupText;
           if (textbox.value) {
             self.removeAttribute("showonlysettings");
+            groupText = headerSearchText.previousSibling.value +
+                        '"' + headerSearchText.value + '"' +
+                        headerSearchText.nextSibling.value;
             headerPanel.selectedIndex = 1;
           }
           else {
+            let noSearchHeader =
+              document.getAnonymousElementByAttribute(self, "anonid",
+                                                      "searchbar-oneoffheader-search");
+            groupText = noSearchHeader.value;
             headerPanel.selectedIndex = 0;
           }
+          list.setAttribute("aria-label", groupText);
         };
         textbox.addEventListener("input", inputHandler);
         this.addEventListener("popuphiding", function hiding() {
           textbox.removeEventListener("input", inputHandler);
           this.removeEventListener("popuphiding", hiding);
         });
         inputHandler();
 
@@ -1106,22 +1128,21 @@
         while (addEngineList.firstChild)
           addEngineList.firstChild.remove();
 
         const kXULNS =
           "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
         let addEngines = gBrowser.selectedBrowser.engines;
         if (addEngines && addEngines.length > 0) {
-          const kBundleURI = "chrome://browser/locale/search.properties";
-          let bundle = Services.strings.createBundle(kBundleURI);
           for (let engine of addEngines) {
             let button = document.createElementNS(kXULNS, "button");
-            let label = bundle.formatStringFromName("cmd_addFoundEngine",
-                                                    [engine.title], 1);
+            let label = this.bundle.formatStringFromName("cmd_addFoundEngine",
+                                                         [engine.title], 1);
+            button.id = "searchbar-add-engine-" + engine.title.replace(/ /g, '-');
             button.setAttribute("class", "addengine-item");
             button.setAttribute("label", label);
             button.setAttribute("pack", "start");
 
             button.setAttribute("crop", "end");
             button.setAttribute("tooltiptext", engine.uri);
             button.setAttribute("uri", engine.uri);
             if (engine.icon) {
@@ -1129,18 +1150,16 @@
               button.setAttribute("image", uri);
             }
             button.setAttribute("title", engine.title);
             addEngineList.appendChild(button);
           }
         }
 
         // Finally, build the list of one-off buttons.
-        let list = document.getAnonymousElementByAttribute(this, "anonid",
-                                                           "search-panel-one-offs")
         while (list.firstChild)
           list.firstChild.remove();
         textbox._selectedButton = null;
 
         let hiddenList;
         try {
           let pref =
             Services.prefs.getCharPref("browser.search.hiddenOneOffs");
@@ -1190,21 +1209,25 @@
 
         // If the <description> tag with the list of search engines doesn't have
         // a fixed height, the panel will be sized incorrectly, causing the bottom
         // of the suggestion <tree> to be hidden.
         let rowCount = Math.ceil(engines.length / enginesPerRow);
         let height = rowCount * 33; // 32px per row, 1px border.
         list.setAttribute("height", height + "px");
 
+        // Ensure we can refer to the settings button by ID:
+        let settingsEl = document.getAnonymousElementByAttribute(this, "anonid", "search-settings");
+        settingsEl.id = this.id + "-anon-search-settings";
+
         let dummyItems = enginesPerRow - (engines.length % enginesPerRow || enginesPerRow);
         for (let i = 0; i < engines.length; ++i) {
           let engine = engines[i];
           let button = document.createElementNS(kXULNS, "button");
-          button.setAttribute("label", engine.name);
+          button.id = "searchbar-engine-one-off-item-" + engine.name.replace(/ /g, '-');
           let uri = "chrome://browser/skin/search-engine-placeholder.png";
           if (engine.iconURI) {
             uri = PlacesUtils.getImageURLForResolution(window, engine.iconURI.spec);
           }
           button.setAttribute("image", uri);
           button.setAttribute("class", "searchbar-engine-one-off-item");
           button.setAttribute("tooltiptext", engine.name);
           button.setAttribute("width", buttonWidth);
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -821,16 +821,18 @@
             pasteAndSearch.setAttribute("disabled", "true");
         }, false);
 
         var element, label, akey;
 
         element = document.createElementNS(kXULNS, "menuseparator");
         cxmenu.appendChild(element);
 
+        this.setAttribute("aria-owns", this.popup.id);
+
         var insertLocation = cxmenu.firstChild;
         while (insertLocation.nextSibling &&
                insertLocation.getAttribute("cmd") != "cmd_paste")
           insertLocation = insertLocation.nextSibling;
         if (insertLocation) {
           element = document.createElementNS(kXULNS, "menuitem");
           label = this._stringBundle.getString("cmd_pasteAndSearch");
           element.setAttribute("label", label);
@@ -1009,19 +1011,21 @@
         <setter><![CDATA[
           if (this._selectedButton)
             this._selectedButton.removeAttribute("selected");
 
           // Avoid selecting dummy buttons.
           if (val && !val.classList.contains("dummy")) {
             val.setAttribute("selected", "true");
             this._selectedButton = val;
+            this.setAttribute("aria-activedescendant", val.id);
             return;
           }
 
+          this.removeAttribute("aria-activedescendant");
           this._selectedButton = null;
         ]]></setter>
       </property>
 
       <method name="getSelectableButtons">
         <parameter name="aCycleEngines"/>
         <body><![CDATA[
           let buttons = [];