author | Jan Keromnes <janx@linux.com> |
Mon, 29 Feb 2016 03:20:00 +0100 | |
changeset 286219 | 7f9ea43e7bf11c2b062f5e401fa84016c5e16e47 |
parent 286218 | 5e6d66db9b05790dea3c4654ec7309bfac424b73 |
child 286220 | 93e15509f449e9edd5d4b7fef410b796e438abd7 |
push id | 30042 |
push user | kwierso@gmail.com |
push date | Tue, 01 Mar 2016 22:20:44 +0000 |
treeherder | mozilla-central@d6788a70c97b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jdescottes |
bugs | 1209699 |
milestone | 47.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
|
--- a/devtools/client/aboutdebugging/aboutdebugging.css +++ b/devtools/client/aboutdebugging/aboutdebugging.css @@ -46,16 +46,17 @@ button { /* Targets */ .targets { margin-bottom: 25px; } .target { margin-top: 5px; + min-height: 34px; display: flex; flex-direction: row; align-items: center; } .target-icon { height: 24px; margin-right: 5px;
--- a/devtools/client/aboutdebugging/components/target.js +++ b/devtools/client/aboutdebugging/components/target.js @@ -33,16 +33,23 @@ module.exports = createClass({ return dom.div({ className: "target" }, dom.img({ className: "target-icon", role: "presentation", src: target.icon }), 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", onClick: this.debug, disabled: debugDisabled, }, Strings.GetStringFromName("debug")) : null ) @@ -67,16 +74,26 @@ module.exports = createClass({ this.openWorkerToolbox(target.workerActor); break; default: window.alert("Not implemented yet!"); break; } }, + 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) => { gDevTools.showToolbox(TargetFactory.forWorker(workerClient), "jsdebugger", Toolbox.HostType.WINDOW) .then(toolbox => { toolbox.once("destroy", () => workerClient.detach()); });
--- a/devtools/client/aboutdebugging/test/browser.ini +++ b/devtools/client/aboutdebugging/test/browser.ini @@ -2,14 +2,17 @@ tags = devtools subsuite = devtools support-files = head.js addons/unpacked/bootstrap.js 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]
new file mode 100644 --- /dev/null +++ b/devtools/client/aboutdebugging/test/browser_service_workers_push.js @@ -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); +});
--- a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js +++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js @@ -1,32 +1,22 @@ /* 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://, // 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/empty-sw.js"; 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": [ // Accept workers from mochitest's http ["dom.serviceWorkers.testing.enabled", true], // Reduce the timeout to expose issues when service worker // freezing is broken ["dom.serviceWorkers.idle_timeout", SW_TIMEOUT], @@ -37,35 +27,20 @@ add_task(function* () { let { tab, document } = yield openAboutDebugging("workers"); let swTab = yield addTab(TAB_URL); 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 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 debugBtn = targetElement.querySelector(".debug-button"); @@ -83,55 +58,34 @@ add_task(function* () { // Wait for more than the regular timeout, // so that if the worker freezing doesn't work, // it will be destroyed and removed from the list yield new Promise(done => { 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"); yield toolbox.destroy(); toolbox = null; // Now ensure that the worker is correctly destroyed // 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 - // 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); });
--- a/devtools/client/aboutdebugging/test/head.js +++ b/devtools/client/aboutdebugging/test/head.js @@ -1,21 +1,24 @@ /* 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 */ + 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; const CHROME_ROOT = gTestPath.substr(0, gTestPath.lastIndexOf("/") + 1); registerCleanupFunction(() => { DevToolsUtils.testing = false; @@ -141,8 +144,78 @@ function waitForMutation(target, mutatio return new Promise(resolve => { let observer = new MutationObserver(() => { observer.disconnect(); resolve(); }); 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(); + }); + }); +}
new file mode 100644 --- /dev/null +++ b/devtools/client/aboutdebugging/test/service-workers/push-sw.html @@ -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>
new file mode 100644 --- /dev/null +++ b/devtools/client/aboutdebugging/test/service-workers/push-sw.js @@ -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")); +});
--- a/devtools/client/locales/en-US/aboutdebugging.properties +++ b/devtools/client/locales/en-US/aboutdebugging.properties @@ -1,13 +1,14 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # 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 addonDebugging.tooltip = Turning this on will allow you to debug add-ons and various other parts of the browser chrome addonDebugging.moreInfo = more info loadTemporaryAddon = Load Temporary Add-on extensions = Extensions selectAddonFromFile = Select Add-on Directory or XPI File
--- a/devtools/server/actors/worker.js +++ b/devtools/server/actors/worker.js @@ -1,15 +1,17 @@ "use strict"; var { Ci, Cu } = require("chrome"); 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( this, "wdm", "@mozilla.org/dom/workers/workerdebuggermanager;1", "nsIWorkerDebuggerManager" ); @@ -135,16 +137,30 @@ let WorkerActor = protocol.ActorClass({ }); }, { request: { options: Arg(0, "json"), }, 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(); } this.conn.sendActorEvent(this.actorID, "close"); },
--- a/devtools/shared/Loader.jsm +++ b/devtools/shared/Loader.jsm @@ -27,16 +27,17 @@ this.EXPORTED_SYMBOLS = ["DevToolsLoader /** * Providers are different strategies for loading the devtools. */ var loaderModules = { "Services": Object.create(Services), "toolkit/loader": Loader, PromiseDebugging, + ChromeUtils, ThreadSafeChromeUtils, HeapSnapshot, }; XPCOMUtils.defineLazyGetter(loaderModules, "Debugger", () => { // addDebuggerToGlobal only allows adding the Debugger object to a global. The // this object is not guaranteed to be a global (in particular on B2G, due to // compartment sharing), so add the Debugger object to a sandbox instead. let sandbox = Cu.Sandbox(CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')());