Bug 1210778 - improving accessibility for about:debugging. r=janx
authorWes Kocher <wkocher@mozilla.com>
Fri, 15 Apr 2016 10:21:35 -0700
changeset 332431 7849c9bb0832a6b19753807576f9e0fcd4820e3b
parent 332430 8045e15106d71d8267ca52b75d51b70851143136
child 332432 fd06074f3bbf4e581742237ddf9dbef373ecb1d1
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjanx
bugs1210778
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1210778 - improving accessibility for about:debugging. r=janx MozReview-Commit-ID: 94il4DHDUOS
devtools/client/aboutdebugging/aboutdebugging.css
devtools/client/aboutdebugging/components/aboutdebugging.js
devtools/client/aboutdebugging/components/addon-target.js
devtools/client/aboutdebugging/components/addons-tab.js
devtools/client/aboutdebugging/components/tab-menu-entry.js
devtools/client/aboutdebugging/components/tab-menu.js
devtools/client/aboutdebugging/components/target-list.js
devtools/client/aboutdebugging/components/worker-target.js
devtools/client/aboutdebugging/components/workers-tab.js
devtools/client/aboutdebugging/test/.eslintrc
devtools/client/aboutdebugging/test/browser_service_workers.js
devtools/client/aboutdebugging/test/browser_service_workers_push.js
devtools/client/aboutdebugging/test/browser_service_workers_start.js
devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
devtools/client/aboutdebugging/test/head.js
--- a/devtools/client/aboutdebugging/aboutdebugging.css
+++ b/devtools/client/aboutdebugging/aboutdebugging.css
@@ -45,16 +45,21 @@ button {
 }
 
 /* Targets */
 
 .targets {
   margin-bottom: 35px;
 }
 
+.target-list {
+  margin: 0;
+  padding: 0;
+}
+
 .target-container {
   margin-top: 5px;
   min-height: 34px;
   display: flex;
   flex-direction: row;
   align-items: baseline;
 }
 
