Bug 1039952 - Part 0: Make the parent actors manage the set of debuggees. r=fitzgen
authorNick Fitzgerald <fitzgen@gmail.com>
Mon, 28 Jul 2014 15:01:00 +0200
changeset 196521 fea2a90e2d228e7b1ef2c24f3fbb1895f5e6cfda
parent 196520 e024d12f7ee5fb2e4e293330a11db00ab2c85180
child 196522 fe3a7fa0732f34f5b8d241bc49de092db28a63f9
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersfitzgen
bugs1039952
milestone34.0a1
Bug 1039952 - Part 0: Make the parent actors manage the set of debuggees. r=fitzgen
toolkit/devtools/event-emitter.js
toolkit/devtools/server/Makefile.in
toolkit/devtools/server/actors/memory.js
toolkit/devtools/server/actors/root.js
toolkit/devtools/server/actors/script.js
toolkit/devtools/server/actors/tracer.js
toolkit/devtools/server/actors/utils/make-debugger.js
toolkit/devtools/server/actors/utils/map-uri-to-addon-id.js
toolkit/devtools/server/actors/webbrowser.js
toolkit/devtools/server/actors/webconsole.js
toolkit/devtools/server/tests/unit/head_dbg.js
toolkit/devtools/server/tests/unit/test_trace_actor-04.js
toolkit/devtools/server/tests/unit/testactors.js
--- a/toolkit/devtools/event-emitter.js
+++ b/toolkit/devtools/event-emitter.js
@@ -107,17 +107,17 @@ EventEmitter.prototype = {
       this._eventEmitterListeners.set(aEvent, listeners.filter(l => {
         return l !== aListener && l._originalListener !== aListener;
       }));
     }
   },
 
   /**
    * Emit an event.  All arguments to this method will
-   * be sent to listner functions.
+   * be sent to listener functions.
    */
   emit: function EventEmitter_emit(aEvent) {
     this.logEvent(aEvent, arguments);
 
     if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(aEvent)) {
       return;
     }
 
--- a/toolkit/devtools/server/Makefile.in
+++ b/toolkit/devtools/server/Makefile.in
@@ -3,8 +3,9 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 include $(topsrcdir)/config/rules.mk
 
 libs::
 	$(INSTALL) $(IFLAGS1) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
 	$(INSTALL) $(IFLAGS1) $(srcdir)/*.js $(FINAL_TARGET)/modules/devtools/server
 	$(INSTALL) $(IFLAGS1) $(srcdir)/actors/*.js $(FINAL_TARGET)/modules/devtools/server/actors
+	$(INSTALL) $(IFLAGS1) $(srcdir)/actors/utils/*.js $(FINAL_TARGET)/modules/devtools/server/actors/utils
--- a/toolkit/devtools/server/actors/memory.js
+++ b/toolkit/devtools/server/actors/memory.js
@@ -58,17 +58,17 @@ let MemoryActor = protocol.ActorClass({
       result.jsStringsSize = jsStringsSize.value;
       result.jsOtherSize = jsOtherSize.value;
       result.otherSize = otherSize.value;
       result.jsMilliseconds = jsMilliseconds.value.toFixed(1);
       result.nonJSMilliseconds = nonJSMilliseconds.value.toFixed(1);
     } catch (e) {
       console.error(e);
       let url = this.tabActor.url;
-      console.error("Error getting size of "+url);
+      console.error("Error getting size of " + url);
     }
 
     return result;
   }, {
     request: {},
     response: RetVal("json"),
   }),
 
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -6,16 +6,17 @@
 
 "use strict";
 
 const { Ci, Cu } = require("chrome");
 const Services = require("Services");
 const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
 const { DebuggerServer } = require("devtools/server/main");
 const { dumpProtocolSpec } = require("devtools/server/protocol");
+const makeDebugger = require("./utils/make-debugger");
 
 /* Root actor for the remote debugging protocol. */
 
 /**
  * Create a remote debugging protocol root actor.
  *
  * @param aConnection
  *     The DebuggerServerConnection whose root actor we are constructing.
@@ -87,16 +88,22 @@ const { dumpProtocolSpec } = require("de
  * iteration: alliterative lazy live lists.
  */
 function RootActor(aConnection, aParameters) {
   this.conn = aConnection;
   this._parameters = aParameters;
   this._onTabListChanged = this.onTabListChanged.bind(this);
   this._onAddonListChanged = this.onAddonListChanged.bind(this);
   this._extraActors = {};
+
+  // This creates a Debugger instance for chrome debugging all globals.
+  this.makeDebugger = makeDebugger.bind(null, {
+    findDebuggees: dbg => dbg.findAllGlobals(),
+    shouldAddNewGlobalAsDebuggee: () => true
+  });
 }
 
 RootActor.prototype = {
   constructor: RootActor,
   applicationType: "browser",
 
   traits: {
     sources: true,
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -11,62 +11,33 @@ const { Cc, Ci, Cu, components, ChromeWo
 const { ActorPool } = require("devtools/server/actors/common");
 const { DebuggerServer } = require("devtools/server/main");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dbg_assert, dumpn, update } = DevToolsUtils;
 const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
 const promise = require("promise");
 const Debugger = require("Debugger");
 const xpcInspector = require("xpcInspector");
+const mapURIToAddonID = require("./utils/map-uri-to-addon-id");
 
 const { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");
 const { CssLogic } = require("devtools/styleinspector/css-logic");
 
 DevToolsUtils.defineLazyGetter(this, "NetUtil", () => {
   return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
 });
 
-let B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
-
 let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
       "Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
       "Float64Array"];
 
 // Number of items to preview in objects, arrays, maps, sets, lists,
 // collections, etc.
 let OBJECT_PREVIEW_MAX_ITEMS = 10;
 
-let addonManager = null;
-
-/**
- * This is a wrapper around amIAddonManager.mapURIToAddonID which always returns
- * false on B2G to avoid loading the add-on manager there and reports any
- * exceptions rather than throwing so that the caller doesn't have to worry
- * about them.
- */
-function mapURIToAddonID(uri, id) {
-  if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT ||
-      (Services.appinfo.ID || undefined) == B2G_ID) {
-    return false;
-  }
-
-  if (!addonManager) {
-    addonManager = Cc["@mozilla.org/addons/integration;1"].
-                   getService(Ci.amIAddonManager);
-  }
-
-  try {
-    return addonManager.mapURIToAddonID(uri, id);
-  }
-  catch (e) {
-    DevToolsUtils.reportException("mapURIToAddonID", e);
-    return false;
-  }
-}
-
 /**
  * BreakpointStore objects keep track of all breakpoints that get set so that we
  * can reset them when the same script is introduced to the thread again (such
  * as after a refresh).
  */
 function BreakpointStore() {
   this._size = 0;
 
@@ -476,65 +447,94 @@ EventLoop.prototype = {
     }
     return false;
   },
 };
 
 /**
  * JSD2 actors.
  */
