Bug 1215117 - Make console input field work inside a worker toolbox;r=ejpbruel

This commit is contained in:
Brian Grinstead 2015-10-26 09:13:11 -07:00
parent 226f46e27a
commit 68a27aa79d
16 changed files with 302 additions and 105 deletions

View File

@ -574,6 +574,8 @@ skip-if = e10s && debug
skip-if = e10s && debug
[browser_dbg_watch-expressions-02.js]
skip-if = e10s && debug
[browser_dbg_worker-console.js]
skip-if = e10s && debug
[browser_dbg_worker-window.js]
skip-if = e10s && debug
[browser_dbg_WorkerActor.attach.js]

View File

@ -33,7 +33,7 @@ function test() {
let oncePaused = gTarget.once("thread-paused");
EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
let jsterm = yield getSplitConsole();
let jsterm = yield getSplitConsole(gDevTools.getToolbox(gPanel.target));
let executed = jsterm.execute("1+1");
yield oncePaused;
@ -54,20 +54,4 @@ function test() {
yield executed;
});
function getSplitConsole() {
return new Promise(resolve => {
let toolbox = gDevTools.getToolbox(gPanel.target);
toolbox.once("webconsole-ready", () => {
ok(toolbox.splitConsole, "Split console is shown.");
let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
resolve(jsterm);
});
EventUtils.synthesizeKey("VK_ESCAPE", {}, gDebugger);
});
}
}
registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
});

View File

@ -94,19 +94,4 @@ function test() {
closeDebuggerAndFinish(gPanel);
});
});
function getSplitConsole(toolbox, theDebugger) {
return new Promise(resolve => {
toolbox.once("webconsole-ready", () => {
let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
resolve(jsterm);
});
EventUtils.synthesizeKey("VK_ESCAPE", {}, theDebugger);
});
}
}
registerCleanupFunction(() => {
// We don't want the open split console to confuse other tests..
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
});

View File

@ -0,0 +1,145 @@
// Check to make sure that a worker can be attached to a toolbox
// and that the console works.
var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html";
var WORKER_URL = "code_WorkerActor.attachThread-worker.js";
function* initWorkerDebugger(TAB_URL, WORKER_URL) {
if (!DebuggerServer.initialized) {
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));
yield createWorkerInTab(tab, WORKER_URL);
let { workers } = yield listWorkers(tabClient);
let [, workerClient] = yield attachWorker(tabClient,
findWorker(workers, WORKER_URL));
let toolbox = yield gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
"jsdebugger",
Toolbox.HostType.WINDOW);
let debuggerPanel = toolbox.getCurrentPanel();
let gDebugger = debuggerPanel.panelWin;
return {client,tab,tabClient,workerClient,toolbox,gDebugger};
}
add_task(function* testNormalExecution() {
let {client,tab,tabClient,workerClient,toolbox,gDebugger} =
yield initWorkerDebugger(TAB_URL, WORKER_URL);
let jsterm = yield getSplitConsole(toolbox);
let executed = yield jsterm.execute("this.location.toString()");
ok(executed.textContent.includes(WORKER_URL),
"Evaluating the global's location works");
yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
terminateWorkerInTab(tab, WORKER_URL);
yield waitForWorkerClose(workerClient);
yield close(client);
yield removeTab(tab);
});
add_task(function* testWhilePaused() {
let {client,tab,tabClient,workerClient,toolbox,gDebugger} =
yield initWorkerDebugger(TAB_URL, WORKER_URL);
let gTarget = gDebugger.gTarget;
let gResumeButton = gDebugger.document.getElementById("resume");
let gResumeKey = gDebugger.document.getElementById("resumeKey");
// Execute some basic math to make sure evaluations are working.
let jsterm = yield getSplitConsole(toolbox);
let executed = yield jsterm.execute("10000+1");
ok(executed.textContent.includes("10001"), "Text for message appeared correct");
// Pause the worker by waiting for next execution and then sending a message to
// it from the main thread.
let oncePaused = gTarget.once("thread-paused");
EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
once(gDebugger.gClient, "willInterrupt").then(() => {
info("Posting message to worker, then waiting for a pause");
postMessageToWorkerInTab(tab, WORKER_URL, "ping");
});
yield oncePaused;
let command1 = jsterm.execute("10000+2");
let command2 = jsterm.execute("10000+3");
let command3 = jsterm.execute("foobar"); // throw an error
info ("Trying to get the result of command1");
executed = yield command1;
ok(executed.textContent.includes("10002"),
"command1 executed successfully");
info ("Trying to get the result of command2");
executed = yield command2;
ok(executed.textContent.includes("10003"),
"command2 executed successfully");
info ("Trying to get the result of command3")
executed = yield command3;
// XXXworkers This is failing until Bug 1215120 is resolved.
todo(executed.textContent.includes("ReferenceError: foobar is not defined"),
"command3 executed successfully");
let onceResumed = gTarget.once("thread-resumed");
EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
yield onceResumed;
yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
terminateWorkerInTab(tab, WORKER_URL);
yield waitForWorkerClose(workerClient);
yield close(client);
yield removeTab(tab);
});
// Test to see if creating the pause from the console works.
add_task(function* testPausedByConsole() {
let {client,tab,tabClient,workerClient,toolbox,gDebugger} =
yield initWorkerDebugger(TAB_URL, WORKER_URL);
let gTarget = gDebugger.gTarget;
let gResumeButton = gDebugger.document.getElementById("resume");
let gResumeKey = gDebugger.document.getElementById("resumeKey");
let jsterm = yield getSplitConsole(toolbox);
let executed = yield jsterm.execute("10000+1");
ok(executed.textContent.includes("10001"),
"Text for message appeared correct");
let oncePaused = gTarget.once("thread-paused");
EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
let pausedExecution = jsterm.execute("10000+2");
info("Executed a command with 'break on next' active, waiting for pause");
yield oncePaused;
executed = yield jsterm.execute("10000+3");
ok(executed.textContent.includes("10003"),
"Text for message appeared correct");
info("Waiting for a resume");
let onceResumed = gTarget.once("thread-resumed");
EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
yield onceResumed;
executed = yield pausedExecution;
ok(executed.textContent.includes("10002"),
"Text for message appeared correct");
yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
terminateWorkerInTab(tab, WORKER_URL);
yield waitForWorkerClose(workerClient);
yield close(client);
yield removeTab(tab);
});

