--- a/devtools/client/aboutdebugging/components/workers/panel.js
+++ b/devtools/client/aboutdebugging/components/workers/panel.js
@@ -37,59 +37,84 @@ module.exports = createClass({
};
},
componentDidMount() {
let client = this.props.client;
client.addListener("workerListChanged", this.update);
client.addListener("serviceWorkerRegistrationListChanged", this.update);
client.addListener("processListChanged", this.update);
+ client.addListener("registration-changed", this.update);
+
this.update();
},
componentWillUnmount() {
let client = this.props.client;
client.removeListener("processListChanged", this.update);
client.removeListener("serviceWorkerRegistrationListChanged", this.update);
client.removeListener("workerListChanged", this.update);
+ client.removeListener("registration-changed", this.update);
},
update() {
let workers = this.getInitialState().workers;
getWorkerForms(this.props.client).then(forms => {
forms.registrations.forEach(form => {
+ // - In e10s: only active registrations are available, but if the worker is in
+ // activating state it won't be available as the activeWorker. Registrations with
+ // no worker are actually registrations with a hidden activating worker.
+ // - In non-e10s: registrations always have at least one worker, if it is an
+ // active worker, the registration is active.
+ let hasWorker = form.activeWorker || form.waitingWorker || form.installingWorker;
+ let isE10s = Services.appinfo.browserTabsRemoteAutostart;
+ let active = form.activeWorker || (isE10s && !hasWorker);
+
workers.service.push({
icon: WorkerIcon,
name: form.url,
url: form.url,
scope: form.scope,
- registrationActor: form.actor
+ registrationActor: form.actor,
+ active
});
});
forms.workers.forEach(form => {
let worker = {
icon: WorkerIcon,
name: form.url,
url: form.url,
workerActor: form.actor
};
switch (form.type) {
case Ci.nsIWorkerDebugger.TYPE_SERVICE:
- for (let registration of workers.service) {
- if (registration.scope === form.scope) {
- // XXX: Race, sometimes a ServiceWorkerRegistrationInfo doesn't
- // have a scriptSpec, but its associated WorkerDebugger does.
- if (!registration.url) {
- registration.name = registration.url = form.url;
- }
- registration.workerActor = form.actor;
- break;
+ let registration = this.getRegistrationForWorker(form, workers.service);
+ if (registration) {
+ // XXX: Race, sometimes a ServiceWorkerRegistrationInfo doesn't
+ // have a scriptSpec, but its associated WorkerDebugger does.
+ if (!registration.url) {
+ registration.name = registration.url = form.url;
}
+ registration.workerActor = form.actor;
+ } else {
+ // If a service worker registration could not be found, this means we are in
+ // e10s, and registrations are not forwarded to other processes until they
+ // reach the activated state. Add a temporary registration to display it in
+ // aboutdebugging.
+ workers.service.push({
+ icon: WorkerIcon,
+ name: form.url,
+ url: form.url,
+ scope: form.scope,
+ registrationActor: null,
+ workerActor: form.actor,
+ active: false
+ });
}
break;
case Ci.nsIWorkerDebugger.TYPE_SHARED:
workers.shared.push(worker);
break;
default:
workers.other.push(worker);
}
@@ -98,16 +123,25 @@ module.exports = createClass({
// XXX: Filter out the service worker registrations for which we couldn't
// find the scriptSpec.
workers.service = workers.service.filter(reg => !!reg.url);
this.setState({ workers });
});
},
+ getRegistrationForWorker(form, registrations) {
+ for (let registration of registrations) {
+ if (registration.scope === form.scope) {
+ return registration;
+ }
+ }
+ return null;
+ },
+
render() {
let { client, id } = this.props;
let { workers } = this.state;
let isWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(window);
let isPrivateBrowsingMode = PrivateBrowsingUtils.permanentPrivateBrowsing;
let isServiceWorkerDisabled = !Services.prefs
.getBoolPref("dom.serviceWorkers.enabled");
--- a/devtools/client/aboutdebugging/components/workers/service-worker-target.js
+++ b/devtools/client/aboutdebugging/components/workers/service-worker-target.js
@@ -20,53 +20,63 @@ module.exports = createClass({
getInitialState() {
return {
pushSubscription: null
};
},
componentDidMount() {
let { client } = this.props;
- client.addListener("push-subscription-modified",
- this.onPushSubscriptionModified);
+ client.addListener("push-subscription-modified", this.onPushSubscriptionModified);
this.updatePushSubscription();
},
+ componentDidUpdate(oldProps, oldState) {
+ let wasActive = oldProps.target.active;
+ if (!wasActive && this.isActive()) {
+ // While the service worker isn't active, any calls to `updatePushSubscription`
+ // won't succeed. If we just became active, make sure we didn't miss a push
+ // subscription change by updating it now.
+ this.updatePushSubscription();
+ }
+ },
+
componentWillUnmount() {
let { client } = this.props;
- client.removeListener("push-subscription-modified",
- this.onPushSubscriptionModified);
+ client.removeListener("push-subscription-modified", this.onPushSubscriptionModified);
},
debug() {
if (!this.isRunning()) {
// If the worker is not running, we can't debug it.
return;
}
let { client, target } = this.props;
debugWorker(client, target.workerActor);
},
push() {
- if (!this.isRunning()) {
+ if (!this.isActive() || !this.isRunning()) {
// If the worker is not running, we can't push to it.
+ // If the worker is not active, the registration might be unavailable and the
+ // push will not succeed.
return;
}
let { client, target } = this.props;
client.request({
to: target.workerActor,
type: "push"
});
},
start() {
- if (this.isRunning()) {
- // If the worker is already running, we can't start it.
+ if (!this.isActive() || this.isRunning()) {
+ // If the worker is not active or if it is already running, we can't start it.
return;
}
let { client, target } = this.props;
client.request({
to: target.registrationActor,
type: "start"
});
@@ -83,38 +93,93 @@ module.exports = createClass({
onPushSubscriptionModified(type, data) {
let { target } = this.props;
if (data.from === target.registrationActor) {
this.updatePushSubscription();
}
},
updatePushSubscription() {
+ if (!this.props.target.registrationActor) {
+ // A valid registrationActor is needed to retrieve the push subscription.
+ return;
+ }
+
let { client, target } = this.props;
client.request({
to: target.registrationActor,
type: "getPushSubscription"
}, ({ subscription }) => {
this.setState({ pushSubscription: subscription });
});
},
isRunning() {
// We know the target is running if it has a worker actor.
return !!this.props.target.workerActor;
},
+ isActive() {
+ return this.props.target.active;
+ },
+
getServiceWorkerStatus() {
- return this.isRunning() ? "running" : "stopped";
+ if (this.isActive() && this.isRunning()) {
+ return "running";
+ } else if (this.isActive()) {
+ return "stopped";
+ }
+ // We cannot get service worker registrations unless the registration is in
+ // ACTIVE state. Unable to know the actual state ("installing", "waiting"), we
+ // display a custom state "registering" for now. See Bug 1153292.
+ return "registering";
+ },
+
+ renderButtons() {
+ let pushButton = dom.button({
+ className: "push-button",
+ onClick: this.push
+ }, Strings.GetStringFromName("push"));
+
+ let debugButton = dom.button({
+ className: "debug-button",
+ onClick: this.debug,
+ disabled: this.props.debugDisabled
+ }, Strings.GetStringFromName("debug"));
+
+ let startButton = dom.button({
+ className: "start-button",
+ onClick: this.start,
+ }, Strings.GetStringFromName("start"));
+
+ if (this.isRunning()) {
+ if (this.isActive()) {
+ return [pushButton, debugButton];
+ }
+ // Only debug button is available if the service worker is not active.
+ return debugButton;
+ }
+ return startButton;
+ },
+
+ renderUnregisterLink() {
+ if (!this.isActive()) {
+ // If not active, there might be no registrationActor available.
+ return null;
+ }
+
+ return dom.a({
+ onClick: this.unregister,
+ className: "unregister-link"
+ }, Strings.GetStringFromName("unregister"));
},
render() {
- let { target, debugDisabled } = this.props;
+ let { target } = this.props;
let { pushSubscription } = this.state;
- let isRunning = this.isRunning();
let status = this.getServiceWorkerStatus();
return dom.div({ className: "target-container" },
dom.img({
className: "target-icon",
role: "presentation",
src: target.icon
}),
@@ -133,35 +198,16 @@ module.exports = createClass({
null
),
dom.li({ className: "target-detail" },
dom.strong(null, Strings.GetStringFromName("scope")),
dom.span({
className: "service-worker-scope",
title: target.scope
}, target.scope),
- dom.a({
- onClick: this.unregister,
- className: "unregister-link"
- }, Strings.GetStringFromName("unregister"))
+ this.renderUnregisterLink()
)
)
),
- (isRunning ?
- [
- dom.button({
- className: "push-button",
- onClick: this.push
- }, Strings.GetStringFromName("push")),
- dom.button({
- className: "debug-button",
- onClick: this.debug,
- disabled: debugDisabled
- }, Strings.GetStringFromName("debug"))
- ] :
- dom.button({
- className: "start-button",
- onClick: this.start
- }, Strings.GetStringFromName("start"))
- )
+ this.renderButtons()
);
}
});
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -4,16 +4,18 @@ subsuite = devtools
support-files =
head.js
addons/unpacked/bootstrap.js
addons/unpacked/install.rdf
addons/bad/manifest.json
addons/bug1273184.xpi
addons/test-devtools-webextension/*
addons/test-devtools-webextension-nobg/*
+ service-workers/delay-sw.html
+ service-workers/delay-sw.js
service-workers/empty-sw.html
service-workers/empty-sw.js
service-workers/push-sw.html
service-workers/push-sw.js
!/devtools/client/framework/test/shared-head.js
[browser_addons_debug_bootstrapped.js]
[browser_addons_debug_webextension.js]
@@ -29,12 +31,13 @@ tags = webextensions
[browser_addons_reload.js]
[browser_addons_toggle_debug.js]
[browser_page_not_found.js]
[browser_service_workers.js]
[browser_service_workers_not_compatible.js]
[browser_service_workers_push.js]
[browser_service_workers_push_service.js]
[browser_service_workers_start.js]
+[browser_service_workers_status.js]
[browser_service_workers_timeout.js]
skip-if = true # Bug 1232931
[browser_service_workers_unregister.js]
[browser_tabs.js]
--- a/devtools/client/aboutdebugging/test/browser_service_workers.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers.js
@@ -26,29 +26,23 @@ add_task(function* () {
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);
- // Finally, unregister the service worker itself
- let aboutDebuggingUpdate = waitForMutation(serviceWorkersElement,
- { childList: true });
-
try {
- yield unregisterServiceWorker(swTab);
+ yield unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
- yield aboutDebuggingUpdate;
-
// Check that the service worker disappeared from the UI
names = [...document.querySelectorAll("#service-workers .target-name")];
names = names.map(element => element.textContent);
ok(!names.includes(SERVICE_WORKER),
"The service worker url is no longer in the list: " + names);
yield removeTab(swTab);
yield closeAboutDebugging(tab);
--- a/devtools/client/aboutdebugging/test/browser_service_workers_push.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_push.js
@@ -57,21 +57,25 @@ add_task(function* () {
// Check that the service worker appears in the UI.
assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
info("Ensure that the registration resolved before trying to interact with " +
"the service worker.");
yield waitForServiceWorkerRegistered(swTab);
ok(true, "Service worker registration resolved");
+ yield waitForServiceWorkerActivation(SERVICE_WORKER, document);
+
// Retrieve the Push button for the worker.
let names = [...document.querySelectorAll("#service-workers .target-name")];
let name = names.filter(element => element.textContent === SERVICE_WORKER)[0];
ok(name, "Found the service worker in the list");
+
let targetElement = name.parentNode.parentNode;
+
let pushBtn = targetElement.querySelector(".push-button");
ok(pushBtn, "Found its push button");
info("Wait for the service worker to claim the test window before " +
"proceeding.");
yield onClaimed;
info("Click on the Push button and wait for the service worker to receive " +
@@ -83,17 +87,17 @@ add_task(function* () {
});
});
pushBtn.click();
yield onPushNotification;
ok(true, "Service worker received a push notification");
// Finally, unregister the service worker itself.
try {
- yield unregisterServiceWorker(swTab);
+ yield unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
yield removeTab(swTab);
yield closeAboutDebugging(tab);
});
--- a/devtools/client/aboutdebugging/test/browser_service_workers_push_service.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_push_service.js
@@ -67,45 +67,53 @@ add_task(function* () {
let swTab = yield addTab(TAB_URL);
// Wait for the service-workers list to update.
yield onMutation;
// Check that the service worker appears in the UI.
assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
+ yield waitForServiceWorkerActivation(SERVICE_WORKER, document);
+
// Wait for the service worker details to update.
let names = [...document.querySelectorAll("#service-workers .target-name")];
let name = names.filter(element => element.textContent === SERVICE_WORKER)[0];
ok(name, "Found the service worker in the list");
+
let targetContainer = name.parentNode.parentNode;
let targetDetailsElement = targetContainer.querySelector(".target-details");
- yield waitForMutation(targetDetailsElement, { childList: true });
// Retrieve the push subscription endpoint URL, and verify it looks good.
let pushURL = targetContainer.querySelector(".service-worker-push-url");
+ if (!pushURL) {
+ yield waitForMutation(targetDetailsElement, { childList: true });
+ pushURL = targetContainer.querySelector(".service-worker-push-url");
+ }
+
ok(pushURL, "Found the push service URL in the service worker details");
is(pushURL.textContent, FAKE_ENDPOINT, "The push service URL looks correct");
// Unsubscribe from the push service.
ContentTask.spawn(swTab.linkedBrowser, {}, function () {
let win = content.wrappedJSObject;
return win.sub.unsubscribe();
});
// Wait for the service worker details to update again.
yield waitForMutation(targetDetailsElement, { childList: true });
ok(!targetContainer.querySelector(".service-worker-push-url"),
"The push service URL should be removed");
// Finally, unregister the service worker itself.
- yield unregisterServiceWorker(swTab).then(() => {
+ try {
+ yield unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
- }).catch(function (e) {
- ok(false, "Service worker not unregistered; " + e);
- });
+ } catch (e) {
+ ok(false, "SW not unregistered; " + e);
+ }
info("Unmock the push service");
PushService.service = null;
yield removeTab(swTab);
yield closeAboutDebugging(tab);
});
--- a/devtools/client/aboutdebugging/test/browser_service_workers_start.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_start.js
@@ -42,16 +42,18 @@ add_task(function* () {
// Check that the service worker appears in the UI.
assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
info("Ensure that the registration resolved before trying to interact with " +
"the service worker.");
yield waitForServiceWorkerRegistered(swTab);
ok(true, "Service worker registration resolved");
+ yield waitForServiceWorkerActivation(SERVICE_WORKER, document);
+
// Retrieve the Target element corresponding to the service worker.
let names = [...document.querySelectorAll("#service-workers .target-name")];
let name = names.filter(element => element.textContent === SERVICE_WORKER)[0];
ok(name, "Found the service worker in the list");
let targetElement = name.parentNode.parentNode;
// The service worker may already be killed with the low 1s timeout
if (!targetElement.querySelector(".start-button")) {
@@ -77,17 +79,17 @@ add_task(function* () {
yield onStarted;
// Check that we have a Debug button but not a Start button again.
ok(targetElement.querySelector(".debug-button"), "Found its debug button");
ok(!targetElement.querySelector(".start-button"), "No start button");
// Finally, unregister the service worker itself.
try {
- yield unregisterServiceWorker(swTab);
+ yield unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
yield removeTab(swTab);
yield closeAboutDebugging(tab);
});
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_status.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Service workers can't be loaded from chrome://,
+// but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
+const SERVICE_WORKER = URL_ROOT + "service-workers/delay-sw.js";
+const TAB_URL = URL_ROOT + "service-workers/delay-sw.html";
+const SW_TIMEOUT = 2000;
+
+requestLongerTimeout(2);
+
+add_task(function* () {
+ yield SpecialPowers.pushPrefEnv({
+ "set": [
+ // Accept workers from mochitest's http.
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.serviceWorkers.idle_timeout", SW_TIMEOUT],
+ ["dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT],
+ ]
+ });
+
+ let { tab, document } = yield openAboutDebugging("workers");
+
+ // Listen for mutations in the service-workers list.
+ let serviceWorkersElement = getServiceWorkerList(document);
+ let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
+
+ let swTab = yield addTab(TAB_URL);
+
+ info("Make the test page notify us when the service worker sends a message.");
+
+ // Wait for the service-workers list to update.
+ yield onMutation;
+
+ // Check that the service worker appears in the UI
+ let names = [...document.querySelectorAll("#service-workers .target-name")];
+ let name = names.filter(element => element.textContent === SERVICE_WORKER)[0];
+ ok(name, "Found the service worker in the list");
+
+ let targetElement = name.parentNode.parentNode;
+ let status = targetElement.querySelector(".target-status");
+ is(status.textContent, "Registering", "Service worker is currently registering");
+
+ yield waitForMutation(serviceWorkersElement, { childList: true, subtree: true });
+ is(status.textContent, "Running", "Service worker is currently running");
+
+ yield waitForMutation(serviceWorkersElement, { attributes: true, subtree: true });
+ is(status.textContent, "Stopped", "Service worker is currently stopped");
+
+ try {
+ yield unregisterServiceWorker(swTab, serviceWorkersElement);
+ ok(true, "Service worker unregistered");
+ } catch (e) {
+ ok(false, "Service worker not unregistered; " + e);
+ }
+
+ // Check that the service worker disappeared from the UI
+ names = [...document.querySelectorAll("#service-workers .target-name")];
+ names = names.map(element => element.textContent);
+ ok(!names.includes(SERVICE_WORKER),
+ "The service worker url is no longer in the list: " + names);
+
+ yield removeTab(swTab);
+ yield closeAboutDebugging(tab);
+});
--- a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
@@ -72,22 +72,19 @@ add_task(function* () {
// after we destroy the toolbox.
// The DEBUG button should disappear once the worker is destroyed.
yield waitForMutation(targetElement, { childList: true });
ok(!targetElement.querySelector(".debug-button"),
"The debug button was removed when the worker was killed");
// Finally, unregister the service worker itself.
try {
- yield unregisterServiceWorker(swTab);
+ yield unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
- // Now ensure that the worker registration is correctly removed.
- // The list should update once the registration is destroyed.
- yield waitForMutation(serviceWorkersElement, { childList: true });
assertHasTarget(false, document, "service-workers", SERVICE_WORKER);
yield removeTab(swTab);
yield closeAboutDebugging(tab);
});
--- a/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_unregister.js
@@ -34,16 +34,18 @@ add_task(function* () {
let swTab = yield addTab(TAB_URL);
// Wait for the service workers-list to update.
yield onMutation;
// Check that the service worker appears in the UI.
assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
+ yield waitForServiceWorkerActivation(SERVICE_WORKER, document);
+
info("Ensure that the registration resolved before trying to interact with " +
"the service worker.");
yield waitForServiceWorkerRegistered(swTab);
ok(true, "Service worker registration resolved");
let targets = document.querySelectorAll("#service-workers .target");
is(targets.length, 1, "One service worker is now displayed.");
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -1,17 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-env browser */
/* exported openAboutDebugging, changeAboutDebuggingHash, closeAboutDebugging,
installAddon, uninstallAddon, waitForMutation, assertHasTarget,
getServiceWorkerList, getTabList, openPanel, waitForInitialAddonList,
waitForServiceWorkerRegistered, unregisterServiceWorker,
- waitForDelayedStartupFinished, setupTestAboutDebuggingWebExtension */
+ waitForDelayedStartupFinished, setupTestAboutDebuggingWebExtension,
+ waitForServiceWorkerActivation */
/* import-globals-from ../../framework/test/shared-head.js */
"use strict";
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
this);
@@ -254,27 +255,32 @@ function waitForServiceWorkerRegistered(
// Retrieve the `sw` promise created in the html page.
let { sw } = content.wrappedJSObject;
yield sw;
});
}
/**
* Asks the service worker within the test page to unregister, and returns a
- * promise that will resolve when it has successfully unregistered itself.
+ * promise that will resolve when it has successfully unregistered itself and the
+ * about:debugging UI has fully processed this update.
+ *
* @param {Tab} tab
+ * @param {Node} serviceWorkersElement
* @return {Promise} Resolves when the service worker is unregistered.
*/
-function unregisterServiceWorker(tab) {
- return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+function* unregisterServiceWorker(tab, serviceWorkersElement) {
+ let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
// Retrieve the `sw` promise created in the html page
let { sw } = content.wrappedJSObject;
let registration = yield sw;
yield registration.unregister();
});
+ return onMutation;
}
/**
* Waits for the creation of a new window, usually used with create private
* browsing window.
* Returns a promise that will resolve when the window is successfully created.
* @param {window} win
*/
@@ -321,8 +327,25 @@ function* setupTestAboutDebuggingWebExte
let nameEl = names.filter(element => element.textContent === name)[0];
ok(name, "Found the addon in the list");
let targetElement = nameEl.parentNode.parentNode;
let debugBtn = targetElement.querySelector(".debug-button");
ok(debugBtn, "Found its debug button");
return { tab, document, debugBtn };
}
+
+/**
+ * Wait for aboutdebugging to be notified about the activation of the service worker
+ * corresponding to the provided service worker url.
+ */
+function* waitForServiceWorkerActivation(swUrl, document) {
+ let serviceWorkersElement = getServiceWorkerList(document);
+ let names = serviceWorkersElement.querySelectorAll(".target-name");
+ let name = [...names].filter(element => element.textContent === swUrl)[0];
+
+ let targetElement = name.parentNode.parentNode;
+ let targetStatus = targetElement.querySelector(".target-status");
+ while (targetStatus.textContent === "Registering") {
+ // Wait for the status to leave the "registering" stage.
+ yield waitForMutation(serviceWorkersElement, { childList: true, subtree: true });
+ }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/service-workers/delay-sw.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Service worker test</title>
+</head>
+<body>
+<script type="text/javascript">
+"use strict";
+
+var sw = navigator.serviceWorker.register("delay-sw.js");
+sw.then(
+ function () {
+ dump("SW registered\n");
+ },
+ function (e) {
+ dump("SW not registered: " + e + "\n");
+ }
+);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/service-workers/delay-sw.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env worker */
+
+"use strict";
+
+function wait(ms) {
+ return new Promise(resolve => {
+ setTimeout(resolve, ms);
+ });
+}
+
+// Wait for one second to switch from installing to installed.
+self.addEventListener("install", function (event) {
+ event.waitUntil(wait(1000));
+});
--- a/devtools/client/locales/en-US/aboutdebugging.properties
+++ b/devtools/client/locales/en-US/aboutdebugging.properties
@@ -66,19 +66,30 @@ reloadDisabledTooltip = Only temporarily
# LOCALIZATION NOTE (workers):
# This string is displayed as a header of the about:debugging#workers page.
workers = Workers
serviceWorkers = Service Workers
sharedWorkers = Shared Workers
otherWorkers = Other Workers
+# LOCALIZATION NOTE (running):
+# This string is displayed as the state of a service worker in RUNNING state.
running = Running
+
+# LOCALIZATION NOTE (stopped):
+# This string is displayed as the state of a service worker in STOPPED state.
stopped = Stopped
+# LOCALIZATION NOTE (registering):
+# This string is displayed as the state of a service worker for which no service worker
+# registration could be found yet. Only active registrations are visible from
+# about:debugging, so such service workers are considered as registering.
+registering = Registering
+
# LOCALIZATION NOTE (tabs):
# This string is displayed as a header of the about:debugging#tabs page.
tabs = Tabs
# LOCALIZATION NOTE (pageNotFound):
# This string is displayed as the main message at any error/invalid page.
pageNotFound = Page not found
--- a/devtools/server/actors/worker.js
+++ b/devtools/server/actors/worker.js
@@ -351,34 +351,63 @@ protocol.ActorClassWithSpec(serviceWorke
* @param ServiceWorkerRegistrationInfo registration
* The registration's information.
*/
initialize(conn, registration) {
protocol.Actor.prototype.initialize.call(this, conn);
this._conn = conn;
this._registration = registration;
this._pushSubscriptionActor = null;
+ this._registration.addListener(this);
Services.obs.addObserver(this, PushService.subscriptionModifiedTopic, false);
},
+ get installingWorkerForm() {
+ return this._getWorkerForm(this._registration.installingWorker);
+ },
+
+ get activeWorkerForm() {
+ return this._getWorkerForm(this._registration.activeWorker);
+ },
+
+ get waitingWorkerForm() {
+ return this._getWorkerForm(this._registration.waitingWorker);
+ },
+
+ _getWorkerForm: function (worker) {
+ if (!worker) {
+ return null;
+ }
+
+ return { url: worker.scriptSpec, state: worker.state };
+ },
+
+ onChange: function () {
+ events.emit(this, "registration-changed");
+ },
+
form(detail) {
if (detail === "actorid") {
return this.actorID;
}
let registration = this._registration;
return {
actor: this.actorID,
scope: registration.scope,
- url: registration.scriptSpec
+ url: registration.scriptSpec,
+ installingWorker: this.installingWorkerForm,
+ activeWorker: this.activeWorkerForm,
+ waitingWorker: this.waitingWorkerForm,
};
},
destroy() {
protocol.Actor.prototype.destroy.call(this);
Services.obs.removeObserver(this, PushService.subscriptionModifiedTopic, false);
+ this._registration.removeListener(this);
this._registration = null;
if (this._pushSubscriptionActor) {
this._pushSubscriptionActor.destroy();
}
this._pushSubscriptionActor = null;
},
disconnect() {
--- a/devtools/shared/specs/worker.js
+++ b/devtools/shared/specs/worker.js
@@ -39,16 +39,19 @@ const pushSubscriptionSpec = generateAct
exports.pushSubscriptionSpec = pushSubscriptionSpec;
const serviceWorkerRegistrationSpec = generateActorSpec({
typeName: "serviceWorkerRegistration",
events: {
"push-subscription-modified": {
type: "push-subscription-modified"
+ },
+ "registration-changed": {
+ type: "registration-changed"
}
},
methods: {
start: {
request: {},
response: RetVal("json")
},