--- a/devtools/.eslintrc.mochitests
+++ b/devtools/.eslintrc.mochitests
@@ -1,15 +1,25 @@
// Parent config file for all devtools browser mochitest files.
{
"extends": [
"../testing/mochitest/browser.eslintrc"
],
// All globals made available in the test environment.
"globals": {
+ "assertHasTarget": true,
+ "closeAboutDebugging": true,
"DevToolsUtils": true,
"gDevTools": true,
+ "getServiceWorkerList": true,
+ "installAddon": true,
"once": true,
+ "openAboutDebugging": true,
"synthesizeKeyFromKeyTag": true,
"TargetFactory": true,
+ "uninstallAddon": true,
+ "unregisterServiceWorker": true,
+ "waitForInitialAddonList": true,
+ "waitForMutation": true,
+ "waitForServiceWorkerRegistered": true,
"waitForTick": true,
}
}
--- 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/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
@@ -27,17 +27,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) {