Bug 1476854 - Expose whether tabs are multiselected for accessibility. r?dao,jamie draft
authorJared Wein <jwein@mozilla.com>
Tue, 31 Jul 2018 16:40:17 -0400
changeset 828967 ac4f1ee65b9aaab23bb2d02584f65fc3c1b414f0
parent 828887 8b39d1161075364a95bc2d1577b389411fe5c342
push id118737
push userbmo:jaws@mozilla.com
push dateTue, 14 Aug 2018 16:41:35 +0000
reviewersdao, jamie
bugs1476854
milestone63.0a1
Bug 1476854 - Expose whether tabs are multiselected for accessibility. r?dao,jamie MozReview-Commit-ID: 4LAwQxqrKiO
browser/base/content/tabbrowser.js
browser/base/content/tabbrowser.xml
browser/base/content/test/tabs/browser_multiselect_tabs_using_selectedTabs.js
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -3719,16 +3719,17 @@ window._gBrowser = {
   },
 
   addToMultiSelectedTabs(aTab, skipPositionalAttributes) {
     if (aTab.multiselected) {
       return;
     }
 
     aTab.setAttribute("multiselected", "true");
+    aTab.setAttribute("aria-selected", "true");
     this._multiSelectedTabsSet.add(aTab);
     this._startMultiSelectChange();
     if (this._multiSelectChangeRemovals.has(aTab)) {
       this._multiSelectChangeRemovals.delete(aTab);
     } else {
       this._multiSelectChangeAdditions.add(aTab);
     }
 
@@ -3758,16 +3759,17 @@ window._gBrowser = {
     this.tabContainer._setPositionalAttributes();
   },
 
   removeFromMultiSelectedTabs(aTab, updatePositionalAttributes) {
     if (!aTab.multiselected) {
       return;
     }
     aTab.removeAttribute("multiselected");
+    aTab.removeAttribute("aria-selected");
     this._multiSelectedTabsSet.delete(aTab);
     this._startMultiSelectChange();
     if (this._multiSelectChangeAdditions.has(aTab)) {
       this._multiSelectChangeAdditions.delete(aTab);
     } else {
       this._multiSelectChangeRemovals.add(aTab);
     }
     if (updatePositionalAttributes) {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -123,16 +123,21 @@
             newValue => {
               const LIMIT = 50;
               return Math.max(newValue, LIMIT);
             },
           );
 
           this._tabMinWidth = this._tabMinWidthPref;
 
+          XPCOMUtils.defineLazyPreferenceGetter(this, "_multiselectEnabledPref",
+            "browser.tabs.multiselect", null,
+            (pref, prevValue, newValue) => this._multiselectEnabled = newValue);
+          this._multiselectEnabled = this._multiselectEnabledPref;
+
           this._setPositionalAttributes();
 
           CustomizableUI.addListener(this);
           this._updateNewTabVisibility();
 
           XPCOMUtils.defineLazyPreferenceGetter(this, "_closeTabByDblclick",
             "browser.tabs.closeTabByDblclick", false);
         ]]>
@@ -167,16 +172,27 @@
 
       <property name="_tabMinWidth">
         <setter>
           this.style.setProperty("--tab-min-width", val + "px");
           return val;
         </setter>
       </property>
 
+      <property name="_multiselectEnabled">
+        <setter>
+          // Unlike boolean HTML attributes, the value of boolean ARIA attributes actually matters.
+          this.setAttribute("aria-multiselectable", !!val);
+          return val;
+        </setter>
+        <getter>
+          return this.getAttribute("aria-multiselectable") == "true";
+        </getter>
+      </property>
+
       <method name="observe">
         <parameter name="aSubject"/>
         <parameter name="aTopic"/>
         <parameter name="aData"/>
         <body><![CDATA[
           switch (aTopic) {
             case "nsPref:changed":
               // This is has to deal with changes in
@@ -2064,17 +2080,17 @@
           this._selectedOnFirstMouseDown = this.selected;
         }
 
         if (this.selected) {
           this.style.MozUserFocus = "ignore";
         } else {
           // When browser.tabs.multiselect config is set to false,
           // then we ignore the state of multi-selection keys (Ctrl/Cmd).
-          const tabSelectionToggled = Services.prefs.getBoolPref("browser.tabs.multiselect") &&
+          const tabSelectionToggled = tabContainer._multiselectEnabled &&
             (event.getModifierState("Accel") || event.shiftKey);
 
           if (this.mOverCloseButton || this._overPlayingIcon || tabSelectionToggled) {
             // Prevent tabbox.xml from selecting the tab.
             event.stopPropagation();
           }
         }
 
@@ -2087,17 +2103,18 @@
         // Make sure that clear-selection is released.
         // Otherwise selection using Shift key may be broken.
         gBrowser.unlockClearMultiSelection();
 
         this.style.MozUserFocus = "";
       </handler>
 
       <handler event="click" button="0"><![CDATA[
-        if (Services.prefs.getBoolPref("browser.tabs.multiselect")) {
+        let tabContainer = this.parentNode;
+        if (tabContainer._multiselectEnabled) {
           let shiftKey = event.shiftKey;
           let accelKey = event.getModifierState("Accel");
           if (shiftKey) {
             const lastSelectedTab = gBrowser.lastMultiSelectedTab;
             if (!accelKey) {
               gBrowser.selectedTab = lastSelectedTab;
 
               // Make sure selection is cleared when tab-switch doesn't happen.
--- a/browser/base/content/test/tabs/browser_multiselect_tabs_using_selectedTabs.js
+++ b/browser/base/content/test/tabs/browser_multiselect_tabs_using_selectedTabs.js
@@ -7,30 +7,35 @@ const PREF_MULTISELECT_TABS = "browser.t
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({
     set: [
       [PREF_MULTISELECT_TABS, true]
     ]
   });
 
   function testSelectedTabs(tabs) {
+    is(gBrowser.tabContainer.getAttribute("aria-multiselectable"), "true",
+       "tabbrowser should be marked as aria-multiselectable");
     gBrowser.selectedTabs = tabs;
     let {selectedTab, selectedTabs, _multiSelectedTabsSet} = gBrowser;
     is(selectedTab, tabs[0], "The selected tab should be the expected one");
     if (tabs.length == 1) {
       ok(!selectedTab.multiselected, "Selected tab shouldn't be multi-selected because we are not in multi-select context yet");
       ok(!_multiSelectedTabsSet.has(selectedTab), "Selected tab shouldn't be in _multiSelectedTabsSet");
       is(selectedTabs.length, 1, "selectedTabs should contain a single tab");
       is(selectedTabs[0], selectedTab, "selectedTabs should contain the selected tab");
+      ok(!selectedTab.hasAttribute("aria-selected"),
+         "Selected tab shouldn't be marked as aria-selected when only one tab is selected");
     } else {
       ok(selectedTabs.length, tabs.length, "Check number of selected tabs");
       for (let tab of tabs) {
         ok(tab.multiselected, "Tab should be multi-selected");
         ok(_multiSelectedTabsSet.has(tab), "Tab should be in _multiSelectedTabsSet");
         ok(selectedTabs.includes(tab), "Tab should be in selectedTabs");
+        is(tab.getAttribute("aria-selected"), "true", "Selected tab should be marked as aria-selected");
       }
     }
   }
 
   const tab1 = await addTab();
   const tab2 = await addTab();
   const tab3 = await addTab();