Bug 1164564 - Implement WorkerActor.attachThread;r=jlong

This commit is contained in:
Eddy Bruël 2015-06-15 12:18:35 +02:00
Родитель 5560e83bc3
Коммит 14464863a6
15 изменённых файлов: 421 добавлений и 18 удалений

Просмотреть файл

@ -45,6 +45,7 @@ support-files =
code_ugly-8^headers^
code_WorkerActor.attach-worker1.js
code_WorkerActor.attach-worker2.js
code_WorkerActor.attachThread-worker.js
doc_auto-pretty-print-01.html
doc_auto-pretty-print-02.html
doc_binary_search.html
@ -107,6 +108,7 @@ support-files =
doc_with-frame.html
doc_WorkerActor.attach-tab1.html
doc_WorkerActor.attach-tab2.html
doc_WorkerActor.attachThread-tab.html
head.js
sjs_random-javascript.sjs
testactors.js
@ -566,3 +568,5 @@ skip-if = e10s && debug
skip-if = e10s && debug
[browser_dbg_WorkerActor.attach.js]
skip-if = e10s && debug
[browser_dbg_WorkerActor.attachThread.js]
skip-if = e10s && debug

Просмотреть файл

@ -27,7 +27,6 @@ function test() {
// registered. Instead, we have to wait for the promise returned by
// createWorker in the tab to be resolved.
yield createWorkerInTab(tab, WORKER1_URL);
let { workers } = yield listWorkers(tabClient);
let [, workerClient1] = yield attachWorker(tabClient,
findWorker(workers, WORKER1_URL));

Просмотреть файл

@ -0,0 +1,89 @@
let TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html";
let WORKER_URL = "code_WorkerActor.attachThread-worker.js";
function test() {
Task.spawn(function* () {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
let client1 = new DebuggerClient(DebuggerServer.connectPipe());
yield connect(client1);
let client2 = new DebuggerClient(DebuggerServer.connectPipe());
yield connect(client2);
let tab = yield addTab(TAB_URL);
let { tabs: tabs1 } = yield listTabs(client1);
let [, tabClient1] = yield attachTab(client1, findTab(tabs1, TAB_URL));
let { tabs: tabs2 } = yield listTabs(client2);
let [, tabClient2] = yield attachTab(client2, findTab(tabs2, TAB_URL));
yield listWorkers(tabClient1);
yield listWorkers(tabClient2);
yield createWorkerInTab(tab, WORKER_URL);
let { workers: workers1 } = yield listWorkers(tabClient1);
let [, workerClient1] = yield attachWorker(tabClient1,
findWorker(workers1, WORKER_URL));
let { workers: workers2 } = yield listWorkers(tabClient2);
let [, workerClient2] = yield attachWorker(tabClient2,
findWorker(workers2, WORKER_URL));
let location = { line: 5 };
let [, threadClient1] = yield attachThread(workerClient1);
let sources1 = yield getSources(threadClient1);
let sourceClient1 = threadClient1.source(findSource(sources1,
EXAMPLE_URL + WORKER_URL));
let [, breakpointClient1] = yield setBreakpoint(sourceClient1, location);
yield resume(threadClient1);
let [, threadClient2] = yield attachThread(workerClient2);
let sources2 = yield getSources(threadClient2);
let sourceClient2 = threadClient2.source(findSource(sources2,
EXAMPLE_URL + WORKER_URL));
let [, breakpointClient2] = yield setBreakpoint(sourceClient2, location);
yield resume(threadClient2);
postMessageToWorkerInTab(tab, WORKER_URL, "ping");
yield Promise.all([
waitForPause(threadClient1).then((packet) => {
is(packet.type, "paused");
let why = packet.why;
is(why.type, "breakpoint");
is(why.actors.length, 1);
is(why.actors[0], breakpointClient1.actor);
let frame = packet.frame;
let where = frame.where;
is(where.source.actor, sourceClient1.actor);
is(where.line, location.line);
let variables = frame.environment.bindings.variables;
is(variables.a.value, 1);
is(variables.b.value.type, "undefined");
is(variables.c.value.type, "undefined");
return resume(threadClient1);
}),
waitForPause(threadClient2).then((packet) => {
is(packet.type, "paused");
let why = packet.why;
is(why.type, "breakpoint");
is(why.actors.length, 1);
is(why.actors[0], breakpointClient2.actor);
let frame = packet.frame;
let where = frame.where;
is(where.source.actor, sourceClient2.actor);
is(where.line, location.line);
let variables = frame.environment.bindings.variables;
is(variables.a.value, 1);
is(variables.b.value.type, "undefined");
is(variables.c.value.type, "undefined");
return resume(threadClient2);
}),
]);
terminateWorkerInTab(tab, WORKER_URL);
yield waitForWorkerClose(workerClient1);
yield waitForWorkerClose(workerClient2);
yield close(client1);
yield close(client2);
finish();
});
}

Просмотреть файл

@ -0,0 +1,16 @@
"use strict";
function f() {
var a = 1;
var b = 2;
var c = 3;
}
self.onmessage = function (event) {
if (event.data == "ping") {
f()
postMessage("pong");
}
};
postMessage("load");

Просмотреть файл

@ -83,3 +83,15 @@ addMessageListener("jsonrpc", function ({ data: { method, params, id } }) {
});
});
});
addMessageListener("test:postMessageToWorker", function (message) {
dump("Posting message '" + message.data.message + "' to worker with url '" +
message.data.url + "'.\n");
let worker = workers[message.data.url];
worker.postMessage(message.data.message);
worker.addEventListener("message", function listener() {
worker.removeEventListener("message", listener);
sendAsyncMessage("test:postMessageToWorker");
});
});

