Bug 1259603 - Use ConsoleEvents for cached messages and observing messages when webconsole actor is running;r=ejpbruel
authorBrian Grinstead <bgrinstead@mozilla.com>
Wed, 30 Nov 2016 10:32:48 -0800
changeset 324857 6c94c769aaef9042b594ebbdec5640c7582d3c17
parent 324856 e274953486641dbfe3425d2a855537da6aae0304
child 324858 8c4f3476db84e9e779db63d6460a8dc6baa1c4f9
push id84525
push userphilringnalda@gmail.com
push dateThu, 01 Dec 2016 03:22:09 +0000
treeherdermozilla-inbound@b1d875f4c673 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersejpbruel
bugs1259603
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1259603 - Use ConsoleEvents for cached messages and observing messages when webconsole actor is running;r=ejpbruel MozReview-Commit-ID: E3oG451qPe9
devtools/client/shared/developer-toolbar.js
devtools/client/webconsole/webconsole-connection-proxy.js
devtools/server/actors/addon.js
devtools/server/actors/utils/moz.build
devtools/server/actors/utils/webconsole-listeners.js
devtools/server/actors/utils/webconsole-utils.js
devtools/server/actors/utils/webconsole-worker-listeners.js
devtools/server/actors/utils/webconsole-worker-utils.js
devtools/server/actors/webconsole.js
devtools/server/worker.js
devtools/shared/tests/unit/test_console_filtering.js
devtools/shared/webconsole/test/chrome.ini
devtools/shared/webconsole/test/common.js
devtools/shared/webconsole/test/console-test-worker.js
devtools/shared/webconsole/test/test_console_worker.html
devtools/shared/worker/loader.js
toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -21,17 +21,17 @@ const { PluralForm } = require("devtools
 
 loader.lazyGetter(this, "prefBranch", function () {
   return Services.prefs.getBranch(null)
                     .QueryInterface(Ci.nsIPrefBranch2);
 });
 
 loader.lazyRequireGetter(this, "gcliInit", "devtools/shared/gcli/commands/index");
 loader.lazyRequireGetter(this, "util", "gcli/util/util");
-loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/server/actors/utils/webconsole-utils", true);
+loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/server/actors/utils/webconsole-listeners", true);
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
 loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants");
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
 /**
  * A collection of utilities to help working with commands
  */
--- a/devtools/client/webconsole/webconsole-connection-proxy.js
+++ b/devtools/client/webconsole/webconsole-connection-proxy.js
@@ -139,28 +139,25 @@ WebConsoleConnectionProxy.prototype = {
       this._connectTimer.cancel();
       this._connectTimer = null;
     }, () => {
       this._connectTimer = null;
     });
 
     let client = this.client = this.target.client;
 
-    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);
-    }
+    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);
 
     this._consoleActor = this.target.form.consoleActor;
     if (this.target.isTabActor) {
       let tab = this.target.form;
       this.webConsoleFrame.onLocationChange(tab.url, tab.title);
     }
--- a/devtools/server/actors/addon.js
+++ b/devtools/server/actors/addon.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 var { Ci, Cu } = require("chrome");
 var Services = require("Services");
 var { ActorPool } = require("devtools/server/actors/common");
 var { TabSources } = require("./utils/TabSources");
 var makeDebugger = require("./utils/make-debugger");
-var { ConsoleAPIListener } = require("devtools/server/actors/utils/webconsole-utils");
+var { ConsoleAPIListener } = require("devtools/server/actors/utils/webconsole-listeners");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { assert, update } = DevToolsUtils;
 
 loader.lazyRequireGetter(this, "AddonThreadActor", "devtools/server/actors/script", true);
 loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
 loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
 loader.lazyRequireGetter(this, "WebConsoleActor", "devtools/server/actors/webconsole", true);
 
--- a/devtools/server/actors/utils/moz.build
+++ b/devtools/server/actors/utils/moz.build
@@ -9,11 +9,12 @@ DevToolsModules(
     'audionodes.json',
     'automation-timeline.js',
     'css-grid-utils.js',
     'make-debugger.js',
     'map-uri-to-addon-id.js',
     'stack.js',
     'TabSources.js',
     'walker-search.js',
+    'webconsole-listeners.js',
     'webconsole-utils.js',
-    'webconsole-worker-utils.js',
+    'webconsole-worker-listeners.js',
 )
