Bug 1164632 - use new worker helpers in debugger for pretty-printing r=jsantell

This commit is contained in:
James Long 2015-05-19 12:03:32 -04:00
parent 56658777f5
commit b193c8c44e
5 changed files with 184 additions and 201 deletions

View File

@ -49,6 +49,7 @@ const Telemetry = require("devtools/shared/telemetry");
const Editor = require("devtools/sourceeditor/editor");
const TargetFactory = require("devtools/framework/target").TargetFactory;
const EventEmitter = require("devtools/toolkit/event-emitter");
const {DevToolsWorker} = require("devtools/toolkit/shared/worker");
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -650,12 +651,11 @@ var Scratchpad = {
*/
get prettyPrintWorker() {
if (!this._prettyPrintWorker) {
this._prettyPrintWorker = new ChromeWorker(
"resource://gre/modules/devtools/server/actors/pretty-print-worker.js");
this._prettyPrintWorker.addEventListener("error", ({ message, filename, lineno }) => {
DevToolsUtils.reportException(message + " @ " + filename + ":" + lineno);
}, false);
this._prettyPrintWorker = new DevToolsWorker(
"resource://gre/modules/devtools/server/actors/pretty-print-worker.js",
{ name: 'pretty-print',
verbose: DevToolsUtils.dumpn.wantLogging }
);
}
return this._prettyPrintWorker;
},
@ -670,34 +670,17 @@ var Scratchpad = {
prettyPrint: function SP_prettyPrint() {
const uglyText = this.getText();
const tabsize = Services.prefs.getIntPref(TAB_SIZE);
const id = Math.random();
const deferred = promise.defer();
const onReply = ({ data }) => {
if (data.id !== id) {
return;
}
this.prettyPrintWorker.removeEventListener("message", onReply, false);
if (data.error) {
let errorString = DevToolsUtils.safeErrorString(data.error);
this.writeAsErrorComment({ exception: errorString });
deferred.reject(errorString);
} else {
this.editor.setText(data.code);
deferred.resolve(data.code);
}
};
this.prettyPrintWorker.addEventListener("message", onReply, false);
this.prettyPrintWorker.postMessage({
id: id,
return this.prettyPrintWorker.performTask("pretty-print", {
url: "(scratchpad)",
indent: tabsize,
source: uglyText
}).then(data => {
this.editor.setText(data.code);
}).then(null, error => {
this.writeAsErrorComment({ exception: error });
throw error;
});
return deferred.promise;
},
/**
@ -1829,7 +1812,7 @@ var Scratchpad = {
}
if (this._prettyPrintWorker) {
this._prettyPrintWorker.terminate();
this._prettyPrintWorker.destroy();
this._prettyPrintWorker = null;
}

View File

@ -27,27 +27,24 @@
* { id, error }
*/
importScripts("resource://gre/modules/devtools/shared/worker-helper.js");
importScripts("resource://gre/modules/devtools/acorn/acorn.js");
importScripts("resource://gre/modules/devtools/source-map.js");
importScripts("resource://gre/modules/devtools/pretty-fast.js");
self.onmessage = (event) => {
const { data: { id, url, indent, source } } = event;
workerHelper.createTask(self, "pretty-print", ({ url, indent, source }) => {
try {
const prettified = prettyFast(source, {
url: url,
indent: " ".repeat(indent)
});
self.postMessage({
id: id,
return {
code: prettified.code,
mappings: prettified.map._mappings
});
} catch (e) {
self.postMessage({
id: id,
error: e.message + "\n" + e.stack
});
};
}
};
catch(e) {
return new Error(e.message + "\n" + e.stack);
}
});

View File

@ -17,6 +17,7 @@ const promise = require("promise");
const PromiseDebugging = require("PromiseDebugging");
const xpcInspector = require("xpcInspector");
const ScriptStore = require("./utils/ScriptStore");
const {DevToolsWorker} = Cu.import("resource://gre/modules/devtools/shared/worker.js", {});
const { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");
@ -532,35 +533,15 @@ ThreadActor.prototype = {
_prettyPrintWorker: null,
get prettyPrintWorker() {
if (!this._prettyPrintWorker) {
this._prettyPrintWorker = new ChromeWorker(
"resource://gre/modules/devtools/server/actors/pretty-print-worker.js");
this._prettyPrintWorker.addEventListener(
"error", this._onPrettyPrintError, false);
if (dumpn.wantLogging) {
this._prettyPrintWorker.addEventListener("message", this._onPrettyPrintMsg, false);
const postMsg = this._prettyPrintWorker.postMessage;
this._prettyPrintWorker.postMessage = data => {
dumpn("Sending message to prettyPrintWorker: "
+ JSON.stringify(data, null, 2) + "\n");
return postMsg.call(this._prettyPrintWorker, data);
};
}
this._prettyPrintWorker = new DevToolsWorker(
"resource://gre/modules/devtools/server/actors/pretty-print-worker.js",
{ name: "pretty-print",
verbose: dumpn.wantLogging }
);
}
return this._prettyPrintWorker;
},
_onPrettyPrintError: function ({ message, filename, lineno }) {
reportError(new Error(message + " @ " + filename + ":" + lineno));
},
_onPrettyPrintMsg: function ({ data }) {
dumpn("Received message from prettyPrintWorker: "
+ JSON.stringify(data, null, 2) + "\n");
},
/**
* Keep track of all of the nested event loops we use to pause the debuggee
* when we hit a breakpoint/debugger statement/etc in one place so we can
@ -623,11 +604,7 @@ ThreadActor.prototype = {
this._threadLifetimePool = null;
if (this._prettyPrintWorker) {
this._prettyPrintWorker.removeEventListener(
"error", this._onPrettyPrintError, false);
this._prettyPrintWorker.removeEventListener(
"message", this._onPrettyPrintMsg, false);
this._prettyPrintWorker.terminate();
this._prettyPrintWorker.destroy();
this._prettyPrintWorker = null;
}
@ -2571,31 +2548,11 @@ SourceActor.prototype = {
*/
_sendToPrettyPrintWorker: function (aIndent) {
return ({ content }) => {
const deferred = promise.defer();
const id = Math.random();
const onReply = ({ data }) => {
if (data.id !== id) {
return;
}
this.prettyPrintWorker.removeEventListener("message", onReply, false);
if (data.error) {
deferred.reject(new Error(data.error));
} else {
deferred.resolve(data);
}
};
this.prettyPrintWorker.addEventListener("message", onReply, false);
this.prettyPrintWorker.postMessage({
id: id,
return this.prettyPrintWorker.performTask("pretty-print", {
url: this.url,
indent: aIndent,
source: content
});
return deferred.promise;
})
};
},

View File

@ -1,100 +1,112 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
(function (root, factory) {
"use strict";
/**
* This file is to only be included by ChromeWorkers. This exposes
* a `createTask` function to workers to register tasks for communication
* back to `devtools/toolkit/shared/worker`.
*
* Tasks can be send their responses via a return value, either a primitive
* or a promise.
*
* createTask(self, "average", function (data) {
* return data.reduce((sum, val) => sum + val, 0) / data.length;
* });
*
* createTask(self, "average", function (data) {
* return new Promise((resolve, reject) => {
* resolve(data.reduce((sum, val) => sum + val, 0) / data.length);
* });
* });
*
*
* Errors:
*
* Returning an Error value, or if the returned promise is rejected, this
* propagates to the DevToolsWorker as a rejected promise. If an error is
* thrown in a synchronous function, that error is also propagated.
*/
if (typeof define === "function" && define.amd) {
define(factory);
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.workerHelper = factory();
}
}(this, function () {
"use strict";
/**
* Takes a worker's `self` object, a task name, and a function to
* be called when that task is called. The task is called with the
* passed in data as the first argument
*
* @param {object} self
* @param {string} name
* @param {function} fn
*/
function createTask (self, name, fn) {
// Store a hash of task name to function on the Worker
if (!self._tasks) {
self._tasks = {};
/**
* This file is to only be included by ChromeWorkers. This exposes
* a `createTask` function to workers to register tasks for communication
* back to `devtools/toolkit/shared/worker`.
*
* Tasks can be send their responses via a return value, either a primitive
* or a promise.
*
* createTask(self, "average", function (data) {
* return data.reduce((sum, val) => sum + val, 0) / data.length;
* });
*
* createTask(self, "average", function (data) {
* return new Promise((resolve, reject) => {
* resolve(data.reduce((sum, val) => sum + val, 0) / data.length);
* });
* });
*
*
* Errors:
*
* Returning an Error value, or if the returned promise is rejected, this
* propagates to the DevToolsWorker as a rejected promise. If an error is
* thrown in a synchronous function, that error is also propagated.
*/
/**
* Takes a worker's `self` object, a task name, and a function to
* be called when that task is called. The task is called with the
* passed in data as the first argument
*
* @param {object} self
* @param {string} name
* @param {function} fn
*/
function createTask (self, name, fn) {
// Store a hash of task name to function on the Worker
if (!self._tasks) {
self._tasks = {};
}
// Create the onmessage handler if not yet created.
if (!self.onmessage) {
self.onmessage = createHandler(self);
}
// Store the task on the worker.
self._tasks[name] = fn;
}
// Create the onmessage handler if not yet created.
if (!self.onmessage) {
self.onmessage = createHandler(self);
/**
* Creates the `self.onmessage` handler for a Worker.
*
* @param {object} self
* @return {function}
*/
function createHandler (self) {
return function (e) {
let { id, task, data } = e.data;
let taskFn = self._tasks[task];
if (!taskFn) {
self.postMessage({ id, error: `Task "${task}" not found in worker.` });
return;
}
try {
let results;
handleResponse(taskFn(data));
} catch (e) {
handleError(e);
}
function handleResponse (response) {
// If a promise
if (response && typeof response.then === "function") {
response.then(val => self.postMessage({ id, response: val }), handleError);
}
// If an error object
else if (response instanceof Error) {
handleError(response);
}
// If anything else
else {
self.postMessage({ id, response });
}
}
function handleError (e="Error") {
self.postMessage({ id, error: e.message || e });
}
}
}
// Store the task on the worker.
self._tasks[name] = fn;
}
exports.createTask = createTask;
/**
* Creates the `self.onmessage` handler for a Worker.
*
* @param {object} self
* @return {function}
*/
function createHandler (self) {
return function (e) {
let { id, task, data } = e.data;
let taskFn = self._tasks[task];
if (!taskFn) {
self.postMessage({ id, error: `Task "${task}" not found in worker.` });
return;
}
try {
let results;
handleResponse(taskFn(data));
} catch (e) {
handleError(e);
}
function handleResponse (response) {
// If a promise
if (response && typeof response.then === "function") {
response.then(val => self.postMessage({ id, response: val }), handleError);
}
// If an error object
else if (response instanceof Error) {
handleError(response);
}
// If anything else
else {
self.postMessage({ id, response });
}
}
function handleError (e="Error") {
self.postMessage({ id, error: e.message || e });
}
}
}
return { createTask: createTask };
}.bind(this)));

View File

@ -5,18 +5,22 @@
(function (factory) { // Module boilerplate
if (this.module && module.id.indexOf("worker") >= 0) { // require
const { Cc, Ci, ChromeWorker } = require("chrome");
factory.call(this, require, exports, module, { Cc, Ci }, ChromeWorker);
const { Cc, Ci, Cu, ChromeWorker } = require("chrome");
const dumpn = require("devtools/toolkit/DevToolsUtils").dumpn;
factory.call(this, require, exports, module, { Cc, Ci, Cu }, ChromeWorker, dumpn);
} else { // Cu.import
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
this.isWorker = false;
this.Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
this.console = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).console;
factory.call(this, devtools.require, this, { exports: this }, { Cc, Ci }, ChromeWorker);
this.EXPORTED_SYMBOLS = ["DevToolsWorker"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
this.isWorker = false;
this.Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
this.console = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).console;
factory.call(
this, devtools.require, this, { exports: this },
{ Cc, Ci, Cu }, ChromeWorker, null
);
this.EXPORTED_SYMBOLS = ["DevToolsWorker"];
}
}).call(this, function (require, exports, module, { Ci, Cc }, ChromeWorker ) {
}).call(this, function (require, exports, module, { Ci, Cc }, ChromeWorker, dumpn) {
let MESSAGE_COUNTER = 0;
@ -29,9 +33,18 @@ let MESSAGE_COUNTER = 0;
*
* @param {string} url
* The URL of the worker.
* @param Object opts
* An option with the following optional fields:
* - name: a name that will be printed with logs
* - verbose: log incoming and outgoing messages
*/
function DevToolsWorker (url) {
function DevToolsWorker (url, opts) {
opts = opts || {};
this._worker = new ChromeWorker(url);
this._verbose = opts.verbose;
this._name = opts.name;
this._worker.addEventListener("error", this.onError, false);
}
exports.DevToolsWorker = DevToolsWorker;
@ -46,16 +59,31 @@ exports.DevToolsWorker = DevToolsWorker;
* Data to be passed into the task implemented by the worker.
* @return {Promise}
*/
DevToolsWorker.prototype.performTask = function DevToolsWorkerPerformTask (task, data) {
DevToolsWorker.prototype.performTask = function (task, data) {
if (this._destroyed) {
return Promise.reject("Cannot call performTask on a destroyed DevToolsWorker");
}
let worker = this._worker;
let id = ++MESSAGE_COUNTER;
worker.postMessage({ task, id, data });
let payload = { task, id, data };
if(this._verbose && dumpn) {
dumpn("Sending message to worker" +
(this._name ? (" (" + this._name + ")") : "" ) +
": " +
JSON.stringify(payload, null, 2));
}
worker.postMessage(payload);
return new Promise((resolve, reject) => {
let listener = ({ data }) => {
if(this._verbose && dumpn) {
dumpn("Received message from worker" +
(this._name ? (" (" + this._name + ")") : "" ) +
": " +
JSON.stringify(data, null, 2));
}
return new Promise(function (resolve, reject) {
worker.addEventListener("message", function listener({ data }) {
if (data.id !== id) {
return;
}
@ -65,19 +93,25 @@ DevToolsWorker.prototype.performTask = function DevToolsWorkerPerformTask (task,
} else {
resolve(data.response);
}
});
};
worker.addEventListener("message", listener);
});
}
/**
* Terminates the underlying worker. Use when no longer needing the worker.
*/
DevToolsWorker.prototype.destroy = function DevToolsWorkerDestroy () {
DevToolsWorker.prototype.destroy = function () {
this._worker.terminate();
this._worker = null;
this._destroyed = true;
};
DevToolsWorker.prototype.onError = function({ message, filename, lineno }) {
Cu.reportError(new Error(message + " @ " + filename + ":" + lineno));
}
/**
* Takes a function and returns a Worker-wrapped version of the same function.
* Returns a promise upon resolution.