Просмотреть файл

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
</head>
<body>
</body>
</html>

Просмотреть файл

@ -512,9 +512,13 @@ function getTab(aTarget, aWindow) {
}
function getSources(aClient) {
info("Getting sources.");
let deferred = promise.defer();
aClient.getSources(({sources}) => deferred.resolve(sources));
aClient.getSources((packet) => {
deferred.resolve(packet.sources);
});
return deferred.promise;
}
@ -1129,6 +1133,15 @@ function waitForWorkerListChanged(tabClient) {
});
}
function attachThread(workerClient, options) {
info("Attaching to thread.");
return new Promise(function(resolve, reject) {
workerClient.attachThread(options, function (response, threadClient) {
resolve([response, threadClient]);
});
});
}
function waitForWorkerClose(workerClient) {
info("Waiting for worker to close.");
return new Promise(function (resolve) {
@ -1156,3 +1169,52 @@ function waitForWorkerThaw(workerClient) {
});
});
}
function resume(threadClient) {
info("Resuming thread.");
return rdpInvoke(threadClient, threadClient.resume);
}
function findSource(sources, url) {
info("Finding source with url '" + url + "'.\n");
for (let source of sources) {
if (source.url === url) {
return source;
}
}
return null;
}
function setBreakpoint(sourceClient, location) {
info("Setting breakpoint.\n");
return new Promise(function (resolve) {
sourceClient.setBreakpoint(location, function (response, breakpointClient) {
resolve([response, breakpointClient]);
});
});
}
function waitForEvent(client, type, predicate) {
return new Promise(function (resolve) {
function listener(type, packet) {
if (!predicate(packet)) {
return;
}
client.removeListener(listener);
resolve(packet);
}
if (predicate) {
client.addListener(type, listener);
} else {
client.addOneTimeListener(type, function (type, packet) {
resolve(packet);
});
}
});
}
function waitForPause(threadClient) {
info("Waiting for pause.\n");
return waitForEvent(threadClient, "paused");
}

Просмотреть файл