copy from devtools/server/actors/utils/webconsole-utils.js
copy to devtools/server/actors/utils/webconsole-listeners.js
--- a/devtools/server/actors/utils/webconsole-utils.js
+++ b/devtools/server/actors/utils/webconsole-listeners.js
@@ -1,215 +1,20 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
-/* 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";
 
 const {Cc, Ci, Cu, components} = require("chrome");
 const {isWindowIncluded} = require("devtools/shared/layout/utils");
 const Services = require("Services");
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
-
-// TODO: Bug 842672 - browser/ imports modules from toolkit/.
-// Note that these are only used in WebConsoleCommands, see $0 and pprint().
-loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
+const {CONSOLE_WORKER_IDS, WebConsoleUtils} = require("devtools/server/actors/utils/webconsole-utils");
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "swm",
                                    "@mozilla.org/serviceworkers/manager;1",
                                    "nsIServiceWorkerManager");
 
-const CONSOLE_WORKER_IDS = exports.CONSOLE_WORKER_IDS = [
-  "SharedWorker",
-  "ServiceWorker",
-  "Worker"
-];
-
-var WebConsoleUtils = {
-
-  /**
-   * Given a message, return one of CONSOLE_WORKER_IDS if it matches
-   * one of those.
-   *
-   * @return string
-   */
-  getWorkerType: function (message) {
-    let id = message ? message.innerID : null;
-    return CONSOLE_WORKER_IDS[CONSOLE_WORKER_IDS.indexOf(id)] || null;
-  },
-
-  /**
-   * Clone an object.
-   *
-   * @param object object
-   *        The object you want cloned.
-   * @param boolean recursive
-   *        Tells if you want to dig deeper into the object, to clone
-   *        recursively.
-   * @param function [filter]
-   *        Optional, filter function, called for every property. Three
-   *        arguments are passed: key, value and object. Return true if the
-   *        property should be added to the cloned object. Return false to skip
-   *        the property.
-   * @return object
-   *         The cloned object.
-   */
-  cloneObject: function (object, recursive, filter) {
-    if (typeof object != "object") {
-      return object;
-    }
-
-    let temp;
-
-    if (Array.isArray(object)) {
-      temp = [];
-      Array.forEach(object, function (value, index) {
-        if (!filter || filter(index, value, object)) {
-          temp.push(recursive ? WebConsoleUtils.cloneObject(value) : value);
-        }
-      });
-    } else {
-      temp = {};
-      for (let key in object) {
-        let value = object[key];
-        if (object.hasOwnProperty(key) &&
-            (!filter || filter(key, value, object))) {
-          temp[key] = recursive ? WebConsoleUtils.cloneObject(value) : value;
-        }
-      }
-    }
-
-    return temp;
-  },
-
-  /**
-   * Gets the ID of the inner window of this DOM window.
-   *
-   * @param nsIDOMWindow window
-   * @return integer
-   *         Inner ID for the given window.
-   */
-  getInnerWindowId: function (window) {
-    return window.QueryInterface(Ci.nsIInterfaceRequestor)
-             .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
-  },
-
-  /**
-   * Recursively gather a list of inner window ids given a
-   * top level window.
-   *
-   * @param nsIDOMWindow window
-   * @return Array
-   *         list of inner window ids.
-   */
-  getInnerWindowIDsForFrames: function (window) {
-    let innerWindowID = this.getInnerWindowId(window);
-    let ids = [innerWindowID];
-
-    if (window.frames) {
-      for (let i = 0; i < window.frames.length; i++) {
-        let frame = window.frames[i];
-        ids = ids.concat(this.getInnerWindowIDsForFrames(frame));
-      }
-    }
-
-    return ids;
-  },
-
-  /**
-   * Get the property descriptor for the given object.
-   *
-   * @param object object
-   *        The object that contains the property.
-   * @param string prop
-   *        The property you want to get the descriptor for.
-   * @return object
-   *         Property descriptor.
-   */
-  getPropertyDescriptor: function (object, prop) {
-    let desc = null;
-    while (object) {
-      try {
-        if ((desc = Object.getOwnPropertyDescriptor(object, prop))) {
-          break;
-        }
-      } catch (ex) {
-        // Native getters throw here. See bug 520882.
-        // null throws TypeError.
-        if (ex.name != "NS_ERROR_XPC_BAD_CONVERT_JS" &&
-            ex.name != "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO" &&
-            ex.name != "TypeError") {
-          throw ex;
-        }
-      }
-
-      try {
-        object = Object.getPrototypeOf(object);
-      } catch (ex) {
-        if (ex.name == "TypeError") {
-          return desc;
-        }
-        throw ex;
-      }
-    }
-    return desc;
-  },
-
-  /**
-   * Create a grip for the given value. If the value is an object,
-   * an object wrapper will be created.
-   *
-   * @param mixed value
-   *        The value you want to create a grip for, before sending it to the
-   *        client.
-   * @param function objectWrapper
-   *        If the value is an object then the objectWrapper function is
-   *        invoked to give us an object grip. See this.getObjectGrip().
-   * @return mixed
-   *         The value grip.
-   */
-  createValueGrip: function (value, objectWrapper) {
-    switch (typeof value) {
-      case "boolean":
-        return value;
-      case "string":
-        return objectWrapper(value);
-      case "number":
-        if (value === Infinity) {
-          return { type: "Infinity" };
-        } else if (value === -Infinity) {
-          return { type: "-Infinity" };
-        } else if (Number.isNaN(value)) {
-          return { type: "NaN" };
-        } else if (!value && 1 / value === -Infinity) {
-          return { type: "-0" };
-        }
-        return value;
-      case "undefined":
-        return { type: "undefined" };
-      case "object":
-        if (value === null) {
-          return { type: "null" };
-        }
-        // Fall through.
-      case "function":
-        return objectWrapper(value);
-      default:
-        console.error("Failed to provide a grip for value of " + typeof value
-                      + ": " + value);
-        return null;
-    }
-  },
-};
-
-exports.Utils = WebConsoleUtils;
-
 // The page errors listener
 
 /**
  * The nsIConsoleService listener. This is used to send all of the console
  * messages (JavaScript, CSS and more) to the remote Web Console instance.
  *
  * @constructor
  * @param nsIDOMWindow [window]
@@ -553,435 +358,16 @@ ConsoleAPIListener.prototype =
    */
   destroy: function () {
     Services.obs.removeObserver(this, "console-api-log-event");
     this.window = this.owner = null;
   },
 };
 
 /**
- * WebConsole commands manager.
- *
- * Defines a set of functions /variables ("commands") that are available from
- * the Web Console but not from the web page.
- *
- */
-var WebConsoleCommands = {
-  _registeredCommands: new Map(),
-  _originalCommands: new Map(),
-
-  /**
-   * @private
-   * Reserved for built-in commands. To register a command from the code of an
-   * add-on, see WebConsoleCommands.register instead.
-   *
-   * @see WebConsoleCommands.register
-   */
-  _registerOriginal: function (name, command) {
-    this.register(name, command);
-    this._originalCommands.set(name, this.getCommand(name));
-  },
-
-  /**
-   * Register a new command.
-   * @param {string} name The command name (exemple: "$")
-   * @param {(function|object)} command The command to register.
-   *  It can be a function so the command is a function (like "$()"),
-   *  or it can also be a property descriptor to describe a getter / value (like
-   *  "$0").
-   *
-   *  The command function or the command getter are passed a owner object as
-   *  their first parameter (see the example below).
-   *
-   *  Note that setters don't work currently and "enumerable" and "configurable"
-   *  are forced to true.
-   *
-   * @example
-   *
-   *   WebConsoleCommands.register("$", function JSTH_$(owner, selector)
-   *   {
-   *     return owner.window.document.querySelector(selector);
-   *   });
-   *
-   *   WebConsoleCommands.register("$0", {
-   *     get: function(owner) {
-   *       return owner.makeDebuggeeValue(owner.selectedNode);
-   *     }
-   *   });
-   */
-  register: function (name, command) {
-    this._registeredCommands.set(name, command);
-  },
-
-  /**
-   * Unregister a command.
-   *
-   * If the command being unregister overrode a built-in command,
-   * the latter is restored.
-   *
-   * @param {string} name The name of the command
-   */
-  unregister: function (name) {
-    this._registeredCommands.delete(name);
-    if (this._originalCommands.has(name)) {
-      this.register(name, this._originalCommands.get(name));
-    }
-  },
-
-  /**
-   * Returns a command by its name.
-   *
-   * @param {string} name The name of the command.
-   *
-   * @return {(function|object)} The command.
-   */
-  getCommand: function (name) {
-    return this._registeredCommands.get(name);
-  },
-
-  /**
-   * Returns true if a command is registered with the given name.
-   *
-   * @param {string} name The name of the command.
-   *
-   * @return {boolean} True if the command is registered.
-   */
-  hasCommand: function (name) {
-    return this._registeredCommands.has(name);
-  },
-};
-
-exports.WebConsoleCommands = WebConsoleCommands;
-
-/*
- * Built-in commands.
-  *
-  * A list of helper functions used by Firebug can be found here:
-  *   http://getfirebug.com/wiki/index.php/Command_Line_API
- */
-
-/**
- * Find a node by ID.
- *
- * @param string id
- *        The ID of the element you want.
- * @return nsIDOMNode or null
- *         The result of calling document.querySelector(selector).
- */
-WebConsoleCommands._registerOriginal("$", function (owner, selector) {
-  return owner.window.document.querySelector(selector);
-});
-
-/**
- * Find the nodes matching a CSS selector.
- *
- * @param string selector
- *        A string that is passed to window.document.querySelectorAll.
- * @return nsIDOMNodeList
- *         Returns the result of document.querySelectorAll(selector).
- */
-WebConsoleCommands._registerOriginal("$$", function (owner, selector) {
-  let nodes = owner.window.document.querySelectorAll(selector);
-
-  // Calling owner.window.Array.from() doesn't work without accessing the
-  // wrappedJSObject, so just loop through the results instead.
-  let result = new owner.window.Array();
-  for (let i = 0; i < nodes.length; i++) {
-    result.push(nodes[i]);
-  }
-  return result;
-});
-
-/**
- * Returns the result of the last console input evaluation
- *
- * @return object|undefined
- * Returns last console evaluation or undefined
- */
-WebConsoleCommands._registerOriginal("$_", {
-  get: function (owner) {
-    return owner.consoleActor.getLastConsoleInputEvaluation();
-  }
-});
-
-/**
- * Runs an xPath query and returns all matched nodes.
- *
- * @param string xPath
- *        xPath search query to execute.
- * @param [optional] nsIDOMNode context
- *        Context to run the xPath query on. Uses window.document if not set.
- * @return array of nsIDOMNode
- */
-WebConsoleCommands._registerOriginal("$x", function (owner, xPath, context) {
-  let nodes = new owner.window.Array();
-
-  // Not waiving Xrays, since we want the original Document.evaluate function,
-  // instead of anything that's been redefined.
-  let doc = owner.window.document;
-  context = context || doc;
-
-  let results = doc.evaluate(xPath, context, null,
-                             Ci.nsIDOMXPathResult.ANY_TYPE, null);
-  let node;
-  while ((node = results.iterateNext())) {
-    nodes.push(node);
-  }
-
-  return nodes;
-});
-
-/**
- * Returns the currently selected object in the highlighter.
- *
- * @return Object representing the current selection in the
- *         Inspector, or null if no selection exists.
- */
-WebConsoleCommands._registerOriginal("$0", {
-  get: function (owner) {
-    return owner.makeDebuggeeValue(owner.selectedNode);
-  }
-});
-
-/**
- * Clears the output of the WebConsole.
- */
-WebConsoleCommands._registerOriginal("clear", function (owner) {
-  owner.helperResult = {
-    type: "clearOutput",
-  };
-});
-
-/**
- * Clears the input history of the WebConsole.
- */
-WebConsoleCommands._registerOriginal("clearHistory", function (owner) {
-  owner.helperResult = {
-    type: "clearHistory",
-  };
-});
-
-/**
- * Returns the result of Object.keys(object).
- *
- * @param object object
- *        Object to return the property names from.
- * @return array of strings
- */
-WebConsoleCommands._registerOriginal("keys", function (owner, object) {
-  // Need to waive Xrays so we can iterate functions and accessor properties
-  return Cu.cloneInto(Object.keys(Cu.waiveXrays(object)), owner.window);
-});
-
-/**
- * Returns the values of all properties on object.
- *
- * @param object object
- *        Object to display the values from.
- * @return array of string
- */
-WebConsoleCommands._registerOriginal("values", function (owner, object) {
-  let values = [];
-  // Need to waive Xrays so we can iterate functions and accessor properties
-  let waived = Cu.waiveXrays(object);
-  let names = Object.getOwnPropertyNames(waived);
-
-  for (let name of names) {
-    values.push(waived[name]);
-  }
-
-  return Cu.cloneInto(values, owner.window);
-});
-
-/**
- * Opens a help window in MDN.
- */
-WebConsoleCommands._registerOriginal("help", function (owner) {
-  owner.helperResult = { type: "help" };
-});
-
-/**
- * Change the JS evaluation scope.
- *
- * @param DOMElement|string|window window
- *        The window object to use for eval scope. This can be a string that
- *        is used to perform document.querySelector(), to find the iframe that
- *        you want to cd() to. A DOMElement can be given as well, the
- *        .contentWindow property is used. Lastly, you can directly pass
- *        a window object. If you call cd() with no arguments, the current
- *        eval scope is cleared back to its default (the top window).
- */
-WebConsoleCommands._registerOriginal("cd", function (owner, window) {
-  if (!window) {
-    owner.consoleActor.evalWindow = null;
-    owner.helperResult = { type: "cd" };
-    return;
-  }
-
-  if (typeof window == "string") {
-    window = owner.window.document.querySelector(window);
-  }
-  if (window instanceof Ci.nsIDOMElement && window.contentWindow) {
-    window = window.contentWindow;
-  }
-  if (!(window instanceof Ci.nsIDOMWindow)) {
-    owner.helperResult = {
-      type: "error",
-      message: "cdFunctionInvalidArgument"
-    };
-    return;
-  }
-
-  owner.consoleActor.evalWindow = window;
-  owner.helperResult = { type: "cd" };
-});
-
-/**
- * Inspects the passed object. This is done by opening the PropertyPanel.
- *
- * @param object object
- *        Object to inspect.
- */
-WebConsoleCommands._registerOriginal("inspect", function (owner, object) {
-  let dbgObj = owner.makeDebuggeeValue(object);
-  let grip = owner.createValueGrip(dbgObj);
-  owner.helperResult = {
-    type: "inspectObject",
-    input: owner.evalInput,
-    object: grip,
-  };
-});
-
-/**
- * Prints object to the output.
- *
- * @param object object
- *        Object to print to the output.
- * @return string
- */
-WebConsoleCommands._registerOriginal("pprint", function (owner, object) {
-  if (object === null || object === undefined || object === true ||
-      object === false) {
-    owner.helperResult = {
-      type: "error",
-      message: "helperFuncUnsupportedTypeError",
-    };
-    return null;
-  }
-
-  owner.helperResult = { rawOutput: true };
-
-  if (typeof object == "function") {
-    return object + "\n";
-  }
-
-  let output = [];
-
-  let obj = object;
-  for (let name in obj) {
-    let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {};
-    if (desc.get || desc.set) {
-      // TODO: Bug 842672 - toolkit/ imports modules from browser/.
-      let getGrip = VariablesView.getGrip(desc.get);
-      let setGrip = VariablesView.getGrip(desc.set);
-      let getString = VariablesView.getString(getGrip);
-      let setString = VariablesView.getString(setGrip);
-      output.push(name + ":", "  get: " + getString, "  set: " + setString);
-    } else {
-      let valueGrip = VariablesView.getGrip(obj[name]);
-      let valueString = VariablesView.getString(valueGrip);
-      output.push(name + ": " + valueString);
-    }
-  }
-
-  return "  " + output.join("\n  ");
-});
-
-/**
- * Print the String representation of a value to the output, as-is.
- *
- * @param any value
- *        A value you want to output as a string.
- * @return void
- */
-WebConsoleCommands._registerOriginal("print", function (owner, value) {
-  owner.helperResult = { rawOutput: true };
-  if (typeof value === "symbol") {
-    return Symbol.prototype.toString.call(value);
-  }
-  // Waiving Xrays here allows us to see a closer representation of the
-  // underlying object. This may execute arbitrary content code, but that
-  // code will run with content privileges, and the result will be rendered
-  // inert by coercing it to a String.
-  return String(Cu.waiveXrays(value));
-});
-
-/**
- * Copy the String representation of a value to the clipboard.
- *
- * @param any value
- *        A value you want to copy as a string.
- * @return void
- */
-WebConsoleCommands._registerOriginal("copy", function (owner, value) {
-  let payload;
-  try {
-    if (value instanceof Ci.nsIDOMElement) {
-      payload = value.outerHTML;
-    } else if (typeof value == "string") {
-      payload = value;
-    } else {
-      payload = JSON.stringify(value, null, "  ");
-    }
-  } catch (ex) {
-    payload = "/* " + ex + " */";
-  }
-  owner.helperResult = {
-    type: "copyValueToClipboard",
-    value: payload,
-  };
-});
-
-/**
- * (Internal only) Add the bindings to |owner.sandbox|.
- * This is intended to be used by the WebConsole actor only.
-  *
-  * @param object owner
-  *        The owning object.
-  */
-function addWebConsoleCommands(owner) {
-  if (!owner) {
-    throw new Error("The owner is required");
-  }
-  for (let [name, command] of WebConsoleCommands._registeredCommands) {
-    if (typeof command === "function") {
-      owner.sandbox[name] = command.bind(undefined, owner);
-    } else if (typeof command === "object") {
-      let clone = Object.assign({}, command, {
-        // We force the enumerability and the configurability (so the
-        // WebConsoleActor can reconfigure the property).
-        enumerable: true,
-        configurable: true
-      });
-
-      if (typeof command.get === "function") {
-        clone.get = command.get.bind(undefined, owner);
-      }
-      if (typeof command.set === "function") {
-        clone.set = command.set.bind(undefined, owner);
-      }
-
-      Object.defineProperty(owner.sandbox, name, clone);
-    }
-  }
-}
-
-exports.addWebConsoleCommands = addWebConsoleCommands;
-
-/**
  * A ReflowObserver that listens for reflow events from the page.
  * Implements nsIReflowObserver.
  *
  * @constructor
  * @param object window
  *        The window for which we need to track reflow.
  * @param object owner
  *        The listener owner which needs to implement:
--- a/devtools/server/actors/utils/webconsole-utils.js
+++ b/devtools/server/actors/utils/webconsole-utils.js
@@ -2,28 +2,21 @@
 /* vim: set ft= javascript ts=2 et sw=2 tw=80: */
 /* 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";
 
 const {Cc, Ci, Cu, components} = require("chrome");
-const {isWindowIncluded} = require("devtools/shared/layout/utils");
-const Services = require("Services");
-const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 
-// TODO: Bug 842672 - browser/ imports modules from toolkit/.
-// Note that these are only used in WebConsoleCommands, see $0 and pprint().
-loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this,
-                                   "swm",
-                                   "@mozilla.org/serviceworkers/manager;1",
-                                   "nsIServiceWorkerManager");
+// Note that this is only used in WebConsoleCommands, see $0 and pprint().
+if (!isWorker) {
+  loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
+}
 
 const CONSOLE_WORKER_IDS = exports.CONSOLE_WORKER_IDS = [
   "SharedWorker",
   "ServiceWorker",
   "Worker"
 ];
 
 var WebConsoleUtils = {
@@ -198,369 +191,17 @@ var WebConsoleUtils = {
       default:
         console.error("Failed to provide a grip for value of " + typeof value
                       + ": " + value);
         return null;
     }
   },
 };
 
-exports.Utils = WebConsoleUtils;
-
-// The page errors listener
-
-/**
- * The nsIConsoleService listener. This is used to send all of the console
- * messages (JavaScript, CSS and more) to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow [window]
- *        Optional - the window object for which we are created. This is used
- *        for filtering out messages that belong to other windows.
- * @param object listener
- *        The listener object must have one method:
- *        - onConsoleServiceMessage(). This method is invoked with one argument,
- *        the nsIConsoleMessage, whenever a relevant message is received.
- */
-function ConsoleServiceListener(window, listener) {
-  this.window = window;
-  this.listener = listener;
-}
-exports.ConsoleServiceListener = ConsoleServiceListener;
-
-ConsoleServiceListener.prototype =
-{
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
-
-  /**
-   * The content window for which we listen to page errors.
-   * @type nsIDOMWindow
-   */
-  window: null,
-
-  /**
-   * The listener object which is notified of messages from the console service.
-   * @type object
-   */
-  listener: null,
-
-  /**
-   * Initialize the nsIConsoleService listener.
-   */
-  init: function () {
-    Services.console.registerListener(this);
-  },
-
-  /**
-   * The nsIConsoleService observer. This method takes all the script error
-   * messages belonging to the current window and sends them to the remote Web
-   * Console instance.
-   *
-   * @param nsIConsoleMessage message
-   *        The message object coming from the nsIConsoleService.
-   */
-  observe: function (message) {
-    if (!this.listener) {
-      return;
-    }
-
-    if (this.window) {
-      if (!(message instanceof Ci.nsIScriptError) ||
-          !message.outerWindowID ||
-          !this.isCategoryAllowed(message.category)) {
-        return;
-      }
-
-      let errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID);
-      if (!errorWindow || !isWindowIncluded(this.window, errorWindow)) {
-        return;
-      }
-    }
-
-    this.listener.onConsoleServiceMessage(message);
-  },
-
-  /**
-   * Check if the given message category is allowed to be tracked or not.
-   * We ignore chrome-originating errors as we only care about content.
-   *
-   * @param string category
-   *        The message category you want to check.
-   * @return boolean
-   *         True if the category is allowed to be logged, false otherwise.
-   */
-  isCategoryAllowed: function (category) {
-    if (!category) {
-      return false;
-    }
-
-    switch (category) {
-      case "XPConnect JavaScript":
-      case "component javascript":
-      case "chrome javascript":
-      case "chrome registration":
-      case "XBL":
-      case "XBL Prototype Handler":
-      case "XBL Content Sink":
-      case "xbl javascript":
-        return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Get the cached page errors for the current inner window and its (i)frames.
-   *
-   * @param boolean [includePrivate=false]
-   *        Tells if you want to also retrieve messages coming from private
-   *        windows. Defaults to false.
-   * @return array
-   *         The array of cached messages. Each element is an nsIScriptError or
-   *         an nsIConsoleMessage
-   */
-  getCachedMessages: function (includePrivate = false) {
-    let errors = Services.console.getMessageArray() || [];
-
-    // if !this.window, we're in a browser console. Still need to filter
-    // private messages.
-    if (!this.window) {
-      return errors.filter((error) => {
-        if (error instanceof Ci.nsIScriptError) {
-          if (!includePrivate && error.isFromPrivateWindow) {
-            return false;
-          }
-        }
-
-        return true;
-      });
-    }
-
-    let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-
-    return errors.filter((error) => {
-      if (error instanceof Ci.nsIScriptError) {
-        if (!includePrivate && error.isFromPrivateWindow) {
-          return false;
-        }
-        if (ids &&
-            (ids.indexOf(error.innerWindowID) == -1 ||
-             !this.isCategoryAllowed(error.category))) {
-          return false;
-        }
-      } else if (ids && ids[0]) {
-        // If this is not an nsIScriptError and we need to do window-based
-        // filtering we skip this message.
-        return false;
-      }
-
-      return true;
-    });
-  },
-
-  /**
-   * Remove the nsIConsoleService listener.
-   */
-  destroy: function () {
-    Services.console.unregisterListener(this);
-    this.listener = this.window = null;
-  },
-};
-
-// The window.console API observer
-
-/**
- * The window.console API observer. This allows the window.console API messages
- * to be sent to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow window
- *        Optional - the window object for which we are created. This is used
- *        for filtering out messages that belong to other windows.
- * @param object owner
- *        The owner object must have the following methods:
- *        - onConsoleAPICall(). This method is invoked with one argument, the
- *        Console API message that comes from the observer service, whenever
- *        a relevant console API call is received.
- * @param object filteringOptions
- *        Optional - The filteringOptions that this listener should listen to:
- *        - addonId: filter console messages based on the addonId.
- */
-function ConsoleAPIListener(window, owner, {addonId} = {}) {
-  this.window = window;
-  this.owner = owner;
-  this.addonId = addonId;
-}
-exports.ConsoleAPIListener = ConsoleAPIListener;
-
-ConsoleAPIListener.prototype =
-{
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
-
-  /**
-   * The content window for which we listen to window.console API calls.
-   * @type nsIDOMWindow
-   */
-  window: null,
-
-  /**
-   * The owner object which is notified of window.console API calls. It must
-   * have a onConsoleAPICall method which is invoked with one argument: the
-   * console API call object that comes from the observer service.
-   *
-   * @type object
-   * @see WebConsoleActor
-   */
-  owner: null,
-
-  /**
-   * The addonId that we listen for. If not null then only messages from this
-   * console will be returned.
-   */
-  addonId: null,
-
-  /**
-   * Initialize the window.console API observer.
-   */
-  init: function () {
-    // Note that the observer is process-wide. We will filter the messages as
-    // needed, see CAL_observe().
-    Services.obs.addObserver(this, "console-api-log-event", false);
-  },
-
-  /**
-   * The console API message observer. When messages are received from the
-   * observer service we forward them to the remote Web Console instance.
-   *
-   * @param object message
-   *        The message object receives from the observer service.
-   * @param string topic
-   *        The message topic received from the observer service.
-   */
-  observe: function (message, topic) {
-    if (!this.owner) {
-      return;
-    }
-
-    // Here, wrappedJSObject is not a security wrapper but a property defined
-    // by the XPCOM component which allows us to unwrap the XPCOM interface and
-    // access the underlying JSObject.
-    let apiMessage = message.wrappedJSObject;
-
-    if (!this.isMessageRelevant(apiMessage)) {
-      return;
-    }
-
-    this.owner.onConsoleAPICall(apiMessage);
-  },
-
-  /**
-   * Given a message, return true if this window should show it and false
-   * if it should be ignored.
-   *
-   * @param message
-   *        The message from the Storage Service
-   * @return bool
-   *         Do we care about this message?
-   */
-  isMessageRelevant: function (message) {
-    let workerType = WebConsoleUtils.getWorkerType(message);
-
-    if (this.window && workerType === "ServiceWorker") {
-      // For messages from Service Workers, message.ID is the
-      // scope, which can be used to determine whether it's controlling
-      // a window.
-      let scope = message.ID;
-
-      if (!swm.shouldReportToWindow(this.window, scope)) {
-        return false;
-      }
-    }
-
-    if (this.window && !workerType) {
-      let msgWindow = Services.wm.getCurrentInnerWindowWithId(message.innerID);
-      if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) {
-        // Not the same window!
-        return false;
-      }
-    }
-
-    if (this.addonId) {
-      // ConsoleAPI.jsm messages contains a consoleID, (and it is currently
-      // used in Addon SDK add-ons), the standard 'console' object
-      // (which is used in regular webpages and in WebExtensions pages)
-      // contains the originAttributes of the source document principal.
-
-      // Filtering based on the originAttributes used by
-      // the Console API object.
-      if (message.originAttributes &&
-          message.originAttributes.addonId == this.addonId) {
-        return true;
-      }
-
-      // Filtering based on the old-style consoleID property used by
-      // the legacy Console JSM module.
-      if (message.consoleID && message.consoleID == `addon/${this.addonId}`) {
-        return true;
-      }
-
-      return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Get the cached messages for the current inner window and its (i)frames.
-   *
-   * @param boolean [includePrivate=false]
-   *        Tells if you want to also retrieve messages coming from private
-   *        windows. Defaults to false.
-   * @return array
-   *         The array of cached messages.
-   */
-  getCachedMessages: function (includePrivate = false) {
-    let messages = [];
-    let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
-                              .getService(Ci.nsIConsoleAPIStorage);
-
-    // if !this.window, we're in a browser console. Retrieve all events
-    // for filtering based on privacy.
-    if (!this.window) {
-      messages = ConsoleAPIStorage.getEvents();
-    } else {
-      let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-      ids.forEach((id) => {
-        messages = messages.concat(ConsoleAPIStorage.getEvents(id));
-      });
-    }
-
-    CONSOLE_WORKER_IDS.forEach((id) => {
-      messages = messages.concat(ConsoleAPIStorage.getEvents(id));
-    });
-
-    messages = messages.filter(msg => {
-      return this.isMessageRelevant(msg);
-    });
-
-    if (includePrivate) {
-      return messages;
-    }
-
-    return messages.filter((m) => !m.private);
-  },
-
-  /**
-   * Destroy the console API listener.
-   */
-  destroy: function () {
-    Services.obs.removeObserver(this, "console-api-log-event");
-    this.window = this.owner = null;
-  },
-};
+exports.WebConsoleUtils = WebConsoleUtils;
 
 /**
  * WebConsole commands manager.
  *
  * Defines a set of functions /variables ("commands") that are available from
  * the Web Console but not from the web page.
  *
  */
@@ -943,20 +584,23 @@ WebConsoleCommands._registerOriginal("co
 /**
  * (Internal only) Add the bindings to |owner.sandbox|.
  * This is intended to be used by the WebConsole actor only.
   *
   * @param object owner
   *        The owning object.
   */
 function addWebConsoleCommands(owner) {
+  // Not supporting extra commands in workers yet.  This should be possible to
+  // add one by one as long as they don't require jsm, Cu, etc.
+  let commands = isWorker ? [] : WebConsoleCommands._registeredCommands;
   if (!owner) {
     throw new Error("The owner is required");
   }
-  for (let [name, command] of WebConsoleCommands._registeredCommands) {
+  for (let [name, command] of commands) {
     if (typeof command === "function") {
       owner.sandbox[name] = command.bind(undefined, owner);
     } else if (typeof command === "object") {
       let clone = Object.assign({}, command, {
         // We force the enumerability and the configurability (so the
         // WebConsoleActor can reconfigure the property).
         enumerable: true,
         configurable: true
@@ -970,94 +614,8 @@ function addWebConsoleCommands(owner) {
       }
 
       Object.defineProperty(owner.sandbox, name, clone);
     }
   }
 }
 
 exports.addWebConsoleCommands = addWebConsoleCommands;
-
-/**
- * A ReflowObserver that listens for reflow events from the page.
- * Implements nsIReflowObserver.
- *
- * @constructor
- * @param object window
- *        The window for which we need to track reflow.
- * @param object owner
- *        The listener owner which needs to implement:
- *        - onReflowActivity(reflowInfo)
- */
-
-function ConsoleReflowListener(window, listener) {
-  this.docshell = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                         .getInterface(Ci.nsIWebNavigation)
-                         .QueryInterface(Ci.nsIDocShell);
-  this.listener = listener;
-  this.docshell.addWeakReflowObserver(this);
-}
-
-exports.ConsoleReflowListener = ConsoleReflowListener;
-
-ConsoleReflowListener.prototype =
-{
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
-                                         Ci.nsISupportsWeakReference]),
-  docshell: null,
-  listener: null,
-
-  /**
-   * Forward reflow event to listener.
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   * @param boolean interruptible
-   */
-  sendReflow: function (start, end, interruptible) {
-    let frame = components.stack.caller.caller;
-
-    let filename = frame ? frame.filename : null;
-
-    if (filename) {
-      // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js",
-      // we only take the last part.
-      filename = filename.split(" ").pop();
-    }
-
-    this.listener.onReflowActivity({
-      interruptible: interruptible,
-      start: start,
-      end: end,
-      sourceURL: filename,
-      sourceLine: frame ? frame.lineNumber : null,
-      functionName: frame ? frame.name : null
-    });
-  },
-
-  /**
-   * On uninterruptible reflow
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   */
-  reflow: function (start, end) {
-    this.sendReflow(start, end, false);
-  },
-
-  /**
-   * On interruptible reflow
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   */
-  reflowInterruptible: function (start, end) {
-    this.sendReflow(start, end, true);
-  },
-
-  /**
-   * Unregister listener.
-   */
-  destroy: function () {
-    this.docshell.removeWeakReflowObserver(this);
-    this.listener = this.docshell = null;
-  },
-};
rename from devtools/server/actors/utils/webconsole-worker-utils.js
rename to devtools/server/actors/utils/webconsole-worker-listeners.js
--- a/devtools/server/actors/utils/webconsole-worker-utils.js
+++ b/devtools/server/actors/utils/webconsole-worker-listeners.js
@@ -1,20 +1,37 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft= javascript ts=2 et sw=2 tw=80: */
 /* 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";
 
-// XXXworkers This file is loaded on the server side for worker debugging.
+// 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.
+// have access to Services / Components but the listeners defined here
+// are imported by webconsole-utils and used for the webconsole actor.
+
+function ConsoleAPIListener(window, owner, consoleID) {
+  this.window = window;
+  this.owner = owner;
+  this.consoleID = consoleID;
+  this.observe = this.observe.bind(this);
+}
 
-exports.Utils = { L10n: function () {} };
-exports.ConsoleServiceListener = function () {};
-exports.ConsoleAPIListener = function () {};
-exports.addWebConsoleCommands = function () {};
-exports.ConsoleReflowListener = function () {};
-exports.CONSOLE_WORKER_IDS = [];
+ConsoleAPIListener.prototype =
+{
+  init: function () {
+    setConsoleEventHandler(this.observe);
+  },
+  destroy: function () {
+    setConsoleEventHandler(null);
+  },
+  observe: function(message) {
+    this.owner.onConsoleAPICall(message.wrappedJSObject);
+  },
+  getCachedMessages: function() {
+    return retrieveConsoleEvents();
+  }
+};
+
+exports.ConsoleAPIListener = ConsoleAPIListener;
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -19,34 +19,29 @@ loader.lazyRequireGetter(this, "NetworkM
 loader.lazyRequireGetter(this, "NetworkMonitorChild", "devtools/shared/webconsole/network-monitor", true);
 loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true);
 loader.lazyRequireGetter(this, "StackTraceCollector", "devtools/shared/webconsole/network-monitor", true);
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "ServerLoggingListener", "devtools/shared/webconsole/server-logger", true);
 loader.lazyRequireGetter(this, "JSPropertyProvider", "devtools/shared/webconsole/js-property-provider", true);
 loader.lazyRequireGetter(this, "Parser", "resource://devtools/shared/Parser.jsm", true);
 loader.lazyRequireGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm", true);
+loader.lazyRequireGetter(this, "addWebConsoleCommands", "devtools/server/actors/utils/webconsole-utils", true);
+loader.lazyRequireGetter(this, "CONSOLE_WORKER_IDS", "devtools/server/actors/utils/webconsole-utils", true);
+loader.lazyRequireGetter(this, "WebConsoleUtils", "devtools/server/actors/utils/webconsole-utils", true);
 
-for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
-    "ConsoleAPIListener", "addWebConsoleCommands",
-    "ConsoleReflowListener", "CONSOLE_WORKER_IDS"]) {
-  Object.defineProperty(this, name, {
-    get: function (prop) {
-      if (prop == "WebConsoleUtils") {
-        prop = "Utils";
-      }
-      if (isWorker) {
-        return require("devtools/server/actors/utils/webconsole-worker-utils")[prop];
-      } else {
-        return require("devtools/server/actors/utils/webconsole-utils")[prop];
-      }
-    }.bind(null, name),
-    configurable: true,
-    enumerable: true
-  });
+// Overwrite implemented listeners for workers so that we don't attempt
+// to load an unsupported module.
+if (isWorker) {
+  loader.lazyRequireGetter(this, "ConsoleAPIListener", "devtools/server/actors/utils/webconsole-worker-listeners", true);
+  loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/server/actors/utils/webconsole-worker-listeners", true);
+} else {
+  loader.lazyRequireGetter(this, "ConsoleAPIListener", "devtools/server/actors/utils/webconsole-listeners", true);
+  loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/server/actors/utils/webconsole-listeners", true);
+  loader.lazyRequireGetter(this, "ConsoleReflowListener", "devtools/server/actors/utils/webconsole-listeners", true);
 }
 
 /**
  * The WebConsoleActor implements capabilities needed for the Web Console
  * feature.
  *
  * @constructor
  * @param object aConnection
@@ -314,16 +309,21 @@ WebConsoleActor.prototype =
   },
 
   grip: function WCA_grip()
   {
     return { actor: this.actorID };
   },
 
   hasNativeConsoleAPI: function WCA_hasNativeConsoleAPI(aWindow) {
+    if (isWorker) {
+      // Can't use XPCNativeWrapper as a way to check for console API in workers
+      return true;
+    }
+
     let isNative = false;
     try {
       // We are very explicitly examining the "console" property of
       // the non-Xrayed object here.
       let console = aWindow.wrappedJSObject.console;
       isNative = new XPCNativeWrapper(console).IS_NATIVE_CONSOLE
     }
     catch (ex) { }
@@ -557,33 +557,32 @@ WebConsoleActor.prototype =
    *
    * @param object aRequest
    *        The JSON request object received from the Web Console client.
    * @return object
    *         The response object which holds the startedListeners array.
    */
   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 messageManager = null;
 
     if (this._parentIsContentActor) {
       messageManager = this.parentActor.messageManager;
     }
 
     while (aRequest.listeners.length > 0) {
       let listener = aRequest.listeners.shift();
       switch (listener) {
         case "PageError":
+          // Workers don't support this message type yet
+          if (isWorker) {
+            break;
+          }
           if (!this.consoleServiceListener) {
             this.consoleServiceListener =
               new ConsoleServiceListener(window, this);
             this.consoleServiceListener.init();
           }
           startedListeners.push(listener);
           break;
         case "ConsoleAPI":
@@ -593,16 +592,20 @@ WebConsoleActor.prototype =
             this.consoleAPIListener =
               new ConsoleAPIListener(window, this,
                                      this.parentActor.consoleAPIListenerOptions);
             this.consoleAPIListener.init();
           }
           startedListeners.push(listener);
           break;
         case "NetworkActivity":
+          // Workers don't support this message type
+          if (isWorker) {
+            break;
+          }
           if (!this.networkMonitor) {
             // Create a StackTraceCollector that's going to be shared both by the
             // NetworkMonitorChild (getting messages about requests from parent) and
             // by the NetworkMonitor that directly watches service workers requests.
             this.stackTraceCollector = new StackTraceCollector({ window });
             this.stackTraceCollector.init();
 
             let processBoundary = Services.appinfo.processType !=
@@ -620,34 +623,46 @@ WebConsoleActor.prototype =
             } else {
               this.networkMonitor = new NetworkMonitor({ window }, this);
               this.networkMonitor.init();
             }
           }
           startedListeners.push(listener);
           break;
         case "FileActivity":
+          // Workers don't support this message type
+          if (isWorker) {
+            break;
+          }
           if (this.window instanceof Ci.nsIDOMWindow) {
             if (!this.consoleProgressListener) {
               this.consoleProgressListener =
                 new ConsoleProgressListener(this.window, this);
             }
             this.consoleProgressListener.startMonitor(this.consoleProgressListener.
                                                       MONITOR_FILE_ACTIVITY);
             startedListeners.push(listener);
           }
           break;
         case "ReflowActivity":
+          // Workers don't support this message type
+          if (isWorker) {
+            break;
+          }
           if (!this.consoleReflowListener) {
             this.consoleReflowListener =
               new ConsoleReflowListener(this.window, this);
           }
           startedListeners.push(listener);
           break;
         case "ServerLogging":
+          // Workers don't support this message type
+          if (isWorker) {
+            break;
+          }
           if (!this.serverLoggingListener) {
             this.serverLoggingListener =
               new ServerLoggingListener(this.window, this);
           }
           startedListeners.push(listener);
           break;
       }
     }
--- a/devtools/server/worker.js
+++ b/devtools/server/worker.js
@@ -76,16 +76,19 @@ this.addEventListener("message", functio
         },
 
         window: global
       };
 
       let threadActor = new ThreadActor(parent, global);
       pool.addActor(threadActor);
 
+      // parentActor.threadActor is needed from the webconsole for grip previewing
+      parent.threadActor = 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,
--- a/devtools/shared/tests/unit/test_console_filtering.js
+++ b/devtools/shared/tests/unit/test_console_filtering.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const { console, ConsoleAPI } = require("resource://gre/modules/Console.jsm");
-const { ConsoleAPIListener } = require("devtools/server/actors/utils/webconsole-utils");
+const { ConsoleAPIListener } = require("devtools/server/actors/utils/webconsole-listeners");
 const Services = require("Services");
 
 var seenMessages = 0;
 var seenTypes = 0;
 
 var callback = {
   onConsoleAPICall: function (aMessage) {
     if (aMessage.consoleID && aMessage.consoleID == "addon/foo") {
--- a/devtools/shared/webconsole/test/chrome.ini
+++ b/devtools/shared/webconsole/test/chrome.ini
@@ -16,16 +16,17 @@ support-files =
 [test_cached_messages.html]
 [test_commands_other.html]
 [test_commands_registration.html]
 [test_consoleapi.html]
 [test_consoleapi_innerID.html]
 [test_console_serviceworker.html]
 [test_console_serviceworker_cached.html]
 [test_console_styling.html]
+[test_console_worker.html]
 [test_file_uri.html]
 [test_reflow.html]
 [test_jsterm.html]
 [test_jsterm_autocomplete.html]
 [test_jsterm_cd_iframe.html]
 [test_jsterm_last_result.html]
 [test_jsterm_queryselector.html]
 [test_network_get.html]
--- a/devtools/shared/webconsole/test/common.js
+++ b/devtools/shared/webconsole/test/common.js
@@ -19,17 +19,17 @@ var WebConsoleUtils = require("devtools/
 var {Task} = require("devtools/shared/task");
 
 var ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
                           .getService(Ci.nsIConsoleAPIStorage);
 var {DebuggerServer} = require("devtools/server/main");
 var {DebuggerClient, ObjectClient} = require("devtools/shared/client/main");
 
 var {ConsoleServiceListener, ConsoleAPIListener} =
-  require("devtools/server/actors/utils/webconsole-utils");
+  require("devtools/server/actors/utils/webconsole-listeners");
 
 function initCommon()
 {
   // Services.prefs.setBoolPref("devtools.debugger.log", true);
 }
 
 function initDebuggerServer()
 {
--- a/devtools/shared/webconsole/test/console-test-worker.js
+++ b/devtools/shared/webconsole/test/console-test-worker.js
@@ -1,10 +1,12 @@
 "use strict";
 
+console.log("Log from worker init");
+
 function f() {
   var a = 1;
   var b = 2;
   var c = 3;
 }
 
 self.onmessage = function (event) {
   if (event.data == "ping") {
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_console_worker.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for the Console API and Workers</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript;version=1.8" src="common.js"></script>
+  <!-- Any copyright is dedicated to the Public Domain.
+     - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the Console API and Workers</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let expectedCachedConsoleCalls = [
+    {
+      level: "log",
+      filename: /console-test-worker/,
+      arguments: ['Log from worker init'],
+    },
+];
+
+let expectedConsoleAPICalls = [
+    {
+      level: "log",
+      arguments: ['Log was requested from worker'],
+    },
+];
+
+window.onload = Task.async(function*() {
+  let {state,response} = yield new Promise(resolve => {
+    attachConsoleToWorker(["ConsoleAPI"], (state, response) => {
+      resolve({state,response})
+    });
+  });
+
+  yield testCachedMessages(state);
+  yield testConsoleAPI(state);
+
+  closeDebugger(state, function() {
+    SimpleTest.finish();
+  });
+});
+
+let testCachedMessages = Task.async(function*(state) {
+  info("testCachedMessages entered");
+  return new Promise(resolve => {
+    let onCachedConsoleAPI = (response) => {
+      let consoleCalls = response.messages;
+
+      info('Received cached response. Checking console calls.');
+      checkConsoleAPICalls(consoleCalls, expectedCachedConsoleCalls);
+      resolve();
+    };
+    state.client.getCachedMessages(["ConsoleAPI"], onCachedConsoleAPI);
+  })
+});
+
+let testConsoleAPI = Task.async(function*(state) {
+  info("testConsoleAPI entered");
+  return new Promise(resolve => {
+    let onConsoleAPICall = (type, packet) => {
+      info("received message level: " + packet.message.level);
+      is(packet.from, state.actor, "console API call actor");
+      checkConsoleAPICalls([packet.message], expectedConsoleAPICalls);
+      state.dbgClient.removeListener("consoleAPICall", onConsoleAPICall);
+      resolve();
+    }
+
+    info("testConsoleAPI: adding listener for consoleAPICall");
+    state.dbgClient.addListener("consoleAPICall", onConsoleAPICall);
+
+    state.client.evaluateJS("console.log('Log was requested from worker')",
+      () => { });
+  });
+});
+
+</script>
+</body>
+</html>
--- a/devtools/shared/worker/loader.js
+++ b/devtools/shared/worker/loader.js
@@ -11,16 +11,19 @@
 // In principle, the standard instance of the worker loader should provide the
 // same built-in modules as its devtools counterpart, so that both loaders are
 // interchangable on the main thread, making them easier to test.
 //
 // On the worker thread, some of these modules, in particular those that rely on
 // the use of Components, and for which the worker debugger doesn't provide an
 // alternative API, will be replaced by vacuous objects. Consequently, they can
 // still be required, but any attempts to use them will lead to an exception.
+//
+// Note: to see dump output when running inside the worker thread, you might
+// need to enable the browser.dom.window.dump.enabled pref.
 
 this.EXPORTED_SYMBOLS = ["WorkerDebuggerLoader", "worker"];
 
 // Some notes on module ids and URLs:
 //
 // An id is either a relative id or an absolute id. An id is relative if and
 // only if it starts with a dot. An absolute id is a normalized id if and only
 // if it contains no redundant components.
@@ -362,17 +365,17 @@ var {
   Debugger,
   URL,
   createSandbox,
   dump,
   rpc,
   loadSubScript,
   reportError,
   setImmediate,
-  xpcInspector
+  xpcInspector,
 } = (function () {
   if (typeof Components === "object") { // Main thread
     let {
       Constructor: CC,
       classes: Cc,
       manager: Cm,
       interfaces: Ci,
       results: Cr,
@@ -479,18 +482,20 @@ var {
 this.worker = new WorkerDebuggerLoader({
   createSandbox: createSandbox,
   globals: {
     "isWorker": true,
     "dump": dump,
     "loader": loader,
     "reportError": reportError,
     "rpc": rpc,
+    "URL": URL,
     "setImmediate": setImmediate,
-    "URL": URL,
+    "retrieveConsoleEvents": this.retrieveConsoleEvents,
+    "setConsoleEventHandler": this.setConsoleEventHandler,
   },
   loadSubScript: loadSubScript,
   modules: {
     "Debugger": Debugger,
     "Services": Object.create(null),
     "chrome": chrome,
     "xpcInspector": xpcInspector
   },
--- a/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
+++ b/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
@@ -14,17 +14,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://devtools/shared/Loader.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
                                    "@mozilla.org/contentsecuritymanager;1",
                                    "nsIContentSecurityManager");
 XPCOMUtils.defineLazyServiceGetter(this, "gScriptSecurityManager",
                                    "@mozilla.org/scriptsecuritymanager;1",
                                    "nsIScriptSecurityManager");
 XPCOMUtils.defineLazyGetter(this, "WebConsoleUtils", () => {
-  return this.devtools.require("devtools/server/actors/utils/webconsole-utils").Utils;
+  return this.devtools.require("devtools/server/actors/utils/webconsole-utils").WebConsoleUtils;
 });
 
 this.InsecurePasswordUtils = {
   _formRootsWarned: new WeakMap(),
   _sendWebConsoleMessage(messageTag, domDoc) {
     let windowId = WebConsoleUtils.getInnerWindowId(domDoc.defaultView);
     let category = "Insecure Password Field";
     // All web console messages are warnings for now.