View File

@ -1190,3 +1190,28 @@ function afterDispatch(store, type) {
});
});
}
// Return a promise with a reference to jsterm, opening the split
// console if necessary. This cleans up the split console pref so
// it won't pollute other tests.
function getSplitConsole(toolbox, win) {
registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
});
if (!win) {
win = toolbox.doc.defaultView;
}
if (!toolbox.splitConsole) {
EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
}
return new Promise(resolve => {
toolbox.getPanelWhenReady("webconsole").then(() => {
ok(toolbox.splitConsole, "Split console is shown.");
let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
resolve(jsterm);
});
});
}

View File

@ -732,8 +732,14 @@ WorkerTarget.prototype = {
return this._workerClient.url;
},
get isWorkerTarget() {
return true;
},
get form() {
return {};
return {
consoleActor: this._workerClient.consoleActor
};
},
get activeTab() {

View File

@ -5033,13 +5033,17 @@ WebConsoleConnectionProxy.prototype = {
let client = this.client = this.target.client;
client.addListener("logMessage", this._onLogMessage);
client.addListener("pageError", this._onPageError);
client.addListener("consoleAPICall", this._onConsoleAPICall);
client.addListener("fileActivity", this._onFileActivity);
client.addListener("reflowActivity", this._onReflowActivity);
client.addListener("serverLogCall", this._onServerLogCall);
client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited);
if (this.target.isWorkerTarget) {
// XXXworkers: Not Console API yet inside of workers (Bug 1209353).
} else {
client.addListener("logMessage", this._onLogMessage);
client.addListener("pageError", this._onPageError);
client.addListener("consoleAPICall", this._onConsoleAPICall);
client.addListener("fileActivity", this._onFileActivity);
client.addListener("reflowActivity", this._onReflowActivity);
client.addListener("serverLogCall", this._onServerLogCall);
client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited);
}
this.target.on("will-navigate", this._onTabNavigated);
this.target.on("navigate", this._onTabNavigated);

View File