--- a/devtools/client/aboutdebugging/components/aboutdebugging.js
+++ b/devtools/client/aboutdebugging/components/aboutdebugging.js
@@ -18,21 +18,23 @@ loader.lazyGetter(this, "AddonsTab",
 loader.lazyGetter(this, "WorkersTab",
   () => createFactory(require("./workers-tab")));
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
 const tabs = [{
   id: "addons",
+  panelId: "tab-addons",
   name: Strings.GetStringFromName("addons"),
   icon: "chrome://devtools/skin/images/debugging-addons.svg",
   component: AddonsTab
 }, {
   id: "workers",
+  panelId: "tab-workers",
   name: Strings.GetStringFromName("workers"),
   icon: "chrome://devtools/skin/images/debugging-workers.svg",
   component: WorkersTab
 }];
 
 const defaultTabId = "addons";
 
 module.exports = createClass({
@@ -78,13 +80,13 @@ module.exports = createClass({
     let { selectedTabId } = this.state;
     let selectTab = this.selectTab;
 
     let selectedTab = tabs.find(t => t.id == selectedTabId);
 
     return dom.div({ className: "app" },
       TabMenu({ tabs, selectedTabId, selectTab }),
       dom.div({ className: "main-content" },
-        selectedTab.component({ client })
+        selectedTab.component({ client, id: selectedTab.panelId })
       )
     );
   }
 });
--- a/devtools/client/aboutdebugging/components/addon-target.js
+++ b/devtools/client/aboutdebugging/components/addon-target.js
@@ -36,17 +36,17 @@ module.exports = createClass({
       throw new Error(
         "Error reloading addon " + target.addonID + ": " + error);
     });
   },
 
   render() {
     let { target, debugDisabled } = this.props;
 
-    return dom.div({ className: "target-container" },
+    return dom.li({ className: "target-container" },
       dom.img({
         className: "target-icon",
         role: "presentation",
         src: target.icon
       }),
       dom.div({ className: "target" },
         dom.div({ className: "target-name" }, target.name)
       ),
--- a/devtools/client/aboutdebugging/components/addons-tab.js
+++ b/devtools/client/aboutdebugging/components/addons-tab.js
@@ -101,23 +101,23 @@ module.exports = createClass({
   /**
    * Mandatory callback as AddonManager listener.
    */
   onDisabled() {
     this.updateAddonsList();
   },
 
   render() {
-    let { client } = this.props;
+    let { client, id } = this.props;
     let { debugDisabled, extensions: targets } = this.state;
     let name = Strings.GetStringFromName("extensions");
     let targetClass = AddonTarget;
 
     return dom.div({
-      id: "tab-addons",
+      id: id,
       className: "tab",
       role: "tabpanel",
       "aria-labelledby": "tab-addons-header-name"
     },
     TabHeader({
       id: "tab-addons-header-name",
       name: Strings.GetStringFromName("addons")
     }),
--- a/devtools/client/aboutdebugging/components/tab-menu-entry.js
+++ b/devtools/client/aboutdebugging/components/tab-menu-entry.js
@@ -9,23 +9,32 @@ const { createClass, DOM: dom } =
 
 module.exports = createClass({
   displayName: "TabMenuEntry",
 
   onClick() {
     this.props.selectTab(this.props.tabId);
   },
 
+  onKeyUp(event) {
+    if ([" ", "Enter"].includes(event.key)) {
+      this.props.selectTab(this.props.tabId);
+    }
+  },
+
   render() {
-    let { icon, name, selected } = this.props;
+    let { panelId, icon, name, selected } = this.props;
 
     // Here .category, .category-icon, .category-name classnames are used to
     // apply common styles defined.
     let className = "category" + (selected ? " selected" : "");
     return dom.div({
       "aria-selected": selected,
+      "aria-controls": panelId,
       className,
       onClick: this.onClick,
+      onKeyUp: this.onKeyUp,
+      tabIndex: "0",
       role: "tab" },
     dom.img({ className: "category-icon", src: icon, role: "presentation" }),
     dom.div({ className: "category-name" }, name));
   }
 });
--- a/devtools/client/aboutdebugging/components/tab-menu.js
+++ b/devtools/client/aboutdebugging/components/tab-menu.js
@@ -8,17 +8,19 @@ const { createClass, createFactory, DOM:
   require("devtools/client/shared/vendor/react");
 const TabMenuEntry = createFactory(require("./tab-menu-entry"));
 
 module.exports = createClass({
   displayName: "TabMenu",
 
   render() {
     let { tabs, selectedTabId, selectTab } = this.props;
-    let tabLinks = tabs.map(({ id, name, icon }) => {
+    let tabLinks = tabs.map(({ panelId, id, name, icon }) => {
       let selected = id == selectedTabId;
-      return TabMenuEntry({ tabId: id, name, icon, selected, selectTab });
+      return TabMenuEntry({
+        tabId: id, panelId, name, icon, selected, selectTab
+      });
     });
 
     // "categories" id used for styling purposes
-    return dom.div({ id: "categories" }, tabLinks);
+    return dom.div({ id: "categories", role: "tablist" }, tabLinks);
   },
 });
--- a/devtools/client/aboutdebugging/components/target-list.js
+++ b/devtools/client/aboutdebugging/components/target-list.js
@@ -20,15 +20,15 @@ module.exports = createClass({
 
   render() {
     let { client, debugDisabled, targetClass } = this.props;
     let targets = this.props.targets.sort(LocaleCompare).map(target => {
       return targetClass({ client, target, debugDisabled });
     });
 
     return dom.div({ id: this.props.id, className: "targets" },
-      dom.h4(null, this.props.name),
+      dom.h2(null, this.props.name),
       targets.length > 0 ?
-        targets :
+        dom.ul({ className: "target-list" }, targets) :
         dom.p(null, Strings.GetStringFromName("nothing"))
     );
   },
 });
--- a/devtools/client/aboutdebugging/components/worker-target.js
+++ b/devtools/client/aboutdebugging/components/worker-target.js
@@ -20,17 +20,17 @@ module.exports = createClass({
   debug() {
     let { client, target } = this.props;
     debugWorker(client, target.workerActor);
   },
 
   render() {
     let { target, debugDisabled } = this.props;
 
-    return dom.div({ className: "target-container" },
+    return dom.li({ className: "target-container" },
       dom.img({
         className: "target-icon",
         role: "presentation",
         src: target.icon
       }),
       dom.div({ className: "target" },
         dom.div({ className: "target-name" }, target.name)
       ),
--- a/devtools/client/aboutdebugging/components/workers-tab.js
+++ b/devtools/client/aboutdebugging/components/workers-tab.js
@@ -95,21 +95,21 @@ module.exports = createClass({
       // find the scriptSpec.
       workers.service = workers.service.filter(reg => !!reg.url);
 
       this.setState({ workers });
     });
   },
 
   render() {
-    let { client } = this.props;
+    let { client, id } = this.props;
     let { workers } = this.state;
 
     return dom.div({
-      id: "tab-workers",
+      id: id,
       className: "tab",
       role: "tabpanel",
       "aria-labelledby": "tab-workers-header-name"
     },
     TabHeader({
       id: "tab-workers-header-name",
       name: Strings.GetStringFromName("workers")
     }),
--- a/devtools/client/aboutdebugging/test/.eslintrc
+++ b/devtools/client/aboutdebugging/test/.eslintrc
@@ -1,4 +1,22 @@
 {
   // Extend from the shared list of defined globals for mochitests.
-  "extends": "../../../.eslintrc.mochitests"
+  "extends": "../../../.eslintrc.mochitests",
+  // All globals made available in aboutdebugging head.js file.
+  "globals": {
+    "AddonManager": true,
+    "addTab": true,
+    "assertHasTarget": true,
+    "CHROME_ROOT": true,
+    "closeAboutDebugging": true,
+    "getServiceWorkerList": true,
+    "getSupportsFile": true,
+    "installAddon": true,
+    "openAboutDebugging": true,
+    "removeTab": true,
+    "uninstallAddon": true,
+    "unregisterServiceWorker": true,
+    "waitForInitialAddonList": true,
+    "waitForMutation": true,
+    "waitForServiceWorkerRegistered": true
+  }
 }
--- a/devtools/client/aboutdebugging/test/browser_service_workers.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers.js
@@ -21,17 +21,17 @@ add_task(function* () {
     ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   let swTab = yield addTab(TAB_URL);
 
-  let serviceWorkersElement = document.getElementById("service-workers");
+  let serviceWorkersElement = getServiceWorkerList(document);
 
   yield waitForMutation(serviceWorkersElement, { childList: true });
 
   // Check that the service worker appears in the UI
   let names = [...document.querySelectorAll("#service-workers .target-name")];
   names = names.map(element => element.textContent);
   ok(names.includes(SERVICE_WORKER),
     "The service worker url appears in the list: " + names);
--- a/devtools/client/aboutdebugging/test/browser_service_workers_push.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_push.js
@@ -25,17 +25,17 @@ add_task(function* () {
       ["dom.serviceWorkers.testing.enabled", true],
     ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   // Listen for mutations in the service-workers list.
-  let serviceWorkersElement = document.getElementById("service-workers");
+  let serviceWorkersElement = getServiceWorkerList(document);
   let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
 
   // Open a tab that registers a push service worker.
   let swTab = yield addTab(TAB_URL);
 
   info("Make the test page notify us when the service worker sends a message.");
   let frameScript = function() {
     let win = content.wrappedJSObject;
--- a/devtools/client/aboutdebugging/test/browser_service_workers_start.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_start.js
@@ -27,17 +27,17 @@ add_task(function* () {
       ["dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT],
     ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   // Listen for mutations in the service-workers list.
-  let serviceWorkersElement = document.getElementById("service-workers");
+  let serviceWorkersElement = getServiceWorkerList(document);
   let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
 
   // Open a tab that registers an empty service worker.
   let swTab = yield addTab(TAB_URL);
 
   // Wait for the service-workers list to update.
   yield onMutation;
 
--- a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
@@ -24,17 +24,17 @@ add_task(function* () {
     ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   let swTab = yield addTab(TAB_URL);
 
-  let serviceWorkersElement = document.getElementById("service-workers");
+  let serviceWorkersElement = getServiceWorkerList(document);
   yield waitForMutation(serviceWorkersElement, { childList: true });
 
   assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
 
   // Ensure that the registration resolved before trying to connect to the sw
   yield waitForServiceWorkerRegistered(swTab);
   ok(true, "Service worker registration resolved");
 
--- a/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
@@ -1,13 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* eslint-disable mozilla/no-cpows-in-tests */
-/* global sendAsyncMessage */
 
 "use strict";
 
 // Test that clicking on the unregister link in the Service Worker details works
 // as intended in about:debugging.
 // It should unregister the service worker, which should trigger an update of
 // the displayed list of service workers.
 
@@ -27,17 +26,17 @@ add_task(function* () {
       ["dom.serviceWorkers.testing.enabled", true],
     ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   // Listen for mutations in the service-workers list.
-  let serviceWorkersElement = document.getElementById("service-workers");
+  let serviceWorkersElement = getServiceWorkerList(document);
   let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
 
   // Open a tab that registers an empty service worker.
   let swTab = yield addTab(TAB_URL);
 
   // Wait for the service workers-list to update.
   yield onMutation;
 
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* eslint-env browser */
 /* eslint-disable mozilla/no-cpows-in-tests */
 /* exported openAboutDebugging, closeAboutDebugging, installAddon,
-   uninstallAddon, waitForMutation, assertHasTarget,
+   uninstallAddon, waitForMutation, assertHasTarget, getServiceWorkerList,
    waitForInitialAddonList, waitForServiceWorkerRegistered,
    unregisterServiceWorker */
 /* global sendAsyncMessage */
 
 "use strict";
 
 var { utils: Cu, classes: Cc, interfaces: Ci } = Components;
 
@@ -88,24 +88,46 @@ function removeTab(tab, win) {
 function getSupportsFile(path) {
   let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
     .getService(Ci.nsIChromeRegistry);
   let uri = Services.io.newURI(CHROME_ROOT + path, null, null);
   let fileurl = cr.convertChromeURL(uri);
   return fileurl.QueryInterface(Ci.nsIFileURL);
 }
 
+/**
+ * Depending on whether there are addons installed, return either a target list
+ * element or its container.
+ * @param  {DOMDocument}  document   #addons section container document
+ * @return {DOMNode}                 target list or container element
+ */
+function getAddonList(document) {
+  return document.querySelector("#addons .target-list") ||
+    document.querySelector("#addons .targets");
+}
+
+/**
+ * Depending on whether there are service workers installed, return either a
+ * target list element or its container.
+ * @param  {DOMDocument}  document   #service-workers section container document
+ * @return {DOMNode}                 target list or container element
+ */
+function getServiceWorkerList(document) {
+  return document.querySelector("#service-workers .target-list") ||
+    document.querySelector("#service-workers.targets");
+}
+
 function* installAddon(document, path, name, evt) {
   // Mock the file picker to select a test addon
   let MockFilePicker = SpecialPowers.MockFilePicker;
   MockFilePicker.init(null);
   let file = getSupportsFile(path);
   MockFilePicker.returnFiles = [file.file];
 
-  let addonList = document.querySelector("#addons .targets");
+  let addonList = getAddonList(document);
   let addonListMutation = waitForMutation(addonList, { childList: true });
 
   // Wait for a message sent by the addon's bootstrap.js file
   let onAddonInstalled = new Promise(done => {
     Services.obs.addObserver(function listener() {
       Services.obs.removeObserver(listener, evt);
 
       done();
@@ -121,17 +143,17 @@ function* installAddon(document, path, n
   yield addonListMutation;
   let names = [...addonList.querySelectorAll(".target-name")];
   names = names.map(element => element.textContent);
   ok(names.includes(name),
     "The addon name appears in the list of addons: " + names);
 }
 
 function* uninstallAddon(document, addonId, addonName) {
-  let addonList = document.querySelector("#addons .targets");
+  let addonList = getAddonList(document);
   let addonListMutation = waitForMutation(addonList, { childList: true });
 
   // Now uninstall this addon
   yield new Promise(done => {
     AddonManager.getAddonByID(addonId, addon => {
       let listener = {
         onUninstalled: function(uninstalledAddon) {
           if (uninstalledAddon != addon) {
@@ -158,17 +180,17 @@ function* uninstallAddon(document, addon
 
 /**
  * Returns a promise that will resolve when the add-on list has been updated.
  *
  * @param {Node} document
  * @return {Promise}
  */
 function waitForInitialAddonList(document) {
-  const addonListContainer = document.querySelector("#addons .targets");
+  const addonListContainer = getAddonList(document);
   let addonCount = addonListContainer.querySelectorAll(".target");
   addonCount = addonCount ? [...addonCount].length : -1;
   info("Waiting for add-ons to load. Current add-on count: " + addonCount);
 
   // This relies on the network speed of the actor responding to the
   // listAddons() request and also the speed of openAboutDebugging().
   let result;
   if (addonCount > 0) {