Bug 1168853 - Implement (Worker)SourceActor.source;r=jlong

This commit is contained in:
Eddy Bruël 2015-06-17 17:22:18 +02:00
parent 10a086a22d
commit 2ecb55970f
6 changed files with 233 additions and 124 deletions

View File

@ -43,6 +43,17 @@ function test() {
let [, breakpointClient2] = yield setBreakpoint(sourceClient2, location);
yield resume(threadClient2);
let packet = yield source(sourceClient1);
let text = (yield new Promise(function (resolve) {
let request = new XMLHttpRequest();
request.open("GET", EXAMPLE_URL + WORKER_URL, true);
request.send();
request.onload = function () {
resolve(request.responseText);
};
}));
is(packet.source, text);
postMessageToWorkerInTab(tab, WORKER_URL, "ping");
yield Promise.all([
waitForPause(threadClient1).then((packet) => {

View File

@ -934,10 +934,13 @@ function attachAddonActorForUrl(aClient, aUrl) {
function rdpInvoke(aClient, aMethod, ...args) {
return promiseInvoke(aClient, aMethod, ...args)
.then(({error, message }) => {
.then((packet) => {
let { error, message } = packet;
if (error) {
throw new Error(error + ": " + message);
}
return packet;
});
}
@ -1185,15 +1188,6 @@ function findSource(sources, url) {
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) {
@ -1218,3 +1212,13 @@ function waitForPause(threadClient) {
info("Waiting for pause.\n");
return waitForEvent(threadClient, "paused");
}
function setBreakpoint(sourceClient, location) {
info("Setting breakpoint.\n");
return rdpInvoke(sourceClient, sourceClient.setBreakpoint, location);
}
function source(sourceClient) {
info("Getting source.\n");
return rdpInvoke(sourceClient, sourceClient.source);
}

View File

@ -445,66 +445,69 @@ exports.defineLazyGetter(this, "NetUtil", () => {
* without relying on caching when we can (not for eval, etc.):
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
*/
exports.fetch = function fetch(aURL, aOptions={ loadFromCache: true,
policy: Ci.nsIContentPolicy.TYPE_OTHER,
window: null,
charset: null }) {
let deferred = promise.defer();
let scheme;
let url = aURL.split(" -> ").pop();
let charset;
let contentType;
try {
scheme = Services.io.extractScheme(url);
} catch (e) {
// In the xpcshell tests, the script url is the absolute path of the test
// file, which will make a malformed URI error be thrown. Add the file
// scheme prefix ourselves.
url = "file://" + url;
scheme = Services.io.extractScheme(url);
}
// Fetch is defined differently depending on whether we are on the main thread
// or a worker thread.
if (!this.isWorker) {
exports.fetch = function (aURL, aOptions={ loadFromCache: true,
policy: Ci.nsIContentPolicy.TYPE_OTHER,
window: null,
charset: null }) {
let deferred = promise.defer();
let scheme;
let url = aURL.split(" -> ").pop();
let charset;
let contentType;
switch (scheme) {
case "file":
case "chrome":
case "resource":
try {
NetUtil.asyncFetch({
uri: url,
loadUsingSystemPrincipal: true
}, function onFetch(aStream, aStatus, aRequest) {
if (!components.isSuccessCode(aStatus)) {
deferred.reject(new Error("Request failed with status code = "
+ aStatus
+ " after NetUtil.asyncFetch for url = "
+ url));
return;
}
try {
scheme = Services.io.extractScheme(url);
} catch (e) {
// In the xpcshell tests, the script url is the absolute path of the test
// file, which will make a malformed URI error be thrown. Add the file
// scheme prefix ourselves.
url = "file://" + url;
scheme = Services.io.extractScheme(url);
}
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
contentType = aRequest.contentType;
deferred.resolve(source);
aStream.close();
});
} catch (ex) {
deferred.reject(ex);
}
break;
switch (scheme) {
case "file":
case "chrome":
case "resource":
try {
NetUtil.asyncFetch({
uri: url,
loadUsingSystemPrincipal: true
}, function onFetch(aStream, aStatus, aRequest) {
if (!components.isSuccessCode(aStatus)) {
deferred.reject(new Error("Request failed with status code = "
+ aStatus
+ " after NetUtil.asyncFetch for url = "
+ url));
return;
}
default:
let channel;
try {
channel = Services.io.newChannel2(url,
null,
null,
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
aOptions.policy);
} catch (e) {
if (e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
contentType = aRequest.contentType;
deferred.resolve(source);
aStream.close();
});
} catch (ex) {
deferred.reject(ex);
}
break;
default:
let channel;
try {
channel = Services.io.newChannel2(url,
null,
null,
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
aOptions.policy);
} catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
// On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
// newChannel won't be able to handle it.
url = "file:///" + url;
@ -516,65 +519,70 @@ exports.fetch = function fetch(aURL, aOptions={ loadFromCache: true,
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
aOptions.policy);
} else {
throw e;
}
}
let chunks = [];
let streamListener = {
onStartRequest: function(aRequest, aContext, aStatusCode) {
if (!components.isSuccessCode(aStatusCode)) {
deferred.reject(new Error("Request failed with status code = "
+ aStatusCode
+ " in onStartRequest handler for url = "
+ url));
}
},
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
if (!components.isSuccessCode(aStatusCode)) {
deferred.reject(new Error("Request failed with status code = "
+ aStatusCode
+ " in onStopRequest handler for url = "
+ url));
return;
}
let chunks = [];
let streamListener = {
onStartRequest: function(aRequest, aContext, aStatusCode) {
if (!components.isSuccessCode(aStatusCode)) {
deferred.reject(new Error("Request failed with status code = "
+ aStatusCode
+ " in onStartRequest handler for url = "
+ url));
}
},
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
if (!components.isSuccessCode(aStatusCode)) {
deferred.reject(new Error("Request failed with status code = "
+ aStatusCode
+ " in onStopRequest handler for url = "
+ url));
return;
}
charset = channel.contentCharset || aOptions.charset;
contentType = channel.contentType;
deferred.resolve(chunks.join(""));
charset = channel.contentCharset || aOptions.charset;
contentType = channel.contentType;
deferred.resolve(chunks.join(""));
}
};
if (aOptions.window) {
// Respect private browsing.
channel.loadGroup = aOptions.window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocumentLoader)
.loadGroup;
}
channel.loadFlags = aOptions.loadFromCache
? channel.LOAD_FROM_CACHE
: channel.LOAD_BYPASS_CACHE;
try {
channel.asyncOpen(streamListener, null);
} catch(e) {
deferred.reject(new Error("Request failed for '"
+ url
+ "': "
+ e.message));
}
break;
}
return deferred.promise.then(source => {
return {
content: convertToUnicode(source, charset),
contentType: contentType
};
if (aOptions.window) {
// Respect private browsing.
channel.loadGroup = aOptions.window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocumentLoader)
.loadGroup;
}
channel.loadFlags = aOptions.loadFromCache
? channel.LOAD_FROM_CACHE
: channel.LOAD_BYPASS_CACHE;
try {
channel.asyncOpen(streamListener, null);
} catch(e) {
deferred.reject(new Error("Request failed for '"
+ url
+ "': "
+ e.message));
}
break;
});
}
} else {
// Services is not available in worker threads, nor is there any other way
// to fetch a URL. We need to enlist the help from the main thread here, by
// issuing an rpc request, to fetch the URL on our behalf.
exports.fetch = function (url, options) {
return rpc("fetch", url, options);
}
return deferred.promise.then(source => {
return {
content: convertToUnicode(source, charset),
contentType: contentType
};
});
}
/**

View File

@ -20,6 +20,7 @@ let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
let { dumpn, dumpv, dbg_assert } = DevToolsUtils;
let EventEmitter = require("devtools/toolkit/event-emitter");
let Debugger = require("Debugger");
let Promise = require("promise");
DevToolsUtils.defineLazyGetter(this, "DebuggerSocket", () => {
let { DebuggerSocket } = require("devtools/toolkit/security/socket");
@ -760,8 +761,53 @@ var DebuggerServer = {
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 1: Ensure the worker debugger is initialized.
if (!aDbg.isInitialized) {
aDbg.initialize("resource://gre/modules/devtools/server/worker.js");
// Create a listener for rpc requests from the worker debugger. Only do
// this once, when the worker debugger is first initialized, rather than
// for each connection.
let listener = {
onClose: () => {
aDbg.removeListener(listener);
},
onMessage: (message) => {
let packet = JSON.parse(message);
if (packet.type !== "rpc") {
return;
}
Promise.resolve().then(() => {
let method = {
"fetch": DevToolsUtils.fetch,
}[packet.method];
if (!method) {
throw Error("Unknown method: " + packet.method);
}
return method.apply(undefined, packet.params);
}).then((value) => {
aDbg.postMessage(JSON.stringify({
type: "rpc",
result: value,
error: null,
id: packet.id
}));
}, (reason) => {
aDbg.postMessage(JSON.stringify({
type: "rpc",
result: null,
error: reason,
id: packet.id
}));
});
}
};
aDbg.addListener(listener);
}
// Step 2: Send a connect request to the worker debugger.
aDbg.postMessage(JSON.stringify({

View File

@ -1,7 +1,27 @@
"use strict"
// This function is used to do remote procedure calls from the worker to the
// main thread. It is exposed as a built-in global to every module by the
// worker loader. To make sure the worker loader can access it, it needs to be
// defined before loading the worker loader script below.
this.rpc = function (method, ...params) {
let id = nextId++;
postMessage(JSON.stringify({
type: "rpc",
method: method,
params: params,
id: id
}));
let deferred = Promise.defer();
rpcDeferreds[id] = deferred;
return deferred.promise;
};
loadSubScript("resource://gre/modules/devtools/worker-loader.js");
let Promise = worker.require("promise");
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");
@ -14,6 +34,8 @@ DebuggerServer.createRootActor = function () {
};
let connections = Object.create(null);
let nextId = 0;
let rpcDeferreds = [];
this.addEventListener("message", function (event) {
let packet = JSON.parse(event.data);
@ -21,7 +43,10 @@ this.addEventListener("message", function (event) {
case "connect":
// Step 3: Create a connection to the parent.
let connection = DebuggerServer.connectToParent(packet.id, this);
connections[packet.id] = connection;
connections[packet.id] = {
connection : connection,
rpcs: []
};
// Step 4: Create a thread actor for the connection to the parent.
let pool = new ActorPool(connection);
@ -60,7 +85,16 @@ this.addEventListener("message", function (event) {
break;
case "disconnect":
connections[packet.id].close();
connections[packet.id].connection.close();
break;
case "rpc":
let deferred = rpcDeferreds[packet.id];
delete rpcDeferreds[packet.id];
if (packet.error) {
deferred.reject(packet.error);
}
deferred.resolve(packet.result);
break;
};
});

View File

@ -368,6 +368,7 @@ let {
Debugger,
createSandbox,
dump,
rpc,
loadSubScript,
reportError,
setImmediate,
@ -405,6 +406,8 @@ let {
});
};
let rpc = undefined;
let subScriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
getService(Ci.mozIJSSubScriptLoader);
@ -427,6 +430,7 @@ let {
Debugger,
createSandbox,
dump,
rpc,
loadSubScript,
reportError,
setImmediate,
@ -459,6 +463,7 @@ let {
Debugger: this.Debugger,
createSandbox: this.createSandbox,
dump: this.dump,
rpc: this.rpc,
loadSubScript: this.loadSubScript,
reportError: this.reportError,
setImmediate: this.setImmediate,
@ -477,6 +482,7 @@ this.worker = new WorkerDebuggerLoader({
"dump": dump,
"loader": loader,
"reportError": reportError,
"rpc": rpc,
"setImmediate": setImmediate
},
loadSubScript: loadSubScript,