@ -6,35 +6,18 @@
"use strict";
const Services = require("Services");
const { Cc, Ci, Cu } = require("chrome");
const { DebuggerServer, ActorPool } = require("devtools/server/main");
const { EnvironmentActor, ThreadActor } = require("devtools/server/actors/script");
const { ObjectActor, LongStringActor, createValueGrip, stringIsLong } = require("devtools/server/actors/object");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => {
return require("devtools/shared/webconsole/network-monitor")
.NetworkMonitor;
});
XPCOMUtils.defineLazyGetter(this, "NetworkMonitorChild", () => {
return require("devtools/shared/webconsole/network-monitor")
.NetworkMonitorChild;
});
XPCOMUtils.defineLazyGetter(this, "ConsoleProgressListener", () => {
return require("devtools/shared/webconsole/network-monitor")
.ConsoleProgressListener;
});
XPCOMUtils.defineLazyGetter(this, "events", () => {
return require("sdk/event/core");
});
XPCOMUtils.defineLazyGetter(this, "ServerLoggingListener", () => {
return require("devtools/shared/webconsole/server-logger")
.ServerLoggingListener;
});
loader.lazyRequireGetter(this, "NetworkMonitor", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "NetworkMonitorChild", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "events", "sdk/event/core");
loader.lazyRequireGetter(this, "ServerLoggingListener", "devtools/shared/webconsole/server-logger", true);
for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
"ConsoleAPIListener", "addWebConsoleCommands", "JSPropertyProvider",
@ -44,7 +27,11 @@ for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
if (prop == "WebConsoleUtils") {
prop = "Utils";
}
return require("devtools/shared/webconsole/utils")[prop];
if (isWorker) {
return require("devtools/shared/webconsole/worker-utils")[prop];
} else {
return require("devtools/shared/webconsole/utils")[prop];
}
}.bind(null, name),
configurable: true,
enumerable: true
@ -556,6 +543,11 @@ WebConsoleActor.prototype =
*/
onStartListeners: function WCA_onStartListeners(aRequest)
{
// XXXworkers: Not handling the Console API yet for workers (Bug 1209353).
if (isWorker) {
aRequest.listeners = [];
}
let startedListeners = [];
let window = !this.parentActor.isRootActor ? this.window : null;
let appId = null;
@ -849,8 +841,12 @@ WebConsoleActor.prototype =
} else if ("throw" in evalResult) {
let error = evalResult.throw;
errorGrip = this.createValueGrip(error);
errorMessage = error && (typeof error === "object")
? error.unsafeDereference().toString()
// XXXworkers: Calling unsafeDereference() returns an object with no
// toString method in workers. See Bug 1215120.
let unsafeDereference = error && (typeof error === "object") &&
error.unsafeDereference();
errorMessage = unsafeDereference && unsafeDereference.toString
? unsafeDereference.toString()
: "" + error;
}
}
@ -899,8 +895,8 @@ WebConsoleActor.prototype =
environment = frame.environment;
}
else {
Cu.reportError("Web Console Actor: the frame actor was not found: " +
frameActorId);
DevToolsUtils.reportException("onAutocomplete",
Error("The frame actor was not found: " + frameActorId));
}
}
// This is the general case (non-paused debugger)
@ -1043,9 +1039,13 @@ WebConsoleActor.prototype =
}
for (let name in helpers.sandbox) {
let desc = Object.getOwnPropertyDescriptor(helpers.sandbox, name);
maybeExport(desc, 'get');
maybeExport(desc, 'set');
maybeExport(desc, 'value');
// Workers don't have access to Cu so won't be able to exportFunction.
if (!isWorker) {
maybeExport(desc, 'get');
maybeExport(desc, 'set');
maybeExport(desc, 'value');
}
if (desc.value) {
// Make sure the helpers can be used during eval.
desc.value = aDebuggerGlobal.makeDebuggeeValue(desc.value);
@ -1138,8 +1138,8 @@ WebConsoleActor.prototype =
frame = frameActor.frame;
}
else {
Cu.reportError("Web Console Actor: the frame actor was not found: " +
aOptions.frameActor);
DevToolsUtils.reportException("evalWithDebugger",
Error("The frame actor was not found: " + aOptions.frameActor));
}
}

View File

