From 1c6bb60c9aab63b3e29c7caf5c521fcab351f85e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eddy=20Bru=C3=ABl?= Date: Tue, 12 May 2015 18:58:34 +0200 Subject: [PATCH] Bug 1160199 - Implement TabActor.listWorkers;r=jlong --- browser/devtools/debugger/test/browser.ini | 4 + .../debugger/test/browser_dbg_listworkers.js | 59 ++++++++ .../debugger/test/code_listworkers-worker1.js | 3 + .../debugger/test/code_listworkers-worker2.js | 3 + .../debugger/test/doc_listworkers-tab.html | 8 ++ browser/devtools/debugger/test/head.js | 65 +++++++++ toolkit/components/telemetry/Histograms.json | 14 ++ toolkit/devtools/client/dbg-client.jsm | 8 +- toolkit/devtools/server/actors/webbrowser.js | 39 +++++- toolkit/devtools/server/actors/worker.js | 129 ++++++++++++++++++ toolkit/devtools/server/moz.build | 1 + 11 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 browser/devtools/debugger/test/browser_dbg_listworkers.js create mode 100644 browser/devtools/debugger/test/code_listworkers-worker1.js create mode 100644 browser/devtools/debugger/test/code_listworkers-worker2.js create mode 100644 browser/devtools/debugger/test/doc_listworkers-tab.html create mode 100644 toolkit/devtools/server/actors/worker.js diff --git a/browser/devtools/debugger/test/browser.ini b/browser/devtools/debugger/test/browser.ini index 0f1662156dc7..db2609c53e11 100644 --- a/browser/devtools/debugger/test/browser.ini +++ b/browser/devtools/debugger/test/browser.ini @@ -22,6 +22,8 @@ support-files = code_function-search-02.js code_function-search-03.js code_location-changes.js + code_listworkers-worker1.js + code_listworkers-worker2.js code_math.js code_math.map code_math.min.js @@ -72,6 +74,7 @@ support-files = doc_inline-debugger-statement.html doc_inline-script.html doc_large-array-buffer.html + doc_listworkers-tab.html doc_minified.html doc_minified_bogus_map.html doc_native-event-handler.html @@ -250,6 +253,7 @@ skip-if = e10s # TODO skip-if = e10s # TODO [browser_dbg_listtabs-03.js] skip-if = e10s && debug +[browser_dbg_listworkers.js] [browser_dbg_location-changes-01-simple.js] skip-if = e10s && debug [browser_dbg_location-changes-02-blank.js] diff --git a/browser/devtools/debugger/test/browser_dbg_listworkers.js b/browser/devtools/debugger/test/browser_dbg_listworkers.js new file mode 100644 index 000000000000..7bf3cf55899c --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_listworkers.js @@ -0,0 +1,59 @@ +let TAB_URL = EXAMPLE_URL + "doc_listworkers-tab.html"; +let WORKER1_URL = "code_listworkers-worker1.js"; +let WORKER2_URL = "code_listworkers-worker2.js"; + +function test() { + Task.spawn(function* () { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let tab = yield addTab(TAB_URL); + let { tabs } = yield listTabs(client); + let [, tabClient] = yield attachTab(client, findTab(tabs, TAB_URL)); + + let { workers } = yield listWorkers(tabClient); + is(workers.length, 0); + + executeSoon(() => { + evalInTab(tab, "let worker1 = new Worker('" + WORKER1_URL + "');"); + }); + yield waitForWorkerListChanged(tabClient); + + ({ workers } = yield listWorkers(tabClient)); + is(workers.length, 1); + is(workers[0].url, WORKER1_URL); + + executeSoon(() => { + evalInTab(tab, "let worker2 = new Worker('" + WORKER2_URL + "');"); + }); + yield waitForWorkerListChanged(tabClient); + + ({ workers } = yield listWorkers(tabClient)); + is(workers.length, 2); + is(workers[0].url, WORKER1_URL); + is(workers[1].url, WORKER2_URL); + + executeSoon(() => { + evalInTab(tab, "worker1.terminate()"); + }); + yield waitForWorkerListChanged(tabClient); + + ({ workers } = yield listWorkers(tabClient)); + is(workers.length, 1); + is(workers[0].url, WORKER2_URL); + + executeSoon(() => { + evalInTab(tab, "worker2.terminate()"); + }); + yield waitForWorkerListChanged(tabClient); + + ({ workers } = yield listWorkers(tabClient)); + is(workers.length, 0); + + yield close(client); + finish(); + }); +} diff --git a/browser/devtools/debugger/test/code_listworkers-worker1.js b/browser/devtools/debugger/test/code_listworkers-worker1.js new file mode 100644 index 000000000000..8cee6809e5f6 --- /dev/null +++ b/browser/devtools/debugger/test/code_listworkers-worker1.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/browser/devtools/debugger/test/code_listworkers-worker2.js b/browser/devtools/debugger/test/code_listworkers-worker2.js new file mode 100644 index 000000000000..8cee6809e5f6 --- /dev/null +++ b/browser/devtools/debugger/test/code_listworkers-worker2.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/browser/devtools/debugger/test/doc_listworkers-tab.html b/browser/devtools/debugger/test/doc_listworkers-tab.html new file mode 100644 index 000000000000..62ab9be7d2e8 --- /dev/null +++ b/browser/devtools/debugger/test/doc_listworkers-tab.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/browser/devtools/debugger/test/head.js b/browser/devtools/debugger/test/head.js index 808db094c686..643d349414cb 100644 --- a/browser/devtools/debugger/test/head.js +++ b/browser/devtools/debugger/test/head.js @@ -1025,3 +1025,68 @@ function getSourceForm(aSources, aURL) { let item = aSources.getItemByValue(getSourceActor(gSources, aURL)); return item.attachment.source; } + +function connect(client) { + info("Connecting client."); + return new Promise(function (resolve) { + client.connect(function () { + resolve(); + }); + }); +} + +function close(client) { + info("Closing client.\n"); + return new Promise(function (resolve) { + client.close(() => { + resolve(); + }); + }); +} + +function listTabs(client) { + info("Listing tabs."); + return new Promise(function (resolve) { + client.listTabs(function (response) { + resolve(response); + }); + }); +} + +function findTab(tabs, url) { + info("Finding tab with url '" + url + "'."); + for (let tab of tabs) { + if (tab.url === url) { + return tab; + } + } + return null; +} + +function attachTab(client, tab) { + info("Attaching to tab with url '" + tab.url + "'."); + return new Promise(function (resolve) { + client.attachTab(tab.actor, function (response, tabClient) { + resolve([response, tabClient]); + }); + }); +} + +function listWorkers(tabClient) { + info("Listing workers."); + return new Promise(function (resolve) { + tabClient.listWorkers(function (response) { + resolve(response); + }); + }); +} + +function waitForWorkerListChanged(tabClient) { + info("Waiting for worker list to change."); + return new Promise(function (resolve) { + tabClient.addListener("workerListChanged", function listener() { + tabClient.removeListener("workerListChanged", listener); + resolve(); + }); + }); +} diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index c6f0002c051a..94950aefc068 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -5526,6 +5526,20 @@ "n_buckets": "1000", "description": "The time (in milliseconds) that it took a 'reconfigure tab' request to go round trip." }, + "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTWORKERS_MS": { + "expires_in_version": "never", + "kind": "exponential", + "high": "10000", + "n_buckets": "1000", + "description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip." + }, + "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTWORKERS_MS": { + "expires_in_version": "never", + "kind": "exponential", + "high": "10000", + "n_buckets": "1000", + "description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip." + }, "DEVTOOLS_DEBUGGER_RDP_LOCAL_RECONFIGURETHREAD_MS": { "expires_in_version": "never", "kind": "exponential", diff --git a/toolkit/devtools/client/dbg-client.jsm b/toolkit/devtools/client/dbg-client.jsm index c3fdccb3728b..4d6d6b404812 100644 --- a/toolkit/devtools/client/dbg-client.jsm +++ b/toolkit/devtools/client/dbg-client.jsm @@ -1218,7 +1218,7 @@ function TabClient(aClient, aForm) { this.thread = null; this.request = this.client.request; this.traits = aForm.traits || {}; - this.events = []; + this.events = ["workerListChanged"]; } TabClient.prototype = { @@ -1321,6 +1321,12 @@ TabClient.prototype = { }, { telemetry: "RECONFIGURETAB" }), + + listWorkers: DebuggerClient.requester({ + type: "listWorkers" + }, { + telemetry: "LISTWORKERS" + }) }; eventSource(TabClient.prototype); diff --git a/toolkit/devtools/server/actors/webbrowser.js b/toolkit/devtools/server/actors/webbrowser.js index 0b5aa030c388..30bb18d7fafb 100644 --- a/toolkit/devtools/server/actors/webbrowser.js +++ b/toolkit/devtools/server/actors/webbrowser.js @@ -15,6 +15,7 @@ let DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); let { dbg_assert } = DevToolsUtils; let { TabSources } = require("./utils/TabSources"); let makeDebugger = require("./utils/make-debugger"); +let { WorkerActorList } = require("devtools/server/actors/worker"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -725,6 +726,10 @@ function TabActor(aConnection) this.listenForNewDocShells = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; this.traits = { reconfigure: true, frames: true }; + + this._workerActorList = null; + this._workerActorPool = null; + this._onWorkerActorListChanged = this._onWorkerActorListChanged.bind(this); } // XXX (bug 710213): TabActor attach/detach/exit/disconnect is a @@ -1076,6 +1081,37 @@ TabActor.prototype = { return { frames: windows }; }, + onListWorkers: function BTA_onListWorkers(aRequest) { + if (this._workerActorList === null) { + this._workerActorList = new WorkerActorList({ + window: this.window + }); + } + + return this._workerActorList.getList().then((actors) => { + let pool = new ActorPool(this.conn); + for (let actor of actors) { + pool.addActor(actor); + } + + this.conn.removeActorPool(this._workerActorPool); + this._workerActorPool = pool; + this.conn.addActorPool(this._workerActorPool); + + this._workerActorList.onListChanged = this._onWorkerActorListChanged; + + return { + "from": this.actorID, + "workers": actors.map((actor) => actor.form()) + }; + }); + }, + + _onWorkerActorListChanged: function () { + this._workerActorList.onListChanged = null; + this.conn.sendActorEvent(this.actorID, "workerListChanged"); + }, + observe: function (aSubject, aTopic, aData) { // Ignore any event that comes before/after the tab actor is attached // That typically happens during firefox shutdown. @@ -1769,7 +1805,8 @@ TabActor.prototype.requestTypes = { "navigateTo": TabActor.prototype.onNavigateTo, "reconfigure": TabActor.prototype.onReconfigure, "switchToFrame": TabActor.prototype.onSwitchToFrame, - "listFrames": TabActor.prototype.onListFrames + "listFrames": TabActor.prototype.onListFrames, + "listWorkers": TabActor.prototype.onListWorkers }; exports.TabActor = TabActor; diff --git a/toolkit/devtools/server/actors/worker.js b/toolkit/devtools/server/actors/worker.js new file mode 100644 index 000000000000..eb5f990e12f5 --- /dev/null +++ b/toolkit/devtools/server/actors/worker.js @@ -0,0 +1,129 @@ +"use strict"; + +let { Ci, Cu } = require("chrome"); + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyServiceGetter( + this, "wdm", + "@mozilla.org/dom/workers/workerdebuggermanager;1", + "nsIWorkerDebuggerManager" +); + +function matchWorkerDebugger(dbg, options) { + if ("window" in options) { + let window = dbg.window; + while (window !== null && window.parent !== window) { + window = window.parent; + } + + if (window !== options.window) { + return false; + } + } + + return true; +} + +function WorkerActor(dbg) { + this._dbg = dbg; +} + +WorkerActor.prototype = { + actorPrefix: "worker", + + form: function () { + return { + actor: this.actorID, + url: this._dbg.url + }; + } +}; + +exports.WorkerActor = WorkerActor; + +function WorkerActorList(options) { + this._options = options; + this._actors = new Map(); + this._onListChanged = null; + this._mustNotify = false; + this.onRegister = this.onRegister.bind(this); + this.onUnregister = this.onUnregister.bind(this); +} + +WorkerActorList.prototype = { + getList: function () { + let dbgs = new Set(); + let e = wdm.getWorkerDebuggerEnumerator(); + while (e.hasMoreElements()) { + let dbg = e.getNext().QueryInterface(Ci.nsIWorkerDebugger); + if (matchWorkerDebugger(dbg, this._options)) { + dbgs.add(dbg); + } + } + + for (let [dbg, ] of this._actors) { + if (!dbgs.has(dbg)) { + this._actors.delete(dbg); + } + } + + for (let dbg of dbgs) { + if (!this._actors.has(dbg)) { + this._actors.set(dbg, new WorkerActor(dbg)); + } + } + + let actors = []; + for (let [, actor] of this._actors) { + actors.push(actor); + } + + this._mustNotify = true; + this._checkListening(); + + return Promise.resolve(actors); + }, + + get onListChanged() { + return this._onListChanged; + }, + + set onListChanged(onListChanged) { + if (typeof onListChanged !== "function" && onListChanged !== null) { + throw new Error("onListChanged must be either a function or null."); + } + + this._onListChanged = onListChanged; + this._checkListening(); + }, + + _checkListening: function () { + if (this._onListChanged !== null && this._mustNotify) { + wdm.addListener(this); + } else { + wdm.removeListener(this); + } + }, + + _notifyListChanged: function () { + if (this._mustNotify) { + this._onListChanged(); + this._mustNotify = false; + } + }, + + onRegister: function (dbg) { + if (matchWorkerDebugger(dbg, this._options)) { + this._notifyListChanged(); + } + }, + + onUnregister: function (dbg) { + if (matchWorkerDebugger(dbg, this._options)) { + this._notifyListChanged(); + } + } +}; + +exports.WorkerActorList = WorkerActorList; diff --git a/toolkit/devtools/server/moz.build b/toolkit/devtools/server/moz.build index 36abfcd74330..b444e4ac4122 100644 --- a/toolkit/devtools/server/moz.build +++ b/toolkit/devtools/server/moz.build @@ -72,6 +72,7 @@ EXTRA_JS_MODULES.devtools.server.actors += [ 'actors/webbrowser.js', 'actors/webconsole.js', 'actors/webgl.js', + 'actors/worker.js', ] EXTRA_JS_MODULES.devtools.server.actors.utils += [