Bug 1209699 - Add a 'Push' button for service workers in about:debugging. r=jdescottes

This commit is contained in:
Jan Keromnes 2016-02-29 03:20:00 +01:00
parent 634d246572
commit f38239f17f
11 changed files with 274 additions and 56 deletions

View File

@ -51,6 +51,7 @@ button {
.target {
margin-top: 5px;
min-height: 34px;
display: flex;
flex-direction: row;
align-items: center;

View File

@ -38,6 +38,13 @@ module.exports = createClass({
dom.div({ className: "target-details" },
dom.div({ className: "target-name" }, target.name)
),
(isRunning && isServiceWorker ?
dom.button({
className: "push-button",
onClick: this.push
}, Strings.GetStringFromName("push")) :
null
),
(isRunning ?
dom.button({
className: "debug-button",
@ -72,6 +79,16 @@ module.exports = createClass({
}
},
push() {
let { client, target } = this.props;
if (target.workerActor) {
client.request({
to: target.workerActor,
type: "push"
});
}
},
openWorkerToolbox(workerActor) {
let { client } = this.props;
client.attachWorker(workerActor, (response, workerClient) => {

View File

@ -7,9 +7,12 @@ support-files =
addons/unpacked/install.rdf
service-workers/empty-sw.html
service-workers/empty-sw.js
service-workers/push-sw.html
service-workers/push-sw.js
[browser_addons_debugging_initial_state.js]
[browser_addons_install.js]
[browser_addons_toggle_debug.js]
[browser_service_workers.js]
[browser_service_workers_push.js]
[browser_service_workers_timeout.js]

View File

@ -0,0 +1,98 @@
/* 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 Push button next to a Service Worker works as
// intended in about:debugging.
// It should trigger a "push" notification in the worker.
// Service workers can't be loaded from chrome://, but http:// is ok with
// dom.serviceWorkers.testing.enabled turned on.
const HTTP_ROOT = CHROME_ROOT.replace(
"chrome://mochitests/content/", "http://mochi.test:8888/");
const SERVICE_WORKER = HTTP_ROOT + "service-workers/push-sw.js";
const TAB_URL = HTTP_ROOT + "service-workers/push-sw.html";
add_task(function* () {
info("Turn on workers via mochitest http.");
yield new Promise(done => {
let options = { "set": [
// Accept workers from mochitest's http.
["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 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;
win.navigator.serviceWorker.addEventListener("message", function(event) {
sendAsyncMessage(event.data);
}, false);
};
let mm = swTab.linkedBrowser.messageManager;
mm.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
// Expect the service worker to claim the test window when activating.
let onClaimed = new Promise(done => {
mm.addMessageListener("sw-claimed", function listener() {
mm.removeMessageListener("sw-claimed", listener);
done();
});
});
// 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);
info("Ensure that the registration resolved before trying to interact with " +
"the service worker.");
yield waitForServiceWorkerRegistered(swTab);
ok(true, "Service worker registration resolved");
// 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 " +
"a push notification");
let onPushNotification = new Promise(done => {
mm.addMessageListener("sw-pushed", function listener() {
mm.removeMessageListener("sw-pushed", listener);
done();
});
});
pushBtn.click();
yield onPushNotification;
ok(true, "Service worker received a push notification");
// Finally, unregister the service worker itself.
yield unregisterServiceWorker(swTab);
ok(true, "Service worker registration unregistered");
yield removeTab(swTab);
yield closeAboutDebugging(tab);
});

View File

@ -1,9 +1,6 @@
/* 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";
// Service workers can't be loaded from chrome://,
@ -15,13 +12,6 @@ const TAB_URL = HTTP_ROOT + "service-workers/empty-sw.html";
const SW_TIMEOUT = 1000;
function assertHasWorker(expected, document, type, name) {
let names = [...document.querySelectorAll("#" + type + " .target-name")];
names = names.map(element => element.textContent);
is(names.includes(name), expected,
"The " + type + " url appears in the list: " + names);
}
add_task(function* () {
yield new Promise(done => {
let options = {"set": [
@ -42,25 +32,10 @@ add_task(function* () {
let serviceWorkersElement = document.getElementById("service-workers");
yield waitForMutation(serviceWorkersElement, { childList: true });
assertHasWorker(true, document, "service-workers", SERVICE_WORKER);
assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
// Ensure that the registration resolved before trying to connect to the sw
let frameScript = function() {
// Retrieve the `sw` promise created in the html page
let { sw } = content.wrappedJSObject;
sw.then(function() {
sendAsyncMessage("sw-registered");
});
};
let mm = swTab.linkedBrowser.messageManager;
mm.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
yield new Promise(done => {
mm.addMessageListener("sw-registered", function listener() {
mm.removeMessageListener("sw-registered", listener);
done();
});
});
yield waitForServiceWorkerRegistered(swTab);
ok(true, "Service worker registration resolved");
// Retrieve the DEBUG button for the worker
@ -88,7 +63,7 @@ add_task(function* () {
setTimeout(done, SW_TIMEOUT * 2);
});
assertHasWorker(true, document, "service-workers", SERVICE_WORKER);
assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
ok(targetElement.querySelector(".debug-button"),
"The debug button is still there");
@ -102,35 +77,14 @@ add_task(function* () {
ok(!targetElement.querySelector(".debug-button"),
"The debug button was removed when the worker was killed");
// Finally, unregister the service worker itself
// Use message manager to work with e10s
frameScript = function() {
// Retrieve the `sw` promise created in the html page
let { sw } = content.wrappedJSObject;
sw.then(function(registration) {
registration.unregister().then(function() {
sendAsyncMessage("sw-unregistered");
},
function(e) {
dump("SW not unregistered; " + e + "\n");
});
});
};
mm = swTab.linkedBrowser.messageManager;
mm.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
yield new Promise(done => {
mm.addMessageListener("sw-unregistered", function listener() {
mm.removeMessageListener("sw-unregistered", listener);
done();
});
});
// Finally, unregister the service worker itself.
yield unregisterServiceWorker(swTab);
ok(true, "Service worker registration unregistered");
// Now ensure that the worker registration is correctly removed.
// The list should update once the registration is destroyed.
yield waitForMutation(serviceWorkersElement, { childList: true });
assertHasWorker(false, document, "service-workers", SERVICE_WORKER);
assertHasTarget(false, document, "service-workers", SERVICE_WORKER);
yield removeTab(swTab);
yield closeAboutDebugging(tab);

View File

@ -2,15 +2,18 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-env browser */
/* eslint-disable mozilla/no-cpows-in-tests */
/* exported openAboutDebugging, closeAboutDebugging, installAddon,
uninstallAddon, waitForMutation */
uninstallAddon, waitForMutation, assertHasTarget,
waitForServiceWorkerRegistered, unregisterServiceWorker */
/* global sendAsyncMessage */
"use strict";
var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
var { utils: Cu, classes: Cc, interfaces: Ci } = Components;
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
const Services = require("Services");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
DevToolsUtils.testing = true;
@ -146,3 +149,73 @@ function waitForMutation(target, mutationOptions) {
observer.observe(target, mutationOptions);
});
}
/**
* Checks if an about:debugging TargetList element contains a Target element
* corresponding to the specified name.
* @param {Boolean} expected
* @param {Document} document
* @param {String} type
* @param {String} name
*/
function assertHasTarget(expected, document, type, name) {
let names = [...document.querySelectorAll("#" + type + " .target-name")];
names = names.map(element => element.textContent);
is(names.includes(name), expected,
"The " + type + " url appears in the list: " + names);
}
/**
* Returns a promise that will resolve after the service worker in the page
* has successfully registered itself.
* @param {Tab} tab
*/
function waitForServiceWorkerRegistered(tab) {
// Make the test page notify us when the service worker is registered.
let frameScript = function() {
// Retrieve the `sw` promise created in the html page.
let { sw } = content.wrappedJSObject;
sw.then(function(registration) {
sendAsyncMessage("sw-registered");
});
};
let mm = tab.linkedBrowser.messageManager;
mm.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
return new Promise(done => {
mm.addMessageListener("sw-registered", function listener() {
mm.removeMessageListener("sw-registered", listener);
done();
});
});
}
/**
* Asks the service worker within the test page to unregister, and returns a
* promise that will resolve when it has successfully unregistered itself.
* @param {Tab} tab
*/
function unregisterServiceWorker(tab) {
// Use message manager to work with e10s.
let frameScript = function() {
// Retrieve the `sw` promise created in the html page.
let { sw } = content.wrappedJSObject;
sw.then(function(registration) {
registration.unregister().then(function() {
sendAsyncMessage("sw-unregistered");
},
function(e) {
dump("SW not unregistered; " + e + "\n");
});
});
};
let mm = tab.linkedBrowser.messageManager;
mm.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
return new Promise(done => {
mm.addMessageListener("sw-unregistered", function listener() {
mm.removeMessageListener("sw-unregistered", listener);
done();
});
});
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Service worker push test</title>
</head>
<body>
<script type="text/javascript">
"use strict";
var sw = navigator.serviceWorker.register("push-sw.js");
sw.then(
function(registration) {
dump("SW registered\n");
},
function(error) {
dump("SW not registered: " + error + "\n");
}
);
</script>
</body>
</html>

View File

@ -0,0 +1,33 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-env worker */
/* global clients */
"use strict";
// Send a message to all controlled windows.
function postMessage(message) {
return clients.matchAll().then(function(clientlist) {
clientlist.forEach(function(client) {
client.postMessage(message);
});
});
}
// Don't wait for the next page load to become the active service worker.
self.addEventListener("install", function(event) {
event.waitUntil(self.skipWaiting());
});
// Claim control over the currently open test page when activating.
self.addEventListener("activate", function(event) {
event.waitUntil(self.clients.claim().then(function() {
return postMessage("sw-claimed");
}));
});
// Forward all "push" events to the controlled window.
self.addEventListener("push", function(event) {
event.waitUntil(postMessage("sw-pushed"));
});

View File

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
debug = Debug
push = Push
addons = Add-ons
addonDebugging.label = Enable add-on debugging

View File

@ -5,6 +5,8 @@ var { DebuggerServer } = require("devtools/server/main");
const protocol = require("devtools/server/protocol");
const { Arg, method, RetVal } = protocol;
loader.lazyRequireGetter(this, "ChromeUtils");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(
@ -140,6 +142,20 @@ let WorkerActor = protocol.ActorClass({
response: RetVal("json")
}),
push: method(function () {
if (this._dbg.type !== Ci.nsIWorkerDebugger.TYPE_SERVICE) {
return { error: "wrongType" };
}
let registration = this._getServiceWorkerRegistrationInfo();
let originAttributes = ChromeUtils.originAttributesToSuffix(
this._dbg.principal.originAttributes);
swm.sendPushEvent(originAttributes, registration.scope);
return { type: "pushed" };
}, {
request: {},
response: RetVal("json")
}),
onClose: function () {
if (this._attached) {
this._detach();

View File

@ -32,6 +32,7 @@ var loaderModules = {
"Services": Object.create(Services),
"toolkit/loader": Loader,
PromiseDebugging,
ChromeUtils,
ThreadSafeChromeUtils,
HeapSnapshot,
};