+
 /**
  * Creates a ThreadActor.
  *
  * ThreadActors manage a JSInspector object and manage execution/inspection
  * of debuggees.
  *
- * @param aHooks object
- *        An object with preNest and postNest methods for calling when entering
- *        and exiting a nested event loop.
+ * @param aParent object
+ *        This |ThreadActor|'s parent actor. It must implement the following
+ *        properties:
+ *          - url: The URL string of the debuggee.
+ *          - window: The global window object.
+ *          - preNest: Function called before entering a nested event loop.
+ *          - postNest: Function called after exiting a nested event loop.
+ *          - makeDebugger: A function that takes no arguments and instantiates
+ *            a Debugger that manages its globals on its own.
  * @param aGlobal object [optional]
  *        An optional (for content debugging only) reference to the content
  *        window.
  */
-function ThreadActor(aHooks, aGlobal)
+function ThreadActor(aParent, aGlobal)
 {
   this._state = "detached";
   this._frameActors = [];
-  this._hooks = aHooks;
-  this.global = aGlobal;
-  // A map of actorID -> actor for breakpoints created and managed by the server.
-  this._hiddenBreakpoints = new Map();
-
-  this.findGlobals = this.globalManager.findGlobals.bind(this);
-  this.onNewGlobal = this.globalManager.onNewGlobal.bind(this);
-  this.onNewSource = this.onNewSource.bind(this);
-  this._allEventsListener = this._allEventsListener.bind(this);
+  this._parent = aParent;
+  this._dbg = null;
+  this._gripDepth = 0;
+  this._threadLifetimePool = null;
+  this._tabClosed = false;
 
   this._options = {
     useSourceMaps: false,
     autoBlackBox: false
   };
 
-  this._gripDepth = 0;
-  this._threadLifetimePool = null;
-  this._tabClosed = false;
+  // A map of actorID -> actor for breakpoints created and managed by the
+  // server.
+  this._hiddenBreakpoints = new Map();
+
+  this.global = aGlobal;
+
+  this._allEventsListener = this._allEventsListener.bind(this);
+  this.onNewGlobal = this.onNewGlobal.bind(this);
+  this.onNewSource = this.onNewSource.bind(this);
+  this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
+  this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
+  this.onNewScript = this.onNewScript.bind(this);
 }
 
 /**
  * The breakpoint store must be shared across instances of ThreadActor so that
  * page reloads don't blow away all of our breakpoints.
  */
 ThreadActor.breakpointStore = new BreakpointStore();
 
 ThreadActor.prototype = {
   // Used by the ObjectActor to keep track of the depth of grip() calls.
   _gripDepth: null,
 
   actorPrefix: "context",
 
+  get dbg() {
+    if (!this._dbg) {
+      this._dbg = this._parent.makeDebugger();
+      this._dbg.uncaughtExceptionHook = this.uncaughtExceptionHook;
+      this._dbg.onDebuggerStatement = this.onDebuggerStatement;
+      this._dbg.onNewScript = this.onNewScript;
+      this._dbg.on("newGlobal", this.onNewGlobal);
+      // Keep the debugger disabled until a client attaches.
+      this._dbg.enabled = this._state != "detached";
+    }
+    return this._dbg;
+  },
+
+  get globalDebugObject() {
+    return this.dbg.makeGlobalObjectReference(this._parent.window);
+  },
+
   get state() { return this._state; },
   get attached() this.state == "attached" ||
                  this.state == "running" ||
                  this.state == "paused",
 
   get breakpointStore() { return ThreadActor.breakpointStore; },
 
   get threadLifetimePool() {
@@ -613,136 +613,33 @@ ThreadActor.prototype = {
     dbg_assert(eventLoop, "Should have an event loop.");
     eventLoop.resolve();
   },
 
   /**
    * Remove all debuggees and clear out the thread's sources.
    */
   clearDebuggees: function () {
-    if (this.dbg) {
+    if (this._dbg) {
       this.dbg.removeAllDebuggees();
     }
     this._sources = null;
   },
 
   /**
-   * Add a debuggee global to the Debugger object.
-   *
-   * @returns the Debugger.Object that corresponds to the global.
-   */
-  addDebuggee: function (aGlobal) {
-    let globalDebugObject;
-    try {
-      globalDebugObject = this.dbg.addDebuggee(aGlobal);
-    } catch (e) {
-      // Ignore attempts to add the debugger's compartment as a debuggee.
-      dumpn("Ignoring request to add the debugger's compartment as a debuggee");
-    }
-    return globalDebugObject;
-  },
-
-  /**
-   * Initialize the Debugger.
-   */
-  _initDebugger: function () {
-    this.dbg = new Debugger();
-    this.dbg.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
-    this.dbg.onDebuggerStatement = this.onDebuggerStatement.bind(this);
-    this.dbg.onNewScript = this.onNewScript.bind(this);
-    this.dbg.onNewGlobalObject = this.globalManager.onNewGlobal.bind(this);
-    // Keep the debugger disabled until a client attaches.
-    this.dbg.enabled = this._state != "detached";
-  },
-
-  /**
-   * Remove a debuggee global from the JSInspector.
-   */
-  removeDebugee: function (aGlobal) {
-    try {
-      this.dbg.removeDebuggee(aGlobal);
-    } catch(ex) {
-      // XXX: This debuggee has code currently executing on the stack,
-      // we need to save this for later.
-    }
-  },
-
-  /**
-   * Add the provided window and all windows in its frame tree as debuggees.
-   *
-   * @returns the Debugger.Object that corresponds to the window.
+   * Listener for our |Debugger|'s "newGlobal" event.
    */
-  _addDebuggees: function (aWindow) {
-    let globalDebugObject = this.addDebuggee(aWindow);
-    let frames = aWindow.frames;
-    if (frames) {
-      for (let i = 0; i < frames.length; i++) {
-        this._addDebuggees(frames[i]);
-      }
-    }
-    return globalDebugObject;
-  },
-
-  /**
-   * An object that will be used by ThreadActors to tailor their behavior
-   * depending on the debugging context being required (chrome or content).
-   */
-  globalManager: {
-    findGlobals: function () {
-      const { getContentGlobals } = require("devtools/server/content-globals");
-
-      this.globalDebugObject = this._addDebuggees(this.global);
-
-      // global may not be a window
-      try {
-        getContentGlobals({
-          'inner-window-id': getInnerId(this.global)
-        }).forEach(this.addDebuggee.bind(this));
-      }
-      catch(e) {}
-    },
-
-    /**
-     * A function that the engine calls when a new global object
-     * (for example a sandbox) has been created.
-     *
-     * @param aGlobal Debugger.Object
-     *        The new global object that was created.
-     */
-    onNewGlobal: function (aGlobal) {
-      let useGlobal = (aGlobal.hostAnnotations &&
-                       aGlobal.hostAnnotations.type == "document" &&
-                       aGlobal.hostAnnotations.element === this.global);
-
-      // check if the global is a sdk page-mod sandbox
-      if (!useGlobal) {
-        let metadata = {};
-        let id = "";
-        try {
-          id = getInnerId(this.global);
-          metadata = Cu.getSandboxMetadata(aGlobal.unsafeDereference());
-        }
-        catch (e) {}
-
-        useGlobal = (metadata['inner-window-id'] && metadata['inner-window-id'] == id);
-      }
-
-      // Content debugging only cares about new globals in the contant window,
-      // like iframe children.
-      if (useGlobal) {
-        this.addDebuggee(aGlobal);
-        // Notify the client.
-        this.conn.send({
-          from: this.actorID,
-          type: "newGlobal",
-          // TODO: after bug 801084 lands see if we need to JSONify this.
-          hostAnnotations: aGlobal.hostAnnotations
-        });
-      }
-    }
+  onNewGlobal: function (aGlobal) {
+    // Notify the client.
+    this.conn.send({
+      from: this.actorID,
+      type: "newGlobal",
+      // TODO: after bug 801084 lands see if we need to JSONify this.
+      hostAnnotations: aGlobal.hostAnnotations
+    });
   },
 
   disconnect: function () {
     dumpn("in ThreadActor.prototype.disconnect");
     if (this._state == "paused") {
       this.onResume();
     }
 
@@ -754,21 +651,21 @@ ThreadActor.prototype = {
       this._prettyPrintWorker.removeEventListener(
         "error", this._onPrettyPrintError, false);
       this._prettyPrintWorker.removeEventListener(
         "message", this._onPrettyPrintMsg, false);
       this._prettyPrintWorker.terminate();
       this._prettyPrintWorker = null;
     }
 
-    if (!this.dbg) {
+    if (!this._dbg) {
       return;
     }
-    this.dbg.enabled = false;
-    this.dbg = null;
+    this._dbg.enabled = false;
+    this._dbg = null;
   },
 
   /**
    * Disconnect the debugger and put the actor in the exited state.
    */
   exit: function () {
     this.disconnect();
     this._state = "exited";
@@ -787,25 +684,22 @@ ThreadActor.prototype = {
 
     this._state = "attached";
 
     update(this._options, aRequest.options || {});
 
     // Initialize an event loop stack. This can't be done in the constructor,
     // because this.conn is not yet initialized by the actor pool at that time.
     this._nestedEventLoops = new EventLoopStack({
-      hooks: this._hooks,
+      hooks: this._parent,
       connection: this.conn,
       thread: this
     });
 
-    if (!this.dbg) {
-      this._initDebugger();
-    }
-    this.findGlobals();
+    this.dbg.addDebuggees();
     this.dbg.enabled = true;
     try {
       // Put ourselves in the paused state.
       let packet = this._paused();
       if (!packet) {
         return { error: "notAttached" };
       }
       packet.why = { type: "attached" };
@@ -1127,18 +1021,18 @@ ThreadActor.prototype = {
           + this._state + "'"
       };
     }
 
     // In case of multiple nested event loops (due to multiple debuggers open in
     // different tabs or multiple debugger clients connected to the same tab)
     // only allow resumption in a LIFO order.
     if (this._nestedEventLoops.size && this._nestedEventLoops.lastPausedUrl
-        && (this._nestedEventLoops.lastPausedUrl !== this._hooks.url
-        || this._nestedEventLoops.lastConnection !== this.conn)) {
+        && (this._nestedEventLoops.lastPausedUrl !== this._parent.url
+            || this._nestedEventLoops.lastConnection !== this.conn)) {
       return {
         error: "wrongOrder",
         message: "trying to resume in the wrong order.",
         lastPausedUrl: this._nestedEventLoops.lastPausedUrl
       };
     }
 
     if (aRequest && aRequest.forceCompletion) {
@@ -1920,16 +1814,17 @@ ThreadActor.prototype = {
 
     // Clear stepping hooks.
     this.dbg.onEnterFrame = undefined;
     this.dbg.onExceptionUnwind = undefined;
     if (aFrame) {
       aFrame.onStep = undefined;
       aFrame.onPop = undefined;
     }
+
     // Clear DOM event breakpoints.
     // XPCShell tests don't use actual DOM windows for globals and cause
     // removeListenerForAllEvents to throw.
     if (this.global && !this.global.toString().contains("Sandbox")) {
       let els = Cc["@mozilla.org/eventlistenerservice;1"]
                 .getService(Ci.nsIEventListenerService);
       els.removeListenerForAllEvents(this.global, this._allEventsListener, true);
       for (let [,bp] of this._hiddenBreakpoints) {
@@ -4831,94 +4726,57 @@ Object.defineProperty(Debugger.Frame.pro
  * Creates an actor for handling chrome debugging. ChromeDebuggerActor is a
  * thin wrapper over ThreadActor, slightly changing some of its behavior.
  *
  * @param aConnection object
  *        The DebuggerServerConnection with which this ChromeDebuggerActor
  *        is associated. (Currently unused, but required to make this
  *        constructor usable with addGlobalActor.)
  *
- * @param aHooks object
- *        An object with preNest and postNest methods for calling when entering
- *        and exiting a nested event loop.
+ * @param aParent object
+ *        This actor's parent actor. See ThreadActor for a list of expected
+ *        properties.
  */
-function ChromeDebuggerActor(aConnection, aHooks)
+function ChromeDebuggerActor(aConnection, aParent)
 {
-  ThreadActor.call(this, aHooks);
+  ThreadActor.call(this, aParent);
 }
 
 ChromeDebuggerActor.prototype = Object.create(ThreadActor.prototype);
 
 update(ChromeDebuggerActor.prototype, {
   constructor: ChromeDebuggerActor,
 
   // A constant prefix that will be used to form the actor ID by the server.
   actorPrefix: "chromeDebugger",
 
   /**
    * Override the eligibility check for scripts and sources to make sure every
    * script and source with a URL is stored when debugging chrome.
    */
-  _allowSource: function(aSourceURL) !!aSourceURL,
-
-   /**
-   * An object that will be used by ThreadActors to tailor their behavior
-   * depending on the debugging context being required (chrome or content).
-   * The methods that this object provides must be bound to the ThreadActor
-   * before use.
-   */
-  globalManager: {
-    findGlobals: function () {
-      // Add every global known to the debugger as debuggee.
-      this.dbg.addAllGlobalsAsDebuggees();
-    },
-
-    /**
-     * A function that the engine calls when a new global object has been
-     * created.
-     *
-     * @param aGlobal Debugger.Object
-     *        The new global object that was created.
-     */
-    onNewGlobal: function (aGlobal) {
-      this.addDebuggee(aGlobal);
-      // Notify the client.
-      this.conn.send({
-        from: this.actorID,
-        type: "newGlobal",
-        // TODO: after bug 801084 lands see if we need to JSONify this.
-        hostAnnotations: aGlobal.hostAnnotations
-      });
-    }
-  }
+  _allowSource: aSourceURL => !!aSourceURL
 });
 
 exports.ChromeDebuggerActor = ChromeDebuggerActor;
 
 /**
  * Creates an actor for handling add-on debugging. AddonThreadActor is
  * a thin wrapper over ThreadActor.
  *
  * @param aConnection object
  *        The DebuggerServerConnection with which this AddonThreadActor
  *        is associated. (Currently unused, but required to make this
  *        constructor usable with addGlobalActor.)
  *
- * @param aHooks object
- *        An object with preNest and postNest methods for calling
- *        when entering and exiting a nested event loops.
- *
- * @param aAddonID string
- *        ID of the add-on this actor will debug. It will be used to
- *        filter out globals marked for debugging.
+ * @param aParent object
+ *        This actor's parent actor. See ThreadActor for a list of expected
+ *        properties.
  */
-
-function AddonThreadActor(aConnect, aHooks, aAddonID) {
-  this.addonID = aAddonID;
-  ThreadActor.call(this, aHooks);
+function AddonThreadActor(aConnect, aParent) {
+  ThreadActor.call(this, aParent);
 }
 
 AddonThreadActor.prototype = Object.create(ThreadActor.prototype);
 
 update(AddonThreadActor.prototype, {
   constructor: AddonThreadActor,
 
   // A constant prefix that will be used to form the actor ID by the server.
@@ -4930,115 +4788,24 @@ update(AddonThreadActor.prototype, {
    * add-ons.
    */
   _allowSource: function(aSourceURL) {
     // Hide eval scripts
     if (!aSourceURL) {
       return false;
     }
 
-    // XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it
+    // XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it.
     if (aSourceURL == "resource://gre/modules/addons/XPIProvider.jsm") {
       return false;
     }
 
     return true;
   },
 
-  /**
-   * An object that will be used by ThreadActors to tailor their
-   * behaviour depending on the debugging context being required (chrome,
-   * addon or content). The methods that this object provides must
-   * be bound to the ThreadActor before use.
-   */
-  globalManager: {
-    findGlobals: function ADA_findGlobals() {
-      for (let global of this.dbg.findAllGlobals()) {
-        if (this._checkGlobal(global)) {
-          this.dbg.addDebuggee(global);
-        }
-      }
-    },
-
-    /**
-     * A function that the engine calls when a new global object
-     * has been created.
-     *
-     * @param aGlobal Debugger.Object
-     *        The new global object that was created.
-     */
-    onNewGlobal: function ADA_onNewGlobal(aGlobal) {
-      if (this._checkGlobal(aGlobal)) {
-        this.addDebuggee(aGlobal);
-        // Notify the client.
-        this.conn.send({
-          from: this.actorID,
-          type: "newGlobal",
-          // TODO: after bug 801084 lands see if we need to JSONify this.
-          hostAnnotations: aGlobal.hostAnnotations
-        });
-      }
-    }
-  },
-
-  /**
-   * Checks if the provided global belongs to the debugged add-on.
-   *
-   * @param aGlobal Debugger.Object
-   */
-  _checkGlobal: function ADA_checkGlobal(aGlobal) {
-    let obj = null;
-    try {
-      obj = aGlobal.unsafeDereference();
-    }
-    catch (e) {
-      // Because of bug 991399 we sometimes get bad objects here. If we can't
-      // dereference them then they won't be useful to us
-      return false;
-    }
-
-    try {
-      // This will fail for non-Sandbox objects, hence the try-catch block.
-      let metadata = Cu.getSandboxMetadata(obj);
-      if (metadata) {
-        return metadata.addonID === this.addonID;
-      }
-    } catch (e) {
-    }
-
-    if (obj instanceof Ci.nsIDOMWindow) {
-      let id = {};
-      if (mapURIToAddonID(obj.document.documentURIObject, id)) {
-        return id.value === this.addonID;
-      }
-      return false;
-    }
-
-    // Check the global for a __URI__ property and then try to map that to an
-    // add-on
-    let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
-    if (uridescriptor && "value" in uridescriptor && uridescriptor.value) {
-      let uri;
-      try {
-        uri = Services.io.newURI(uridescriptor.value, null, null);
-      }
-      catch (e) {
-        DevToolsUtils.reportException("AddonThreadActor.prototype._checkGlobal",
-                                      new Error("Invalid URI: " + uridescriptor.value));
-        return false;
-      }
-
-      let id = {};
-      if (mapURIToAddonID(uri, id)) {
-        return id.value === this.addonID;
-      }
-    }
-
-    return false;
-  }
 });
 
 exports.AddonThreadActor = AddonThreadActor;
 
 /**
  * Manages the sources for a thread. Handles source maps, locations in the
  * sources, etc for ThreadActors.
  */
--- a/toolkit/devtools/server/actors/tracer.js
+++ b/toolkit/devtools/server/actors/tracer.js
@@ -62,55 +62,58 @@ const TRACE_TYPES = new Set([
   "location",
   "callsite",
   "parameterNames",
   "arguments",
   "depth"
 ]);
 
 /**
- * Creates a TraceActor. TraceActor provides a stream of function
+ * Creates a TracerActor. TracerActor provides a stream of function
  * call/return packets to a remote client gathering a full trace.
  */
-function TraceActor(aConn, aParentActor)
+function TracerActor(aConn, aParent)
 {
+  this._dbg = null;
+  this._parent = aParent;
   this._attached = false;
   this._activeTraces = new MapStack();
   this._totalTraces = 0;
   this._startTime = 0;
+  this._sequence = 0;
+  this._bufferSendTimer = null;
+  this._buffer = [];
 
   // Keep track of how many different trace requests have requested what kind of
   // tracing info. This way we can minimize the amount of data we are collecting
   // at any given time.
   this._requestsForTraceType = Object.create(null);
   for (let type of TRACE_TYPES) {
     this._requestsForTraceType[type] = 0;
   }
 
-  this._sequence = 0;
-  this._bufferSendTimer = null;
-  this._buffer = [];
+  this.onEnterFrame = this.onEnterFrame.bind(this);
   this.onExitFrame = this.onExitFrame.bind(this);
-
-  // aParentActor.window might be an Xray for a window, but it might also be a
-  // double-wrapper for a Sandbox.  We want to unwrap the latter but not the
-  // former.
-  this.global = aParentActor.window;
-  if (!Cu.isXrayWrapper(this.global)) {
-      this.global = this.global.wrappedJSObject;
-  }
 }
 
-TraceActor.prototype = {
+TracerActor.prototype = {
   actorPrefix: "trace",
 
   get attached() { return this._attached; },
   get idle()     { return this._attached && this._activeTraces.size === 0; },
   get tracing()  { return this._attached && this._activeTraces.size > 0; },
 
+  get dbg() {
+    if (!this._dbg) {
+      this._dbg = this._parent.makeDebugger();
+      this._dbg.onEnterFrame = this.onEnterFrame;
+    }
+    return this._dbg;
+  },
+
   /**
    * Buffer traces and only send them every BUFFER_SEND_DELAY milliseconds.
    */
   _send: function(aPacket) {
     this._buffer.push(aPacket);
     if (this._bufferSendTimer === null) {
       this._bufferSendTimer = setTimeout(() => {
         this.conn.send({
@@ -119,102 +122,30 @@ TraceActor.prototype = {
           traces: this._buffer.splice(0, this._buffer.length)
         });
         this._bufferSendTimer = null;
       }, BUFFER_SEND_DELAY);
     }
   },
 
   /**
-   * Initializes a Debugger instance and adds listeners to it.
-   */
-  _initDebugger: function() {
-    this.dbg = new Debugger();
-    this.dbg.onEnterFrame = this.onEnterFrame.bind(this);
-    this.dbg.onNewGlobalObject = this.globalManager.onNewGlobal.bind(this);
-    this.dbg.enabled = false;
-  },
-
-  /**
-   * Add a debuggee global to the Debugger object.
-   */
-  _addDebuggee: function(aGlobal) {
-    try {
-      this.dbg.addDebuggee(aGlobal);
-    } catch (e) {
-      // Ignore attempts to add the debugger's compartment as a debuggee.
-      DevToolsUtils.reportException("TraceActor",
-                      new Error("Ignoring request to add the debugger's "
-                                + "compartment as a debuggee"));
-    }
-  },
-
-  /**
-   * Add the provided window and all windows in its frame tree as debuggees.
-   */
-  _addDebuggees: function(aWindow) {
-    this._addDebuggee(aWindow);
-    let frames = aWindow.frames;
-    if (frames) {
-      for (let i = 0; i < frames.length; i++) {
-        this._addDebuggees(frames[i]);
-      }
-    }
-  },
-
-  /**
-   * An object used by TraceActors to tailor their behavior depending
-   * on the debugging context required (chrome or content).
-   */
-  globalManager: {
-    /**
-     * Adds all globals in the global object as debuggees.
-     */
-    findGlobals: function() {
-      this._addDebuggees(this.global);
-    },
-
-    /**
-     * A function that the engine calls when a new global object has been
-     * created. Adds the global object as a debuggee if it is in the content
-     * window.
-     *
-     * @param aGlobal Debugger.Object
-     *        The new global object that was created.
-     */
-    onNewGlobal: function(aGlobal) {
-      // Content debugging only cares about new globals in the content
-      // window, like iframe children.
-      if (aGlobal.hostAnnotations &&
-          aGlobal.hostAnnotations.type == "document" &&
-          aGlobal.hostAnnotations.element === this.global) {
-        this._addDebuggee(aGlobal);
-      }
-    },
-  },
-
-  /**
    * Handle a protocol request to attach to the trace actor.
    *
    * @param aRequest object
    *        The protocol request object.
    */
   onAttach: function(aRequest) {
     if (this.attached) {
       return {
         error: "wrongState",
         message: "Already attached to a client"
       };
     }
 
-    if (!this.dbg) {
-      this._initDebugger();
-      this.globalManager.findGlobals.call(this);
-    }
-
+    this.dbg.addDebuggees();
     this._attached = true;
 
     return {
       type: "attached",
       traceTypes: Object.keys(this._requestsForTraceType)
         .filter(k => !!this._requestsForTraceType[k])
     };
   },
@@ -225,20 +156,22 @@ TraceActor.prototype = {
    * @param aRequest object
    *        The protocol request object.
    */
   onDetach: function() {
     while (this.tracing) {
       this.onStopTrace();
     }
 
-    this.dbg = null;
+    this._dbg = null;
+    this._attached = false;
 
-    this._attached = false;
-    return { type: "detached" };
+    return {
+      type: "detached"
+    };
   },
 
   /**
    * Handle a protocol request to start a new trace.
    *
    * @param aRequest object
    *        The protocol request object.
    */
@@ -302,17 +235,21 @@ TraceActor.prototype = {
     for (let traceType of stoppedTraceTypes) {
       this._requestsForTraceType[traceType]--;
     }
 
     if (this.idle) {
       this.dbg.enabled = false;
     }
 
-    return { type: "stoppedTrace", why: "requested", name: name };
+    return {
+      type: "stoppedTrace",
+      why: "requested",
+      name
+    };
   },
 
   // JS Debugger API hooks.
 
   /**
    * Called by the engine when a frame is entered. Sends an unsolicited packet
    * to the client carrying requested trace information.
    *
@@ -438,29 +375,29 @@ TraceActor.prototype = {
 
     this._send(packet);
   }
 };
 
 /**
  * The request types this actor can handle.
  */
-TraceActor.prototype.requestTypes = {
-  "attach": TraceActor.prototype.onAttach,
-  "detach": TraceActor.prototype.onDetach,
-  "startTrace": TraceActor.prototype.onStartTrace,
-  "stopTrace": TraceActor.prototype.onStopTrace
+TracerActor.prototype.requestTypes = {
+  "attach": TracerActor.prototype.onAttach,
+  "detach": TracerActor.prototype.onDetach,
+  "startTrace": TracerActor.prototype.onStartTrace,
+  "stopTrace": TracerActor.prototype.onStopTrace
 };
 
 exports.register = function(handle) {
-  handle.addTabActor(TraceActor, "traceActor");
+  handle.addTabActor(TracerActor, "traceActor");
 };
 
 exports.unregister = function(handle) {
-  handle.removeTabActor(TraceActor, "traceActor");
+  handle.removeTabActor(TracerActor, "traceActor");
 };
 
 
 /**
  * MapStack is a collection of key/value pairs with stack ordering,
  * where keys are strings and values are any JS value. In addition to
  * the push and pop stack operations, supports a "delete" operation,
  * which removes the value associated with a given key from any
@@ -605,17 +542,17 @@ function createValueSnapshot(aValue, aDe
     case "object":
       if (aValue === null) {
         return { type: "null" };
       }
       return aDetailed
         ? detailedObjectSnapshot(aValue)
         : objectSnapshot(aValue);
     default:
-      DevToolsUtils.reportException("TraceActor",
+      DevToolsUtils.reportException("TracerActor",
                       new Error("Failed to provide a grip for: " + aValue));
       return null;
   }
 }
 
 /**
  * Create a very minimal snapshot of the given debuggee object.
  *
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/utils/make-debugger.js
@@ -0,0 +1,100 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; 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 EventEmitter = require("devtools/toolkit/event-emitter");
+const Debugger = require("Debugger");
+
+const { reportException } = require("devtools/toolkit/DevToolsUtils");
+
+/**
+ * Multiple actors that use a |Debugger| instance come in a few versions, each
+ * with a different set of debuggees. One version for content tabs (globals
+ * within a tab), one version for chrome debugging (all globals), and sometimes
+ * a third version for addon debugging (chrome globals the addon is loaded in
+ * and content globals the addon injects scripts into). The |makeDebugger|
+ * function helps us avoid repeating the logic for finding and maintaining the
+ * correct set of globals for a given |Debugger| instance across each version of
+ * all of our actors.
+ *
+ * The |makeDebugger| function expects a single object parameter with the
+ * following properties:
+ *
+ * @param Function findDebuggees
+ *        Called with one argument: a |Debugger| instance. This function should
+ *        return an iterable of globals to be added to the |Debugger|
+ *        instance. The globals may be wrapped in a |Debugger.Object|, or
+ *        unwrapped.
+ *
+ * @param Function shouldAddNewGlobalAsDebuggee
+ *        Called with one argument: a |Debugger.Object| wrapping a global
+ *        object. This function must return |true| if the global object should
+ *        be added as debuggee, and |false| otherwise.
+ *
+ * @returns Debugger
+ *          Returns a |Debugger| instance that can manage its set of debuggee
+ *          globals itself and is decorated with the |EventEmitter| class.
+ *
+ *          Events emitted by the returned |Debugger| instance:
+ *
+ *            - "newGlobal": Emitted when a new global has been added as a
+ *               debuggee. Passes the |Debugger.Object| wrapping the new
+ *               debuggee global to listeners.
+ *
+ *          Existing |Debugger| properties set on the returned |Debugger|
+ *          instance:
+ *
+ *            - onNewGlobalObject: The |Debugger| will automatically add new
+ *              globals as debuggees if calling |shouldAddNewGlobalAsDebuggee|
+ *              with the global returns true.
+ *
+ *            - uncaughtExceptionHook: The |Debugger| already has an error
+ *              reporter attached to |uncaughtExceptionHook|, so if any
+ *              |Debugger| hooks fail, the error will be reported.
+ *
+ *          New properties set on the returned |Debugger| instance:
+ *
+ *            - addDebuggees: A function which takes no arguments. It adds all
+ *              current globals that should be debuggees (as determined by
+ *              |findDebuggees|) to the |Debugger| instance.
+ */
+module.exports = function makeDebugger({ findDebuggees, shouldAddNewGlobalAsDebuggee }) {
+  const dbg = new Debugger();
+  EventEmitter.decorate(dbg);
+
+  dbg.uncaughtExceptionHook = reportDebuggerHookException;
+
+  dbg.onNewGlobalObject = global => {
+    if (shouldAddNewGlobalAsDebuggee(global)) {
+      safeAddDebuggee(dbg, global);
+    }
+  };
+
+  dbg.addDebuggees = () => {
+    for (let global of findDebuggees(dbg)) {
+      safeAddDebuggee(dbg, global);
+    }
+  };
+
+  return dbg;
+};
+
+const reportDebuggerHookException = e => reportException("Debugger Hook", e);
+
+/**
+ * Add |global| as a debuggee to |dbg|, handling error cases.
+ */
+function safeAddDebuggee(dbg, global) {
+  try {
+    let wrappedGlobal = dbg.addDebuggee(global);
+    if (wrappedGlobal) {
+      dbg.emit("newGlobal", wrappedGlobal);
+    }
+  } catch (e) {
+    // Ignoring attempt to add the debugger's compartment as a debuggee.
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/utils/map-uri-to-addon-id.js
@@ -0,0 +1,47 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; 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 DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
+const Services = require("Services");
+const { Cc, Ci } = require("chrome");
+
+Object.defineProperty(this, "addonManager", {
+  get: (function () {
+    let cached;
+    return () => cached
+      ? cached
+      : (cached = Cc["@mozilla.org/addons/integration;1"]
+                    .getService(Ci.amIAddonManager))
+  }())
+});
+
+const B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
+
+/**
+ * This is a wrapper around amIAddonManager.mapURIToAddonID which always returns
+ * false on B2G to avoid loading the add-on manager there and reports any
+ * exceptions rather than throwing so that the caller doesn't have to worry
+ * about them.
+ */
+module.exports = function mapURIToAddonID(uri, id) {
+  if (!Services.appinfo
+      || Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT
+      || Services.appinfo.ID == B2G_ID
+      || !uri
+      || !addonManager) {
+    return false;
+  }
+
+  try {
+    return addonManager.mapURIToAddonID(uri, id);
+  }
+  catch (e) {
+    DevToolsUtils.reportException("mapURIToAddonID", e);
+    return false;
+  }
+};
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -9,16 +9,18 @@
 let { Ci, Cu } = require("chrome");
 let Services = require("Services");
 let { ActorPool, createExtraActors, appendExtraActors } = require("devtools/server/actors/common");
 let { RootActor } = require("devtools/server/actors/root");
 let { AddonThreadActor, ThreadActor } = require("devtools/server/actors/script");
 let { DebuggerServer } = require("devtools/server/main");
 let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 let { dbg_assert } = DevToolsUtils;
+let makeDebugger = require("./utils/make-debugger");
+let mapURIToAddonID = require("./utils/map-uri-to-addon-id");
 
 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 
 // Assumptions on events module:
 // events needs to be dispatched synchronously,
@@ -67,16 +69,39 @@ function sendShutdownEvent() {
     evt.initEvent("Debugger:Shutdown", true, false);
     win.document.documentElement.dispatchEvent(evt);
   }
 }
 
 exports.sendShutdownEvent = sendShutdownEvent;
 
 /**
+ * Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has
+ * become a dead object, return |undefined|.
+ *
+ * @param Debugger.Object wrappedGlobal
+ *        The |Debugger.Object| which wraps a global.
+ *
+ * @returns {Object|undefined}
+ *          Returns the unwrapped global object or |undefined| if unwrapping
+ *          failed.
+ */
+const unwrapDebuggerObjectGlobal = wrappedGlobal => {
+  let global;
+  try {
+    global = wrappedGlobal.unsafeDereference();
+  }
+  catch (e) {
+    // Because of bug 991399 we sometimes get bad objects here. If we
+    // can't dereference them then they won't be useful to us.
+  }
+  return global;
+};
+
+/**
  * Construct a root actor appropriate for use in a server running in a
  * browser. The returned root actor:
  * - respects the factories registered with DebuggerServer.addGlobalActor,
  * - uses a BrowserTabList to supply tab actors,
  * - sends all navigator:browser window documents a Debugger:Shutdown event
  *   when it exits.
  *
  * * @param aConnection DebuggerServerConnection
@@ -515,16 +540,23 @@ exports.BrowserTabList = BrowserTabList;
 function TabActor(aConnection)
 {
   this.conn = aConnection;
   this._tabActorPool = null;
   // A map of actor names to actor instances provided by extensions.
   this._extraActors = {};
   this._exited = false;
 
+  this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this);
+
+  this.makeDebugger = makeDebugger.bind(null, {
+    findDebuggees: () => this.windows,
+    shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee
+  });
+
   this.traits = { reconfigure: true };
 }
 
 // XXX (bug 710213): TabActor attach/detach/exit/disconnect is a
 // *complete* mess, needs to be rethought asap.
 
 TabActor.prototype = {
   traits: null,
@@ -710,16 +742,47 @@ TabActor.prototype = {
     if (this._detach()) {
       this.conn.send({ from: this.actorID,
                        type: "tabDetached" });
     }
 
     this._exited = true;
   },
 
+  /**
+   * Return true if the given global is associated with this tab and should be
+   * added as a debuggee, false otherwise.
+   */
+  _shouldAddNewGlobalAsDebuggee: function (wrappedGlobal) {
+    if (wrappedGlobal.hostAnnotations &&
+        wrappedGlobal.hostAnnotations.type == "document" &&
+        wrappedGlobal.hostAnnotations.element === this.window) {
+      return true;
+    }
+
+    let global = unwrapDebuggerObjectGlobal(wrappedGlobal);
+    if (!global) {
+      return false;
+    }
+
+    // Check if the global is a sdk page-mod sandbox.
+    let metadata = {};
+    let id = "";
+    try {
+      id = getInnerId(this.window);
+      metadata = Cu.getSandboxMetadata(global);
+    }
+    catch (e) {}
+    if (metadata["inner-window-id"] && metadata["inner-window-id"] == id) {
+      return true;
+    }
+
+    return false;
+  },
+
   /* Support for DebuggerServer.addTabActor. */
   _createExtraActors: createExtraActors,
   _appendExtraActors: appendExtraActors,
 
   /**
    * Does the actual work of attching to a tab.
    */
   _attach: function BTA_attach() {
@@ -994,17 +1057,17 @@ TabActor.prototype = {
         threadActor.global = window;
         threadActor.maybePauseOnExceptions();
       }
     }
 
     // Refresh the debuggee list when a new window object appears (top window or
     // iframe).
     if (threadActor.attached) {
-      threadActor.findGlobals();
+      threadActor.dbg.addDebuggees();
     }
   },
 
   _windowDestroyed: function (window) {
     events.emit(this, "window-destroyed", {
       window: window,
       isTopLevel: window == this.window
     });
@@ -1282,16 +1345,24 @@ exports.BrowserAddonList = BrowserAddonL
 
 function BrowserAddonActor(aConnection, aAddon) {
   this.conn = aConnection;
   this._addon = aAddon;
   this._contextPool = new ActorPool(this.conn);
   this.conn.addActorPool(this._contextPool);
   this._threadActor = null;
   this._global = null;
+
+  this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this);
+
+  this.makeDebugger = makeDebugger.bind(null, {
+    findDebuggees: this._findDebuggees.bind(this),
+    shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee
+  });
+
   AddonManager.addAddonListener(this);
 }
 
 BrowserAddonActor.prototype = {
   actorPrefix: "addon",
 
   get exited() {
     return !this._addon;
@@ -1368,18 +1439,17 @@ BrowserAddonActor.prototype = {
   },
 
   onAttach: function BAA_onAttach() {
     if (this.exited) {
       return { type: "exited" };
     }
 
     if (!this.attached) {
-      this._threadActor = new AddonThreadActor(this.conn, this,
-                                               this._addon.id);
+      this._threadActor = new AddonThreadActor(this.conn, this);
       this._contextPool.addActor(this._threadActor);
     }
 
     return { type: "tabAttached", threadActor: this._threadActor.actorID };
   },
 
   onDetach: function BAA_onDetach() {
     if (!this.attached) {
@@ -1408,16 +1478,71 @@ BrowserAddonActor.prototype = {
     let e = Services.wm.getEnumerator(null);
     while (e.hasMoreElements()) {
       let win = e.getNext();
       let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
       windowUtils.resumeTimeouts();
       windowUtils.suppressEventHandling(false);
     }
+  },
+
+  /**
+   * Return true if the given global is associated with this addon and should be
+   * added as a debuggee, false otherwise.
+   */
+  _shouldAddNewGlobalAsDebuggee: function (aGlobal) {
+    const global = unwrapDebuggerObjectGlobal(aGlobal);
+    try {
+      // This will fail for non-Sandbox objects, hence the try-catch block.
+      let metadata = Cu.getSandboxMetadata(global);
+      if (metadata) {
+        return metadata.addonID === this.id;
+      }
+    } catch (e) {}
+
+    if (global instanceof Ci.nsIDOMWindow) {
+      let id = {};
+      if (mapURIToAddonID(global.document.documentURIObject, id)) {
+        return id.value === this.id;
+      }
+      return false;
+    }
+
+    // Check the global for a __URI__ property and then try to map that to an
+    // add-on
+    let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
+    if (uridescriptor && "value" in uridescriptor && uridescriptor.value) {
+      let uri;
+      try {
+        uri = Services.io.newURI(uridescriptor.value, null, null);
+      }
+      catch (e) {
+        DevToolsUtils.reportException(
+          "BrowserAddonActor.prototype._shouldAddNewGlobalAsDebuggee",
+          new Error("Invalid URI: " + uridescriptor.value)
+        );
+        return false;
+      }
+
+      let id = {};
+      if (mapURIToAddonID(uri, id)) {
+        return id.value === this.id;
+      }
+    }
+
+    return false;
+  },
+
+  /**
+   * Yield the current set of globals associated with this addon that should be
+   * added as debuggees.
+   */
+  _findDebuggees: function (dbg) {
+    return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
   }
 };
 
 BrowserAddonActor.prototype.requestTypes = {
   "attach": BrowserAddonActor.prototype.onAttach,
   "detach": BrowserAddonActor.prototype.onDetach
 };
 
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const { DebuggerServer, ActorPool } = require("devtools/server/main");
 const { EnvironmentActor, LongStringActor, ObjectActor, ThreadActor } = require("devtools/server/actors/script");
 const { update } = require("devtools/toolkit/DevToolsUtils");
-const Debugger = require("Debugger");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => {
   return require("devtools/toolkit/webconsole/network-monitor")
          .NetworkMonitor;
@@ -62,17 +61,17 @@ function WebConsoleActor(aConnection, aP
   this.conn = aConnection;
   this.parentActor = aParentActor;
 
   this._actorPool = new ActorPool(this.conn);
   this.conn.addActorPool(this._actorPool);
 
   this._prefs = {};
 
-  this.dbg = new Debugger();
+  this.dbg = this.parentActor.makeDebugger();
 
   this._netEvents = new Map();
   this._gripDepth = 0;
 
   this._onWillNavigate = this._onWillNavigate.bind(this);
   this._onObserverNotification = this._onObserverNotification.bind(this);
   if (this.parentActor.isRootActor) {
     Services.obs.addObserver(this._onObserverNotification,
@@ -1006,17 +1005,16 @@ WebConsoleActor.prototype =
     // as ordinary objects, not as references to be followed, so mixing
     // debuggers causes strange behaviors.)
     let dbg = frame ? frameActor.threadActor.dbg : this.dbg;
     let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow);
 
     // If we have an object to bind to |_self|, create a Debugger.Object
     // referring to that object, belonging to dbg.
     let bindSelf = null;
-    let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow);
     if (aOptions.bindObjectActor) {
       let objActor = this.getActorByID(aOptions.bindObjectActor);
       if (objActor) {
         let jsObj = objActor.obj.unsafeDereference();
         // If we use the makeDebuggeeValue method of jsObj's own global, then
         // we'll get a D.O that sees jsObj as viewed from its own compartment -
         // that is, without wrappers. The evalWithBindings call will then wrap
         // jsObj appropriately for the evaluation compartment.
--- a/toolkit/devtools/server/tests/unit/head_dbg.js
+++ b/toolkit/devtools/server/tests/unit/head_dbg.js
@@ -94,17 +94,18 @@ let listener = {
       try {
         var string = "" + aMessage.message;
       } catch (x) {
         var string = "<error converting error message to string>";
       }
     }
 
     // Make sure we exit all nested event loops so that the test can finish.
-    while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
+    while (DebuggerServer.xpcInspector
+           && DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
       DebuggerServer.xpcInspector.exitNestedEventLoop();
     }
 
     // In the world before bug 997440, exceptions were getting lost because of
     // the arbitrary JSContext being used in nsXPCWrappedJSClass::CallMethod.
     // In the new world, the wanderers have returned. However, because of the,
     // currently very-broken, exception reporting machinery in XPCWrappedJSClass
     // these get reported as errors to the console, even if there's actually JS
--- a/toolkit/devtools/server/tests/unit/test_trace_actor-04.js
+++ b/toolkit/devtools/server/tests/unit/test_trace_actor-04.js
@@ -70,17 +70,19 @@ function test_enter_exit_frame()
     .then(function() {
       do_check_eq(traceNames[2], "baz",
                   'Should have entered "baz" frame in third packet');
       do_check_eq(traceNames[3], "bar",
                   'Should have entered "bar" frame in fourth packet');
       do_check_eq(traceNames[4], "foo",
                   'Should have entered "foo" frame in fifth packet');
       finishClient(gClient);
-    });
+    })
+    .then(null, e => DevToolsUtils.reportException("test_trace_actor-04.js",
+                                                   e));
 }
 
 function start_trace()
 {
   let deferred = promise.defer();
   gTraceClient.startTrace(["name"], null, function() { deferred.resolve(); });
   return deferred.promise;
 }
--- a/toolkit/devtools/server/tests/unit/testactors.js
+++ b/toolkit/devtools/server/tests/unit/testactors.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/script");
 const { DebuggerServer } = require("devtools/server/main");
 const promise = require("promise");
+const makeDebugger = require("devtools/server/actors/utils/make-debugger");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(aGlobal) {
   gTestGlobals.push(aGlobal);
 };
 
 // A mock tab list, for use by tests. This simply presents each global in
 // gTestGlobals as a tab, and the list is fixed: it never calls its
@@ -61,16 +62,23 @@ function TestTabActor(aConnection, aGlob
 {
   this.conn = aConnection;
   this._global = aGlobal;
   this._global.wrappedJSObject = aGlobal;
   this._threadActor = new ThreadActor(this, this._global);
   this.conn.addActor(this._threadActor);
   this._attached = false;
   this._extraActors = {};
+  this.makeDebugger = makeDebugger.bind(null, {
+    findDebuggees: () => [this._global],
+    shouldAddNewGlobalAsDebuggee: g => g.hostAnnotations &&
+                                       g.hostAnnotations.type == "document" &&
+                                       g.hostAnnotations.element === this._global
+
+  });
 }
 
 TestTabActor.prototype = {
   constructor: TestTabActor,
   actorPrefix: "TestTabActor",
 
   get window() {
     return { wrappedJSObject: this._global };