From aff2effa69be3557fbfdfa43ed1d358fdaeac93e Mon Sep 17 00:00:00 2001 From: Alexandre Poirot Date: Tue, 15 Dec 2015 03:10:53 -0800 Subject: [PATCH] Bug 1228382 - Keep service worker alive when attaching to them. r=janx,ejpbruel --- .../aboutdebugging/components/target.js | 5 +- .../client/aboutdebugging/test/browser.ini | 1 + .../test/browser_service_workers_timeout.js | 112 ++++++++++++++++++ devtools/server/actors/worker.js | 33 ++++++ 4 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 devtools/client/aboutdebugging/test/browser_service_workers_timeout.js diff --git a/devtools/client/aboutdebugging/components/target.js b/devtools/client/aboutdebugging/components/target.js index 75c563f3d9a4..2179477ac540 100644 --- a/devtools/client/aboutdebugging/components/target.js +++ b/devtools/client/aboutdebugging/components/target.js @@ -41,7 +41,10 @@ exports.TargetComponent = React.createClass({ let workerActor = this.props.target.actorID; client.attachWorker(workerActor, (response, workerClient) => { gDevTools.showToolbox(TargetFactory.forWorker(workerClient), - "jsdebugger", Toolbox.HostType.WINDOW); + "jsdebugger", Toolbox.HostType.WINDOW) + .then(toolbox => { + toolbox.once("destroy", () => workerClient.detach()); + }); }); break; default: diff --git a/devtools/client/aboutdebugging/test/browser.ini b/devtools/client/aboutdebugging/test/browser.ini index 7308c9517629..bba357b0c447 100644 --- a/devtools/client/aboutdebugging/test/browser.ini +++ b/devtools/client/aboutdebugging/test/browser.ini @@ -10,3 +10,4 @@ support-files = [browser_addons_install.js] [browser_service_workers.js] +[browser_service_workers_timeout.js] diff --git a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js new file mode 100644 index 000000000000..c942978dc44c --- /dev/null +++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js @@ -0,0 +1,112 @@ +/* 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 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 waitForWorkersUpdate(document) { + return new Promise(done => { + var observer = new MutationObserver(function(mutations) { + observer.disconnect(); + done(); + }); + var target = document.getElementById("service-workers"); + observer.observe(target, { childList: true }); + }); +} + +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], + ["dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT], + ]}; + SpecialPowers.pushPrefEnv(options, done); + }); + + let { tab, document } = yield openAboutDebugging("workers"); + + let swTab = yield addTab(TAB_URL); + + yield waitForWorkersUpdate(document); + + assertHasWorker(true, document, "service-workers", SERVICE_WORKER); + + // XXX: race, the WorkerDebugger is ready whereas ServiceWorkerInfo + // doesn't has the worker registered yet on its side + yield new Promise(done => { + require("sdk/timers").setTimeout(done, 250); + }); + + // 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 debugBtn = name.parentNode.parentNode.querySelector("button"); + ok(debugBtn, "Found its debug button"); + + // Click on it and wait for the toolbox to be ready + let onToolboxReady = new Promise(done => { + gDevTools.once("toolbox-ready", function (e, toolbox) { + done(toolbox); + }); + }); + debugBtn.click(); + + let toolbox = yield onToolboxReady; + + // 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); + + yield toolbox.destroy(); + toolbox = null; + + // Now ensure that the worker is correctly destroyed + // after we destroy the toolbox. + // The list should update once it get destroyed. + yield waitForWorkersUpdate(document); + + assertHasWorker(false, document, "service-workers", SERVICE_WORKER); + + // Finally, unregister the service worker itself + // 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 (success) { + dump("SW unregistered: " + success + "\n"); + }, + function (e) { + dump("SW not unregistered; " + e + "\n"); + }); + }); + }; + swTab.linkedBrowser.messageManager.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true); + + yield removeTab(swTab); + yield closeAboutDebugging(tab); +}); diff --git a/devtools/server/actors/worker.js b/devtools/server/actors/worker.js index 46b9d3b861db..bfc188adfb8a 100644 --- a/devtools/server/actors/worker.js +++ b/devtools/server/actors/worker.js @@ -11,6 +11,12 @@ XPCOMUtils.defineLazyServiceGetter( "nsIWorkerDebuggerManager" ); +XPCOMUtils.defineLazyServiceGetter( + this, "swm", + "@mozilla.org/serviceworkers/manager;1", + "nsIServiceWorkerManager" +); + function matchWorkerDebugger(dbg, options) { if ("type" in options && dbg.type !== options.type) { return false; @@ -54,6 +60,14 @@ WorkerActor.prototype = { } if (!this._isAttached) { + // Automatically disable their internal timeout that shut them down + // Should be refactored by having actors specific to service workers + if (this._dbg.type == Ci.nsIWorkerDebugger.TYPE_SERVICE) { + let worker = this._getServiceWorkerInfo(); + if (worker) { + worker.attachDebugger(); + } + } this._dbg.addListener(this); this._isAttached = true; } @@ -115,6 +129,11 @@ WorkerActor.prototype = { reportError("ERROR:" + filename + ":" + lineno + ":" + message + "\n"); }, + _getServiceWorkerInfo: function () { + let info = swm.getRegistrationByPrincipal(this._dbg.principal, this._dbg.url); + return info.getWorkerByID(this._dbg.serviceWorkerID); + }, + _detach: function () { if (this._threadActor !== null) { this._transport.close(); @@ -122,6 +141,20 @@ WorkerActor.prototype = { this._threadActor = null; } + // If the worker is already destroyed, nsIWorkerDebugger.type throws + // (_dbg.closed appears to be false when it throws) + let type; + try { + type = this._dbg.type; + } catch(e) {} + + if (type == Ci.nsIWorkerDebugger.TYPE_SERVICE) { + let worker = this._getServiceWorkerInfo(); + if (worker) { + worker.detachDebugger(); + } + } + this._dbg.removeListener(this); this._isAttached = false; }