@ -1360,7 +1360,7 @@ TabClient.prototype = {
eventSource(TabClient.prototype);
function WorkerClient(aClient, aForm) {
this._client = aClient;
this.client = aClient;
this._actor = aForm.from;
this._isClosed = false;
this._isFrozen = aForm.isFrozen;
@ -1376,11 +1376,11 @@ function WorkerClient(aClient, aForm) {
WorkerClient.prototype = {
get _transport() {
return this._client._transport;
return this.client._transport;
},
get request() {
return this._client.request;
return this.client.request;
},
get actor() {
@ -1397,19 +1397,41 @@ WorkerClient.prototype = {
detach: DebuggerClient.requester({ type: "detach" }, {
after: function (aResponse) {
this._client.unregisterClient(this);
this.client.unregisterClient(this);
return aResponse;
},
telemetry: "WORKERDETACH"
}),
attachThread: function(aOptions = {}, aOnResponse = noop) {
if (this.thread) {
DevToolsUtils.executeSoon(() => aOnResponse({
type: "connected",
threadActor: this.thread._actor,
}, this.thread));
return;
}
this.request({
to: this._actor,
type: "connect",
options: aOptions,
}, (aResponse) => {
if (!aResponse.error) {
this.thread = new ThreadClient(this, aResponse.threadActor);
this.client.registerClient(this.thread);
}
aOnResponse(aResponse, this.thread);
});
},
_onClose: function () {
this.removeListener("close", this._onClose);
this.removeListener("freeze", this._onFreeze);
this.removeListener("thaw", this._onThaw);
this._client.unregisterClient(this);
this.client.unregisterClient(this);
this._closed = true;
},

Просмотреть файл

@ -15,6 +15,7 @@ const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
const { dbg_assert, dumpn, update, fetch } = DevToolsUtils;
const { dirname, joinURI } = require("devtools/toolkit/path");
const promise = require("promise");
const PromiseDebugging = require("PromiseDebugging");
const xpcInspector = require("xpcInspector");
const ScriptStore = require("./utils/ScriptStore");
const {DevToolsWorker} = require("devtools/toolkit/shared/worker.js");
@ -1494,7 +1495,7 @@ ThreadActor.prototype = {
// Clear DOM event breakpoints.
// XPCShell tests don't use actual DOM windows for globals and cause
// removeListenerForAllEvents to throw.
if (this.global && !this.global.toString().includes("Sandbox")) {
if (!isWorker && this.global && !this.global.toString().includes("Sandbox")) {
let els = Cc["@mozilla.org/eventlistenerservice;1"]
.getService(Ci.nsIEventListenerService);
els.removeListenerForAllEvents(this.global, this._allEventsListener, true);
@ -1933,7 +1934,7 @@ ThreadActor.prototype = {
}
if (promises.length > 0) {
this.synchronize(Promise.all(promises));
this.synchronize(promise.all(promises));
}
return true;
@ -2870,10 +2871,10 @@ SourceActor.prototype = {
actor,
GeneratedLocation.fromOriginalLocation(originalLocation)
)) {
return Promise.resolve(null);
return promise.resolve(null);
}
return Promise.resolve(originalLocation);
return promise.resolve(originalLocation);
} else {
return this.sources.getAllGeneratedLocations(originalLocation)
.then((generatedLocations) => {

Просмотреть файл

@ -10,7 +10,7 @@ const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
const { dbg_assert, fetch } = DevToolsUtils;
const EventEmitter = require("devtools/toolkit/event-emitter");
const { OriginalLocation, GeneratedLocation, getOffsetColumn } = require("devtools/server/actors/common");
const { resolve } = Promise;
const { resolve } = require("promise");
loader.lazyRequireGetter(this, "SourceActor", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "isEvalSource", "devtools/server/actors/script", true);

Просмотреть файл

@ -1,6 +1,7 @@
"use strict";
let { Ci, Cu } = require("chrome");
let { DebuggerServer } = require("devtools/server/main");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -28,6 +29,8 @@ function matchWorkerDebugger(dbg, options) {
function WorkerActor(dbg) {
this._dbg = dbg;
this._isAttached = false;
this._threadActor = null;
this._transport = null;
}
WorkerActor.prototype = {
@ -66,6 +69,33 @@ WorkerActor.prototype = {
return { type: "detached" };
},
onConnect: function (request) {
if (!this._isAttached) {
return { error: "wrongState" };
}
if (this._threadActor !== null) {
return {
type: "connected",
threadActor: this._threadActor
};
}
return DebuggerServer.connectToWorker(
this.conn, this._dbg, this.actorID, request.options
).then(({ threadActor, transport }) => {
this._threadActor = threadActor;
this._transport = transport;
return {
type: "connected",
threadActor: this._threadActor
};
}, (error) => {
return { error: error.toString() };
});
},
onClose: function () {
if (this._isAttached) {
this._detach();
@ -74,6 +104,10 @@ WorkerActor.prototype = {
this.conn.sendActorEvent(this.actorID, "close");
},
onError: function (filename, lineno, message) {
reportError("ERROR:" + filename + ":" + lineno + ":" + message + "\n");
},
onFreeze: function () {
this.conn.sendActorEvent(this.actorID, "freeze");
},
@ -83,6 +117,12 @@ WorkerActor.prototype = {
},
_detach: function () {
if (this._threadActor !== null) {
this._transport.close();
this._transport = null;
this._threadActor = null;
}
this._dbg.removeListener(this);
this._isAttached = false;
}
@ -90,7 +130,8 @@ WorkerActor.prototype = {
WorkerActor.prototype.requestTypes = {
"attach": WorkerActor.prototype.onAttach,
"detach": WorkerActor.prototype.onDetach
"detach": WorkerActor.prototype.onDetach,
"connect": WorkerActor.prototype.onConnect
};
exports.WorkerActor = WorkerActor;

Просмотреть файл

@ -14,7 +14,7 @@ let { Ci, Cc, CC, Cu, Cr } = require("chrome");
let Services = require("Services");
let { ActorPool, OriginalLocation, RegisteredActorFactory,
ObservedActorFactory } = require("devtools/server/actors/common");
let { LocalDebuggerTransport, ChildDebuggerTransport } =
let { LocalDebuggerTransport, ChildDebuggerTransport, WorkerDebuggerTransport } =
require("devtools/toolkit/transport/transport");
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
let { dumpn, dumpv, dbg_assert } = DevToolsUtils;
@ -685,10 +685,13 @@ var DebuggerServer = {
* "debug:<prefix>:packet", and all its actors will have names
* beginning with "<prefix>/".
*/
connectToParent: function(aPrefix, aMessageManager) {
connectToParent: function(aPrefix, aScopeOrManager) {
this._checkInit();
let transport = new ChildDebuggerTransport(aMessageManager, aPrefix);
let transport = isWorker ?
new WorkerDebuggerTransport(aScopeOrManager, aPrefix) :
new ChildDebuggerTransport(aScopeOrManager, aPrefix);
return this._onConnection(transport, aPrefix, true);
},
@ -755,6 +758,83 @@ var DebuggerServer = {
return deferred.promise;
},
connectToWorker: function (aConnection, aDbg, aId, aOptions) {
return new Promise((resolve, reject) => {
// Step 1: Initialize the worker debugger.
aDbg.initialize("resource://gre/modules/devtools/server/worker.js");
// Step 2: Send a connect request to the worker debugger.
aDbg.postMessage(JSON.stringify({
type: "connect",
id: aId,
options: aOptions
}));
// Steps 3-5 are performed on the worker thread (see worker.js).
// Step 6: Wait for a response from the worker debugger.
let listener = {
onClose: () => {
aDbg.removeListener(listener);
reject("closed");
},
onMessage: (message) => {
let packet = JSON.parse(message);
if (packet.type !== "message" || packet.id !== aId) {
return;
}
message = packet.message;
if (message.error) {
reject(error);
}
if (message.type !== "paused") {
return;
}
aDbg.removeListener(listener);
// Step 7: Create a transport for the connection to the worker.
let transport = new WorkerDebuggerTransport(aDbg, aId);
transport.ready();
transport.hooks = {
onClosed: () => {
if (!aDbg.isClosed) {
aDbg.postMessage(JSON.stringify({
type: "disconnect",
id: aId
}));
}
aConnection.cancelForwarding(aId);
},
onPacket: (packet) => {
// Ensure that any packets received from the server on the worker
// thread are forwarded to the client on the main thread, as if
// they had been sent by the server on the main thread.
aConnection.send(packet);
}
};
// Ensure that any packets received from the client on the main thread
// to actors on the worker thread are forwarded to the server on the
// worker thread.
aConnection.setForwarding(aId, transport);
resolve({
threadActor: message.from,
transport: transport
});
}
};
aDbg.addListener(listener);
});
},
/**
* Check if the caller is running in a content child process.
*

Просмотреть файл

@ -51,6 +51,7 @@ EXTRA_JS_MODULES.devtools.server += [
'content-globals.js',
'main.js',
'protocol.js',
'worker.js'
]
EXTRA_JS_MODULES.devtools.server.actors += [

Просмотреть файл

@ -0,0 +1,66 @@
"use strict"
loadSubScript("resource://gre/modules/devtools/worker-loader.js");
let { ActorPool } = worker.require("devtools/server/actors/common");
let { ThreadActor } = worker.require("devtools/server/actors/script");
let { TabSources } = worker.require("devtools/server/actors/utils/TabSources");
let makeDebugger = worker.require("devtools/server/actors/utils/make-debugger");
let { DebuggerServer } = worker.require("devtools/server/main");
DebuggerServer.init();
DebuggerServer.createRootActor = function () {
throw new Error("Should never get here!");
};
let connections = Object.create(null);
this.addEventListener("message", function (event) {
let packet = JSON.parse(event.data);
switch (packet.type) {
case "connect":
// Step 3: Create a connection to the parent.
let connection = DebuggerServer.connectToParent(packet.id, this);
connections[packet.id] = connection;
// Step 4: Create a thread actor for the connection to the parent.
let pool = new ActorPool(connection);
connection.addActorPool(pool);
let sources = null;
let actor = new ThreadActor({
makeDebugger: makeDebugger.bind(null, {
findDebuggees: () => {
return [this.global];
},
shouldAddNewGlobalAsDebuggee: () => {
return true;
},
}),
get sources() {
if (sources === null) {
sources = new TabSources(actor);
}
return sources;
}
}, global);
pool.addActor(actor);
// Step 5: Attach to the thread actor.
//
// This will cause a packet to be sent over the connection to the parent.
// Because this connection uses WorkerDebuggerTransport internally, this
// packet will be sent using WorkerDebuggerGlobalScope.postMessage, causing
// an onMessage event to be fired on the WorkerDebugger in the main thread.
actor.onAttach({});
break;
case "disconnect":
connections[packet.id].close();
break;
};
});

Просмотреть файл

@ -435,6 +435,8 @@ let {
} else { // Worker thread
let requestors = [];
let scope = this;
let xpcInspector = {
get lastNestRequestor() {
return requestors.length === 0 ? null : requestors[0];
@ -442,13 +444,13 @@ let {
enterNestedEventLoop: function (requestor) {
requestors.push(requestor);
this.enterEventLoop();
scope.enterEventLoop();
return requestors.length;
},
exitNestedEventLoop: function () {
requestors.pop();
this.leaveEventLoop();
scope.leaveEventLoop();
return requestors.length;
}
};