From c58ddfca92626e8967b491fa164bbf56b1feea8a Mon Sep 17 00:00:00 2001 From: James Long Date: Tue, 19 May 2015 12:03:32 -0400 Subject: [PATCH] Bug 1164632 - use new worker helpers in debugger for pretty-printing r=jsantell --- browser/devtools/scratchpad/scratchpad.js | 43 ++-- .../server/actors/pretty-print-worker.js | 19 +- toolkit/devtools/server/actors/script.js | 61 +----- toolkit/devtools/shared/worker-helper.js | 194 ++++++++++-------- toolkit/devtools/shared/worker.js | 68 ++++-- 5 files changed, 184 insertions(+), 201 deletions(-) diff --git a/browser/devtools/scratchpad/scratchpad.js b/browser/devtools/scratchpad/scratchpad.js index b0e7c43a4366..254aba41d215 100644 --- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -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; } diff --git a/toolkit/devtools/server/actors/pretty-print-worker.js b/toolkit/devtools/server/actors/pretty-print-worker.js index 6b5b26d1989a..a6b7af6e7e5e 100644 --- a/toolkit/devtools/server/actors/pretty-print-worker.js +++ b/toolkit/devtools/server/actors/pretty-print-worker.js @@ -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); + } +}); diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index 33f4728aef18..a4f9f732aae3 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -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; + }) }; }, diff --git a/toolkit/devtools/shared/worker-helper.js b/toolkit/devtools/shared/worker-helper.js index 8d019ea2fcbd..a8896b0aad02 100644 --- a/toolkit/devtools/shared/worker-helper.js +++ b/toolkit/devtools/shared/worker-helper.js @@ -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))); diff --git a/toolkit/devtools/shared/worker.js b/toolkit/devtools/shared/worker.js index e211ddf07047..677bfffe9f2f 100644 --- a/toolkit/devtools/shared/worker.js +++ b/toolkit/devtools/shared/worker.js @@ -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.