@ -42,6 +42,7 @@ WorkerActor.prototype = {
form: function () {
return {
actor: this.actorID,
consoleActor: this._consoleActor,
url: this._dbg.url,
type: this._dbg.type
};
@ -87,13 +88,15 @@ WorkerActor.prototype = {
return DebuggerServer.connectToWorker(
this.conn, this._dbg, this.actorID, request.options
).then(({ threadActor, transport }) => {
).then(({ threadActor, transport, consoleActor }) => {
this._threadActor = threadActor;
this._transport = transport;
this._consoleActor = consoleActor;
return {
type: "connected",
threadActor: this._threadActor
threadActor: this._threadActor,
consoleActor: this._consoleActor
};
}, (error) => {
return { error: error.toString() };

View File

@ -833,7 +833,7 @@ var DebuggerServer = {
// Steps 3-5 are performed on the worker thread (see worker.js).
// Step 6: Wait for a response from the worker debugger.
// Step 6: Wait for a connection response from the worker debugger.
let listener = {
onClose: () => {
aDbg.removeListener(listener);
@ -843,19 +843,12 @@ var DebuggerServer = {
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") {
if (packet.type !== "connected" || packet.id !== aId) {
return;
}
// The initial connection packet has been received, don't
// need to listen any longer
aDbg.removeListener(listener);
// Step 7: Create a transport for the connection to the worker.
@ -887,7 +880,8 @@ var DebuggerServer = {
aConnection.setForwarding(aId, transport);
resolve({
threadActor: message.from,
threadActor: packet.threadActor,
consoleActor: packet.consoleActor,
transport: transport
});
}

View File

@ -24,6 +24,7 @@ loadSubScript("resource://devtools/shared/worker/loader.js");
var Promise = worker.require("promise");
var { ActorPool } = worker.require("devtools/server/actors/common");
var { ThreadActor } = worker.require("devtools/server/actors/script");
var { WebConsoleActor } = worker.require("devtools/server/actors/webconsole");
var { TabSources } = worker.require("devtools/server/actors/utils/TabSources");
var makeDebugger = worker.require("devtools/server/actors/utils/make-debugger");
var { DebuggerServer } = worker.require("devtools/server/main");
@ -54,7 +55,7 @@ this.addEventListener("message", function (event) {
let sources = null;
let actor = new ThreadActor({
let parent = {
makeDebugger: makeDebugger.bind(null, {
findDebuggees: () => {
return [this.global];
@ -67,21 +68,28 @@ this.addEventListener("message", function (event) {
get sources() {
if (sources === null) {
sources = new TabSources(actor);
sources = new TabSources(threadActor);
}
return sources;
}
}, global);
},
pool.addActor(actor);
window: global
};
// 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({});
let threadActor = new ThreadActor(parent, global);
pool.addActor(threadActor);
let consoleActor = new WebConsoleActor(connection, parent);
pool.addActor(consoleActor);
// Step 5: Send a response packet to the parent to notify
// it that a connection has been established.
postMessage(JSON.stringify({
type: "connected",
id: packet.id,
threadActor: threadActor.actorID,
consoleActor: consoleActor.actorID,
}));
break;
case "disconnect":

View File

@ -1391,20 +1391,36 @@ WorkerClient.prototype = {
DevToolsUtils.executeSoon(() => aOnResponse({
type: "connected",
threadActor: this.thread._actor,
consoleActor: this.consoleActor,
}, this.thread));
return;
}
// The connect call on server doesn't attach the thread as of version 44.
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);
}, (connectReponse) => {
if (connectReponse.error) {
aOnResponse(connectReponse, null);
return;
}
aOnResponse(aResponse, this.thread);
this.request({
to: connectReponse.threadActor,
type: "attach"
}, (attachResponse) => {
if (attachResponse.error) {
aOnResponse(attachResponse, null);
}
this.thread = new ThreadClient(this, connectReponse.threadActor);
this.consoleActor = connectReponse.consoleActor;
this.client.registerClient(this.thread);
aOnResponse(connectReponse, this.thread);
});
});
},

View File

@ -296,6 +296,13 @@ WebConsoleClient.prototype = {
* Handler for the actors's unsolicited evaluationResult packet.
*/
onEvaluationResult: function(aNotification, aPacket) {
// The client on the main thread can receive notification packets from
// multiple webconsole actors: the one on the main thread and the ones
// on worker threads. So make sure we should be handling this request.
if (aPacket.from !== this._actor) {
return;
}
// Find the associated callback based on this ID, and fire it.
// In a sync evaluation, this would have already been called in
// direct response to the client.request function.

View File

@ -15,4 +15,5 @@ DevToolsModules(
'server-logger-monitor.js',
'server-logger.js',
'utils.js',
'worker-utils.js',
)

View File

@ -0,0 +1,13 @@
// XXXworkers This file is loaded on the server side for worker debugging.
// Since the server is running in the worker thread, it doesn't
// have access to Services / Components. This functionality
// is stubbed out to prevent errors, and will need to implemented
// for Bug 1209353.
exports.Utils = { l10n: function() {} };
exports.ConsoleServiceListener = function() {};
exports.ConsoleAPIListener = function() {};
exports.addWebConsoleCommands = function() {};
exports.JSPropertyProvider = function() {};
exports.ConsoleReflowListener = function() {};
exports.CONSOLE_WORKER_IDS = [];

View File

@ -442,8 +442,12 @@ var {
let scope = this;
let xpcInspector = {
get eventLoopNestLevel() {
return requestors.length;
},
get lastNestRequestor() {
return requestors.length === 0 ? null : requestors[0];
return requestors.length === 0 ? null : requestors[requestors.length - 1];
},
enterNestedEventLoop: function (requestor) {