Backed out 7 changesets (bug 1473513) for failing devtools e.g. leakcheck | default process: 1618727 bytes leaked
authorarthur.iakab <aiakab@mozilla.com>
Wed, 22 Aug 2018 18:23:46 +0300
changeset 481234 98b5ff9533ee519674b8c441083470d91e1080f8
parent 481233 7472e52f6d1288b773d771918cd23ed951464bf5
child 481235 15ee3d748ec809e28d05b7a3ccc8e4d023f91f74
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
bugs1473513, 1618727
milestone63.0a1
backs outdeb8812556ef956417c81f6db7df15ff04dbbe9f
5bf38cfa04f93fbf001a1625735f9787d232e760
6e157bea362acd8a51fdf4863ebb38dbe6e74df6
12eb1139a80286f7ee6adf378c421cee981a454f
ce86ea60a31cc1944347306e28aaff1fbff38f59
7acc52a7f81f9a69eaa768ce13768cd7fb75b4da
4e1e283b347e15910c87610577ffeba931de3eeb
Backed out 7 changesets (bug 1473513) for failing devtools e.g. leakcheck | default process: 1618727 bytes leaked Backed out changeset deb8812556ef (bug 1473513) Backed out changeset 5bf38cfa04f9 (bug 1473513) Backed out changeset 6e157bea362a (bug 1473513) Backed out changeset 12eb1139a802 (bug 1473513) Backed out changeset ce86ea60a31c (bug 1473513) Backed out changeset 7acc52a7f81f (bug 1473513) Backed out changeset 4e1e283b347e (bug 1473513)
devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js
devtools/client/debugger/test/mochitest/head.js
devtools/client/shared/redux/middleware/log.js
devtools/client/shared/test/shared-head.js
devtools/docs/backend/actor-registration.md
devtools/docs/backend/protocol.js.md
devtools/server/actor-registry.js
devtools/server/actors/common.js
devtools/server/actors/root.js
devtools/server/actors/targets/browsing-context.js
devtools/server/actors/targets/content-process.js
devtools/server/actors/utils/actor-registry-utils.js
devtools/server/actors/webbrowser.js
devtools/server/main.js
devtools/server/moz.build
devtools/server/tests/browser/browser_actor_error.js
devtools/server/tests/browser/head.js
devtools/server/tests/mochitest/test_connectToFrame.html
devtools/server/tests/unit/head_dbg.js
devtools/server/tests/unit/test_add_actors.js
devtools/server/tests/unit/test_client_request.js
devtools/server/tests/unit/test_promises_actor_attach.js
devtools/server/tests/unit/test_promises_actor_exist.js
devtools/server/tests/unit/test_protocol_children.js
devtools/server/tests/unit/test_registerClient.js
devtools/server/tests/unit/test_register_actor.js
devtools/server/tests/unit/testactors.js
devtools/shared/moz.build
devtools/shared/protocol.js
devtools/shared/protocol/lazy-pool.js
devtools/shared/protocol/moz.build
devtools/shared/security/tests/unit/testactors.js
devtools/shared/transport/tests/unit/head_dbg.js
devtools/shared/transport/tests/unit/test_bulk_error.js
devtools/shared/transport/tests/unit/test_client_server_bulk.js
devtools/shared/transport/tests/unit/testactors-no-bulk.js
devtools/shared/transport/tests/unit/testactors.js
mobile/android/modules/dbg-browser-actors.js
testing/talos/talos/tests/devtools/addon/content/tests/server/protocol.js
testing/xpcshell/dbg-actors.js
--- a/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js
@@ -8,17 +8,17 @@
  */
 
 const ACTORS_URL = CHROME_URL + "testactors.js";
 
 add_task(async function() {
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
 
-  ActorRegistry.registerModule(ACTORS_URL, {
+  DebuggerServer.registerModule(ACTORS_URL, {
     prefix: "testOne",
     constructor: "TestActor1",
     type: { global: true },
   });
 
   const transport = DebuggerServer.connectPipe();
   const client = new DebuggerClient(transport);
   const [type] = await client.connect();
@@ -39,21 +39,19 @@ add_task(async function() {
   is(response.pong, "pong", "Actor should respond to requests.");
 
   // Make sure that lazily-created actors are created only once.
   let count = 0;
   for (const connID of Object.getOwnPropertyNames(DebuggerServer._connections)) {
     const conn = DebuggerServer._connections[connID];
     const actorPrefix = conn._prefix + "testOne";
     for (let pool of conn._extraPools) {
-      for (const actor of pool.poolChildren()) {
-        if (actor.actorID.startsWith(actorPrefix)) {
-          count++;
-        }
-      }
+      count += Object.keys(pool._actors).filter(e => {
+        return e.startsWith(actorPrefix);
+      }).length;
     }
   }
 
   is(count, 1,
     "Only one actor exists in all pools. One global actor.");
 
   await client.close();
 });
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -11,17 +11,16 @@ Services.scriptloader.loadSubScript("chr
 // Disable logging for faster test runs. Set this pref to true if you want to
 // debug a test in your try runs. Both the debugger server and frontend will
 // be affected by this pref.
 var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 var { BrowserToolboxProcess } = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
 var { DebuggerServer } = require("devtools/server/main");
-var { ActorRegistry } = require("devtools/server/actor-registry");
 var { DebuggerClient } = require("devtools/shared/client/debugger-client");
 var ObjectClient = require("devtools/shared/client/object-client");
 var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm", {});
 var EventEmitter = require("devtools/shared/event-emitter");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 var { Task } = require("devtools/shared/task");
 
 const chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
--- a/devtools/client/shared/redux/middleware/log.js
+++ b/devtools/client/shared/redux/middleware/log.js
@@ -5,27 +5,17 @@
 
 /**
  * A middleware that logs all actions coming through the system
  * to the console.
  */
 function log({ dispatch, getState }) {
   return next => action => {
     try {
-      // whitelist of fields, rather than printing the whole object
-      console.log("[DISPATCH] action type:", action.type);
-      /*
-       * USE WITH CAUTION!! This will output everything from an action object,
-       * and these can be quite large. Printing out large objects will slow
-       * down tests and cause test failures
-       *
-       * console.log("[DISPATCH]", JSON.stringify(action, null, 2));
-       */
+      console.log("[DISPATCH]", JSON.stringify(action, null, 2));
     } catch (e) {
-      // this occurs if JSON.stringify throws.
-      console.warn(e);
       console.log("[DISPATCH]", action);
     }
     next(action);
   };
 }
 
 exports.log = log;
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -729,17 +729,17 @@ async function injectEventUtilsInContent
 async function enableWebComponents() {
   await pushPref("dom.webcomponents.shadowdom.enabled", true);
   await pushPref("dom.webcomponents.customelements.enabled", true);
 }
 
 /*
  * Register an actor in the content process of the current tab.
  *
- * Calling ActorRegistry.registerModule only registers the actor in the current process.
+ * Calling DebuggerServer.registerModule only registers the actor in the current process.
  * As all test scripts are ran in the parent process, it is only registered here.
  * This function helps register them in the content process used for the current tab.
  *
  * @param {string} url
  *        Actor module URL or absolute require path
  * @param {json} options
  *        Arguments to be passed to DebuggerServer.registerModule
  */
@@ -750,12 +750,12 @@ async function registerActorInContentPro
              .convertChromeURL(Services.io.newURI(uri)).spec;
   }
   // chrome://mochitests URI is registered only in the parent process, so convert these
   // URLs to file:// one in order to work in the content processes
   url = url.startsWith("chrome://mochitests") ? convertChromeToFile(url) : url;
   return ContentTask.spawn(gBrowser.selectedBrowser, { url, options }, args => {
     // eslint-disable-next-line no-shadow
     const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
-    const { ActorRegistry } = require("devtools/server/actor-registry");
-    ActorRegistry.registerModule(args.url, args.options);
+    const { DebuggerServer } = require("devtools/server/main");
+    DebuggerServer.registerModule(args.url, args.options);
   });
 }
--- a/devtools/docs/backend/actor-registration.md
+++ b/devtools/docs/backend/actor-registration.md
@@ -3,37 +3,37 @@
 ## Target-scoped actors vs. global actors
 
 Target-scoped actors are the most common types of actors. That's the type of actors you will most probably be adding.
 
 Target-scoped actors target a document, this could be a tab in Firefox or a remote document in Firefox for Android.
 
 Global actors however are for the rest, for things not related to any particular document but instead for things global to the whole Firefox/Chrome/Safari instance the toolbox is connected to (e.g. the preference actor).
 
-## The ActorRegistry.registerModule function
+## The DebuggerServer.registerModule function
 
 To register a target-scoped actor:
 
 ```
-ActorRegistry.registerModule("devtools/server/actors/webconsole", {
+DebuggerServer.registerModule("devtools/server/actors/webconsole", {
   prefix: "console",
   constructor: "WebConsoleActor",
   type: { target: true }
 });
 ```
 
 To register a global actor:
 
 ```
-ActorRegistry.registerModule("devtools/server/actors/addon/addons", {
+DebuggerServer.registerModule("devtools/server/actors/addon/addons", {
   prefix: "addons",
   constructor: "AddonsActor",
   type: { global: true }
 });
 ```
 
-If you are adding a new built-in actor, you should be registering it using `ActorRegistry.registerModule` in `addBrowserActors` or `addTargetScopedActors` in `/devtools/server/actor-registry.js`.
+If you are adding a new built-in actor, you should be registering it using `DebuggerServer.registerModule` in `_addBrowserActors` or `_addTargetScopedActors` in `/devtools/server/main.js`.
 
 ## A note about lazy registration
 
-The `ActorRegistry` loads and creates all of the actors lazily to keep the initial memory usage down (which is extremely important on lower end devices).
+The `DebuggerServer` loads and creates all of the actors lazily to keep the initial memory usage down (which is extremely important on lower end devices).
 
 It becomes especially important when debugging pages with e10s when there are more than one process, because that's when we need to spawn a `DebuggerServer` per process (it may not be immediately obvious that the server in the main process is mostly only here for piping messages to the actors in the child process).
--- a/devtools/docs/backend/protocol.js.md
+++ b/devtools/docs/backend/protocol.js.md
@@ -46,17 +46,17 @@ The actor implementation would go somewh
       sayHello: function () {
        return "hello";
       },
     });
 
     // You also need to export the actor class in your module for discovery.
     exports.HelloActor = HelloActor;
 
-To activate your actor, register it in the `addBrowserActors` method in `server/actor-registry.js`.
+To activate your actor, register it in the `_addBrowserActors` method in `server/main.js`.
 The registration code would look something like this:
 
     this.registerModule("devtools/server/actors/hello-world", {
       prefix: "hello",
       constructor: "HelloActor",
       type: { global: true }
     });
 
deleted file mode 100644
--- a/devtools/server/actor-registry.js
+++ /dev/null
@@ -1,415 +0,0 @@
-/* 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";
-
-var Services = require("Services");
-var { Ci } = require("chrome");
-var gRegisteredModules = Object.create(null);
-
-const ActorRegistry = {
-  globalActorFactories: {},
-  targetScopedActorFactories: {},
-  init(connections) {
-    this._connections = connections;
-  },
-
-  /**
-   * Register a CommonJS module with the debugger server.
-   * @param id string
-   *        The ID of a CommonJS module.
-   *        The actor is going to be registered immediately, but loaded only
-   *        when a client starts sending packets to an actor with the same id.
-   *
-   * @param options object
-   *        An object with 3 mandatory attributes:
-   *        - prefix (string):
-   *          The prefix of an actor is used to compute:
-   *          - the `actorID` of each new actor instance (ex: prefix1).
-   *            (See ActorPool.addActor)
-   *          - the actor name in the listTabs request. Sending a listTabs
-   *            request to the root actor returns actor IDs. IDs are in
-   *            dictionaries, with actor names as keys and actor IDs as values.
-   *            The actor name is the prefix to which the "Actor" string is
-   *            appended. So for an actor with the `console` prefix, the actor
-   *            name will be `consoleActor`.
-   *        - constructor (string):
-   *          the name of the exported symbol to be used as the actor
-   *          constructor.
-   *        - type (a dictionary of booleans with following attribute names):
-   *          - "global"
-   *            registers a global actor instance, if true.
-   *            A global actor has the root actor as its parent.
-   *          - "target"
-   *            registers a target-scoped actor instance, if true.
-   *            A new actor will be created for each target, such as a tab.
-   */
-  registerModule(id, options) {
-    if (id in gRegisteredModules) {
-      return;
-    }
-
-    if (!options) {
-      throw new Error("ActorRegistry.registerModule requires an options argument");
-    }
-    const {prefix, constructor, type} = options;
-    if (typeof (prefix) !== "string") {
-      throw new Error(`Lazy actor definition for '${id}' requires a string ` +
-                      `'prefix' option.`);
-    }
-    if (typeof (constructor) !== "string") {
-      throw new Error(`Lazy actor definition for '${id}' requires a string ` +
-                      `'constructor' option.`);
-    }
-    if (!("global" in type) && !("target" in type)) {
-      throw new Error(`Lazy actor definition for '${id}' requires a dictionary ` +
-                      `'type' option whose attributes can be 'global' or 'target'.`);
-    }
-    const name = prefix + "Actor";
-    const mod = {
-      id,
-      prefix,
-      constructorName: constructor,
-      type,
-      globalActor: type.global,
-      targetScopedActor: type.target
-    };
-    gRegisteredModules[id] = mod;
-    if (mod.targetScopedActor) {
-      this.addTargetScopedActor(mod, name);
-    }
-    if (mod.globalActor) {
-      this.addGlobalActor(mod, name);
-    }
-  },
-
-  /**
-   * Unregister a previously-loaded CommonJS module from the debugger server.
-   */
-  unregisterModule(id) {
-    const mod = gRegisteredModules[id];
-    if (!mod) {
-      throw new Error("Tried to unregister a module that was not previously registered.");
-    }
-
-    // Lazy actors
-    if (mod.targetScopedActor) {
-      this.removeTargetScopedActor(mod);
-    }
-    if (mod.globalActor) {
-      this.removeGlobalActor(mod);
-    }
-
-    delete gRegisteredModules[id];
-  },
-
-  /**
-   * Install Firefox-specific actors.
-   *
-   * /!\ Be careful when adding a new actor, especially global actors.
-   * Any new global actor will be exposed and returned by the root actor.
-   */
-  addBrowserActors() {
-    this.registerModule("devtools/server/actors/preference", {
-      prefix: "preference",
-      constructor: "PreferenceActor",
-      type: { global: true }
-    });
-    this.registerModule("devtools/server/actors/actor-registry", {
-      prefix: "actorRegistry",
-      constructor: "ActorRegistryActor",
-      type: { global: true }
-    });
-    this.registerModule("devtools/server/actors/addon/addons", {
-      prefix: "addons",
-      constructor: "AddonsActor",
-      type: { global: true }
-    });
-    this.registerModule("devtools/server/actors/device", {
-      prefix: "device",
-      constructor: "DeviceActor",
-      type: { global: true }
-    });
-    this.registerModule("devtools/server/actors/heap-snapshot-file", {
-      prefix: "heapSnapshotFile",
-      constructor: "HeapSnapshotFileActor",
-      type: { global: true }
-    });
-    // Always register this as a global module, even while there is a pref turning
-    // on and off the other performance actor. This actor shouldn't conflict with
-    // the other one. These are also lazily loaded so there shouldn't be a performance
-    // impact.
-    this.registerModule("devtools/server/actors/perf", {
-      prefix: "perf",
-      constructor: "PerfActor",
-      type: { global: true }
-    });
-  },
-
-  /**
-   * Install target-scoped actors.
-   */
-  addTargetScopedActors() {
-    this.registerModule("devtools/server/actors/webconsole", {
-      prefix: "console",
-      constructor: "WebConsoleActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/inspector/inspector", {
-      prefix: "inspector",
-      constructor: "InspectorActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/call-watcher", {
-      prefix: "callWatcher",
-      constructor: "CallWatcherActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/canvas", {
-      prefix: "canvas",
-      constructor: "CanvasActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/webgl", {
-      prefix: "webgl",
-      constructor: "WebGLActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/webaudio", {
-      prefix: "webaudio",
-      constructor: "WebAudioActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/stylesheets", {
-      prefix: "styleSheets",
-      constructor: "StyleSheetsActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/storage", {
-      prefix: "storage",
-      constructor: "StorageActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/gcli", {
-      prefix: "gcli",
-      constructor: "GcliActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/memory", {
-      prefix: "memory",
-      constructor: "MemoryActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/framerate", {
-      prefix: "framerate",
-      constructor: "FramerateActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/reflow", {
-      prefix: "reflow",
-      constructor: "ReflowActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/css-properties", {
-      prefix: "cssProperties",
-      constructor: "CssPropertiesActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/csscoverage", {
-      prefix: "cssUsage",
-      constructor: "CSSUsageActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/timeline", {
-      prefix: "timeline",
-      constructor: "TimelineActor",
-      type: { target: true }
-    });
-    if ("nsIProfiler" in Ci &&
-        !Services.prefs.getBoolPref("devtools.performance.new-panel-enabled", false)) {
-      this.registerModule("devtools/server/actors/performance", {
-        prefix: "performance",
-        constructor: "PerformanceActor",
-        type: { target: true }
-      });
-    }
-    this.registerModule("devtools/server/actors/animation", {
-      prefix: "animations",
-      constructor: "AnimationsActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/promises", {
-      prefix: "promises",
-      constructor: "PromisesActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/emulation", {
-      prefix: "emulation",
-      constructor: "EmulationActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/addon/webextension-inspected-window", {
-      prefix: "webExtensionInspectedWindow",
-      constructor: "WebExtensionInspectedWindowActor",
-      type: { target: true }
-    });
-    this.registerModule("devtools/server/actors/accessibility", {
-      prefix: "accessibility",
-      constructor: "AccessibilityActor",
-      type: { target: true }
-    });
-  },
-
-  /**
-   * Registers handlers for new target-scoped request types defined dynamically.
-   *
-   * Note that the name or actorPrefix of the request type is not allowed to clash with
-   * existing protocol packet properties, like 'title', 'url' or 'actor', since that would
-   * break the protocol.
-   *
-   * @param options object
-   *        - constructorName: (required)
-   *          name of actor constructor, which is also used when removing the actor.
-   *        One of the following:
-   *          - id:
-   *            module ID that contains the actor
-   *          - constructorFun:
-   *            a function to construct the actor
-   * @param name string
-   *        The name of the new request type.
-   */
-  addTargetScopedActor(options, name) {
-    if (!name) {
-      throw Error("addTargetScopedActor requires the `name` argument");
-    }
-    if (["title", "url", "actor"].includes(name)) {
-      throw Error(name + " is not allowed");
-    }
-    if (this.targetScopedActorFactories.hasOwnProperty(name)) {
-      throw Error(name + " already exists");
-    }
-    this.targetScopedActorFactories[name] = { options, name };
-  },
-
-  /**
-   * Unregisters the handler for the specified target-scoped request type.
-   *
-   * When unregistering an existing target-scoped actor, we remove the actor factory as
-   * well as all existing instances of the actor.
-   *
-   * @param actor object, string
-   *        In case of object:
-   *          The `actor` object being given to related addTargetScopedActor call.
-   *        In case of string:
-   *          The `name` string being given to related addTargetScopedActor call.
-   */
-  removeTargetScopedActor(actorOrName) {
-    let name;
-    if (typeof actorOrName == "string") {
-      name = actorOrName;
-    } else {
-      const actor = actorOrName;
-      for (const factoryName in this.targetScopedActorFactories) {
-        const handler = this.targetScopedActorFactories[factoryName];
-        if ((handler.options.constructorName == actor.name) ||
-            (handler.options.id == actor.id)) {
-          name = factoryName;
-          break;
-        }
-      }
-    }
-    if (!name) {
-      return;
-    }
-    delete this.targetScopedActorFactories[name];
-    for (const connID of Object.getOwnPropertyNames(this._connections)) {
-      // DebuggerServerConnection in child process don't have rootActor
-      if (this._connections[connID].rootActor) {
-        this._connections[connID].rootActor.removeActorByName(name);
-      }
-    }
-  },
-
-  /**
-   * Registers handlers for new browser-scoped request types defined dynamically.
-   *
-   * Note that the name or actorPrefix of the request type is not allowed to clash with
-   * existing protocol packet properties, like 'from', 'tabs' or 'selected', since that
-   * would break the protocol.
-   *
-   * @param options object
-   *        - constructorName: (required)
-   *          name of actor constructor, which is also used when removing the actor.
-   *        One of the following:
-   *          - id:
-   *            module ID that contains the actor
-   *          - constructorFun:
-   *            a function to construct the actor
-   * @param name string
-   *        The name of the new request type.
-   */
-  addGlobalActor(options, name) {
-    if (!name) {
-      throw Error("addGlobalActor requires the `name` argument");
-    }
-    if (["from", "tabs", "selected"].includes(name)) {
-      throw Error(name + " is not allowed");
-    }
-    if (this.globalActorFactories.hasOwnProperty(name)) {
-      throw Error(name + " already exists");
-    }
-    this.globalActorFactories[name] = { options, name };
-  },
-
-  /**
-   * Unregisters the handler for the specified browser-scoped request type.
-   *
-   * When unregistering an existing global actor, we remove the actor factory as well as
-   * all existing instances of the actor.
-   *
-   * @param actor object, string
-   *        In case of object:
-   *          The `actor` object being given to related addGlobalActor call.
-   *        In case of string:
-   *          The `name` string being given to related addGlobalActor call.
-   */
-  removeGlobalActor(actorOrName) {
-    let name;
-    if (typeof actorOrName == "string") {
-      name = actorOrName;
-    } else {
-      const actor = actorOrName;
-      for (const factoryName in this.globalActorFactories) {
-        const handler = this.globalActorFactories[factoryName];
-        if ((handler.options.constructorName == actor.name) ||
-            (handler.options.id == actor.id)) {
-          name = factoryName;
-          break;
-        }
-      }
-    }
-    if (!name) {
-      return;
-    }
-    delete this.globalActorFactories[name];
-    for (const connID of Object.getOwnPropertyNames(this._connections)) {
-      // DebuggerServerConnection in child process don't have rootActor
-      if (this._connections[connID].rootActor) {
-        this._connections[connID].rootActor.removeActorByName(name);
-      }
-    }
-  },
-
-  destroy() {
-    for (const id of Object.getOwnPropertyNames(gRegisteredModules)) {
-      this.unregisterModule(id);
-    }
-    gRegisteredModules = Object.create(null);
-
-    this.globalActorFactories = {};
-    this.targetScopedActorFactories = {};
-  },
-};
-
-exports.ActorRegistry = ActorRegistry;
--- a/devtools/server/actors/common.js
+++ b/devtools/server/actors/common.js
@@ -4,16 +4,191 @@
  * 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 { method } = require("devtools/shared/protocol");
 
 /**
+ * Creates "registered" actors factory meant for creating another kind of
+ * factories, ObservedActorFactory, during the call to listTabs.
+ * These factories live in DebuggerServer.{tab|global}ActorFactories.
+ *
+ * These actors only exposes:
+ * - `name` string attribute used to match actors by constructor name
+ *   in DebuggerServer.remove{Global,Tab}Actor.
+ * - `createObservedActorFactory` function to create "observed" actors factory
+ *
+ * @param options object
+ *        - constructorName: (required)
+ *          name of actor constructor, which is also used when removing the actor.
+ *        One of the following:
+ *          - id:
+ *            module ID that contains the actor
+ *          - constructorFun:
+ *            a function to construct the actor
+ */
+function RegisteredActorFactory(options, prefix) {
+  // By default the actor name will also be used for the actorID prefix.
+  this._prefix = prefix;
+  if (options.constructorFun) {
+    // Actor definition registered by ActorRegistryActor or testing helpers
+    this._getConstructor = () => options.constructorFun;
+  } else {
+    // Lazy actor definition, where options contains all the information
+    // required to load the actor lazily.
+    this._getConstructor = function() {
+      // Load the module
+      let mod;
+      try {
+        mod = require(options.id);
+      } catch (e) {
+        throw new Error("Unable to load actor module '" + options.id + "'.\n" +
+                        e.message + "\n" + e.stack + "\n");
+      }
+      // Fetch the actor constructor
+      const c = mod[options.constructorName];
+      if (!c) {
+        throw new Error("Unable to find actor constructor named '" +
+                        options.constructorName + "'. (Is it exported?)");
+      }
+      return c;
+    };
+  }
+  // Exposes `name` attribute in order to allow removeXXXActor to match
+  // the actor by its actor constructor name.
+  this.name = options.constructorName;
+}
+RegisteredActorFactory.prototype.createObservedActorFactory = function(conn,
+  parentActor) {
+  return new ObservedActorFactory(this._getConstructor, this._prefix, conn, parentActor);
+};
+exports.RegisteredActorFactory = RegisteredActorFactory;
+
+/**
+ * Creates "observed" actors factory meant for creating real actor instances.
+ * These factories lives in actor pools and fake various actor attributes.
+ * They will be replaced in actor pools by final actor instances during
+ * the first request for the same actorID from DebuggerServer._getOrCreateActor.
+ *
+ * ObservedActorFactory fakes the following actors attributes:
+ *   actorPrefix (string) Used by ActorPool.addActor to compute the actor id
+ *   actorID (string) Set by ActorPool.addActor just after being instantiated
+ *   registeredPool (object) Set by ActorPool.addActor just after being
+ *                           instantiated
+ * And exposes the following method:
+ *   createActor (function) Instantiate an actor that is going to replace
+ *                          this factory in the actor pool.
+ */
+function ObservedActorFactory(getConstructor, prefix, conn, parentActor) {
+  this._getConstructor = getConstructor;
+  this._conn = conn;
+  this._parentActor = parentActor;
+
+  this.actorPrefix = prefix;
+
+  this.actorID = null;
+  this.registeredPool = null;
+}
+ObservedActorFactory.prototype.createActor = function() {
+  // Fetch the actor constructor
+  const C = this._getConstructor();
+  // Instantiate a new actor instance
+  const instance = new C(this._conn, this._parentActor);
+  instance.conn = this._conn;
+  instance.parentID = this._parentActor.actorID;
+  // We want the newly-constructed actor to completely replace the factory
+  // actor. Reusing the existing actor ID will make sure ActorPool.addActor
+  // does the right thing.
+  instance.actorID = this.actorID;
+  this.registeredPool.addActor(instance);
+  return instance;
+};
+exports.ObservedActorFactory = ObservedActorFactory;
+
+/*
+ * Methods shared between RootActor and BrowsingContextTargetActor.
+ */
+
+/**
+ * Populate |this._extraActors| as specified by |factories|, reusing whatever
+ * actors are already there. Add all actors in the final extra actors table to
+ * |pool|.
+ *
+ * The root actor and the target actor use this to instantiate actors that other
+ * parts of the browser have specified with DebuggerServer.addTargetScopedActor and
+ * DebuggerServer.addGlobalActor.
+ *
+ * @param factories
+ *     An object whose own property names are the names of properties to add to
+ *     some reply packet (say, a target actor grip or the "listTabs" response
+ *     form), and whose own property values are actor constructor functions, as
+ *     documented for addTargetScopedActor and addGlobalActor.
+ *
+ * @param this
+ *     The RootActor or BrowsingContextTargetActor with which the new actors
+ *     will be associated. It should support whatever API the |factories|
+ *     constructor functions might be interested in, as it is passed to them.
+ *     For the sake of CommonCreateExtraActors itself, it should have at least
+ *     the following properties:
+ *
+ *     - _extraActors
+ *        An object whose own property names are factory table (and packet)
+ *        property names, and whose values are no-argument actor constructors,
+ *        of the sort that one can add to an ActorPool.
+ *
+ *     - conn
+ *        The DebuggerServerConnection in which the new actors will participate.
+ *
+ *     - actorID
+ *        The actor's name, for use as the new actors' parentID.
+ */
+exports.createExtraActors = function createExtraActors(factories, pool) {
+  // Walk over global actors added by extensions.
+  for (const name in factories) {
+    let actor = this._extraActors[name];
+    if (!actor) {
+      // Register another factory, but this time specific to this connection.
+      // It creates a fake actor that looks like an regular actor in the pool,
+      // but without actually instantiating the actor.
+      // It will only be instantiated on the first request made to the actor.
+      actor = factories[name].createObservedActorFactory(this.conn, this);
+      this._extraActors[name] = actor;
+    }
+
+    // If the actor already exists in the pool, it may have been instantiated,
+    // so make sure not to overwrite it by a non-instantiated version.
+    if (!pool.has(actor.actorID)) {
+      pool.addActor(actor);
+    }
+  }
+};
+
+/**
+ * Append the extra actors in |this._extraActors|, constructed by a prior call
+ * to CommonCreateExtraActors, to |object|.
+ *
+ * @param object
+ *     The object to which the extra actors should be added, under the
+ *     property names given in the |factories| table passed to
+ *     CommonCreateExtraActors.
+ *
+ * @param this
+ *     The RootActor or BrowsingContextTargetActor whose |_extraActors| table we
+ *     should use; see above.
+ */
+exports.appendExtraActors = function appendExtraActors(object) {
+  for (const name in this._extraActors) {
+    const actor = this._extraActors[name];
+    object[name] = actor.actorID;
+  }
+};
+
+/**
  * Construct an ActorPool.
  *
  * ActorPools are actorID -> actor mapping and storage.  These are
  * used to accumulate and quickly dispose of groups of actors that
  * share a lifetime.
  */
 function ActorPool(connection) {
   this.conn = connection;
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -3,18 +3,17 @@
 /* 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 { Cu } = require("chrome");
 const Services = require("Services");
-const { Pool } = require("devtools/shared/protocol");
-const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
+const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
 const { DebuggerServer } = require("devtools/server/main");
 
 loader.lazyRequireGetter(this, "ChromeWindowTargetActor",
   "devtools/server/actors/targets/chrome-window", true);
 
 /* Root actor for the remote debugging protocol. */
 
 /**
@@ -37,17 +36,17 @@ loader.lazyRequireGetter(this, "ChromeWi
  *
  *     - addonList: a live list (see below) of addon actors. If present, the
  *       new root actor supports the 'listAddons' request, providing the live
  *       list's elements as its addon actors, and sending 'addonListchanged'
  *       notifications when the live list's contents change.
  *
  *     - globalActorFactories: an object |A| describing further actors to
  *       attach to the 'listTabs' reply. This is the type accumulated by
- *       ActorRegistry.addGlobalActor. For each own property |P| of |A|,
+ *       DebuggerServer.addGlobalActor. For each own property |P| of |A|,
  *       the root actor adds a property named |P| to the 'listTabs'
  *       reply whose value is the name of an actor constructed by
  *       |A[P]|.
  *
  *     - onShutdown: a function to call when the root actor is destroyed.
  *
  * Instance properties:
  *
@@ -95,17 +94,18 @@ function RootActor(connection, parameter
   this._onTabListChanged = this.onTabListChanged.bind(this);
   this._onAddonListChanged = this.onAddonListChanged.bind(this);
   this._onWorkerListChanged = this.onWorkerListChanged.bind(this);
   this._onServiceWorkerRegistrationListChanged =
     this.onServiceWorkerRegistrationListChanged.bind(this);
   this._onProcessListChanged = this.onProcessListChanged.bind(this);
   this._extraActors = {};
 
-  this._globalActorPool = new LazyPool(this.conn);
+  this._globalActorPool = new ActorPool(this.conn);
+  this.conn.addActorPool(this._globalActorPool);
 
   this._parentProcessTargetActor = null;
   this._processActors = new Map();
 }
 
 RootActor.prototype = {
   constructor: RootActor,
   applicationType: "browser",
@@ -228,27 +228,31 @@ RootActor.prototype = {
   },
 
   /**
    * Gets the "root" form, which lists all the global actors that affect the entire
    * browser.  This can replace usages of `listTabs` that only wanted the global actors
    * and didn't actually care about tabs.
    */
   onGetRoot: function() {
+    const reply = {
+      from: this.actorID,
+    };
+
     // Create global actors
     if (!this._globalActorPool) {
-      this._globalActorPool = new LazyPool(this.conn);
+      this._globalActorPool = new ActorPool(this.conn);
+      this.conn.addActorPool(this._globalActorPool);
     }
-    const actors = createExtraActors(
-      this._parameters.globalActorFactories,
-      this._globalActorPool,
-      this
-    );
+    this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool);
 
-    return actors;
+    // List the global actors
+    this._appendExtraActors(reply);
+
+    return reply;
   },
 
   /* The 'listTabs' request and the 'tabListChanged' notification. */
 
   /**
    * Handles the listTabs request. The actors will survive until at least
    * the next listTabs request.
    *
@@ -265,47 +269,48 @@ RootActor.prototype = {
                message: "This root actor has no browser tabs." };
     }
 
     // Now that a client has requested the list of tabs, we reattach the onListChanged
     // listener in order to be notified if the list of tabs changes again in the future.
     tabList.onListChanged = this._onTabListChanged;
 
     // Walk the tab list, accumulating the array of target actors for the reply, and
-    // moving all the actors to a new Pool. We'll replace the old tab target actor
+    // moving all the actors to a new ActorPool. We'll replace the old tab target actor
     // pool with the one we build here, thus retiring any actors that didn't get listed
     // again, and preparing any new actors to receive packets.
-    const newActorPool = new Pool(this.conn);
+    const newActorPool = new ActorPool(this.conn);
     const targetActorList = [];
     let selected;
 
     const options = request.options || {};
     const targetActors = await tabList.getList(options);
     for (const targetActor of targetActors) {
       if (targetActor.exited) {
         // Target actor may have exited while we were gathering the list.
         continue;
       }
       if (targetActor.selected) {
         selected = targetActorList.length;
       }
       targetActor.parentID = this.actorID;
-      newActorPool.manage(targetActor);
+      newActorPool.addActor(targetActor);
       targetActorList.push(targetActor);
     }
 
     // Start with the root reply, which includes the global actors for the whole browser.
     const reply = this.onGetRoot();
 
     // Drop the old actorID -> actor map. Actors that still mattered were added to the
     // new map; others will go away.
     if (this._tabTargetActorPool) {
-      this._tabTargetActorPool.destroy();
+      this.conn.removeActorPool(this._tabTargetActorPool);
     }
     this._tabTargetActorPool = newActorPool;
+    this.conn.addActorPool(this._tabTargetActorPool);
 
     // We'll extend the reply here to also mention all the tabs.
     Object.assign(reply, {
       selected: selected || 0,
       tabs: targetActorList.map(actor => actor.form()),
     });
 
     return reply;
@@ -313,17 +318,18 @@ RootActor.prototype = {
 
   onGetTab: async function(options) {
     const tabList = this._parameters.tabList;
     if (!tabList) {
       return { error: "noTabs",
                message: "This root actor has no browser tabs." };
     }
     if (!this._tabTargetActorPool) {
-      this._tabTargetActorPool = new Pool(this.conn);
+      this._tabTargetActorPool = new ActorPool(this.conn);
+      this.conn.addActorPool(this._tabTargetActorPool);
     }
 
     let targetActor;
     try {
       targetActor = await tabList.getTab(options);
     } catch (error) {
       if (error.error) {
         // Pipe expected errors as-is to the client
@@ -331,17 +337,17 @@ RootActor.prototype = {
       }
       return {
         error: "noTab",
         message: "Unexpected error while calling getTab(): " + error
       };
     }
 
     targetActor.parentID = this.actorID;
-    this._tabTargetActorPool.manage(targetActor);
+    this._tabTargetActorPool.addActor(targetActor);
 
     return { tab: targetActor.form() };
   },
 
   onGetWindow: function({ outerWindowID }) {
     if (!DebuggerServer.allowChromeProcess) {
       return {
         from: this.actorID,
@@ -354,22 +360,23 @@ RootActor.prototype = {
       return {
         from: this.actorID,
         error: "notFound",
         message: `No window found with outerWindowID ${outerWindowID}`,
       };
     }
 
     if (!this._chromeWindowActorPool) {
-      this._chromeWindowActorPool = new Pool(this.conn);
+      this._chromeWindowActorPool = new ActorPool(this.conn);
+      this.conn.addActorPool(this._chromeWindowActorPool);
     }
 
     const actor = new ChromeWindowTargetActor(this.conn, window);
     actor.parentID = this.actorID;
-    this._chromeWindowActorPool.manage(actor);
+    this._chromeWindowActorPool.addActor(actor);
 
     return {
       from: this.actorID,
       window: actor.form(),
     };
   },
 
   onTabListChanged: function() {
@@ -384,25 +391,26 @@ RootActor.prototype = {
       return { from: this.actorID, error: "noAddons",
                message: "This root actor has no browser addons." };
     }
 
     // Reattach the onListChanged listener now that a client requested the list.
     addonList.onListChanged = this._onAddonListChanged;
 
     return addonList.getList().then((addonTargetActors) => {
-      const addonTargetActorPool = new Pool(this.conn);
+      const addonTargetActorPool = new ActorPool(this.conn);
       for (const addonTargetActor of addonTargetActors) {
-        addonTargetActorPool.manage(addonTargetActor);
+        addonTargetActorPool.addActor(addonTargetActor);
       }
 
       if (this._addonTargetActorPool) {
-        this._addonTargetActorPool.destroy();
+        this.conn.removeActorPool(this._addonTargetActorPool);
       }
       this._addonTargetActorPool = addonTargetActorPool;
+      this.conn.addActorPool(this._addonTargetActorPool);
 
       return {
         "from": this.actorID,
         "addons": addonTargetActors.map(addonTargetActor => addonTargetActor.form())
       };
     });
   },
 
@@ -417,28 +425,24 @@ RootActor.prototype = {
       return { from: this.actorID, error: "noWorkers",
                message: "This root actor has no workers." };
     }
 
     // Reattach the onListChanged listener now that a client requested the list.
     workerList.onListChanged = this._onWorkerListChanged;
 
     return workerList.getList().then(actors => {
-      const pool = new Pool(this.conn);
+      const pool = new ActorPool(this.conn);
       for (const actor of actors) {
-        pool.manage(actor);
+        pool.addActor(actor);
       }
 
-      // Do not destroy the pool before transfering ownership to the newly created
-      // pool, so that we do not accidently destroy actors that are still in use.
-      if (this._workerTargetActorPool) {
-        this._workerTargetActorPool.destroy();
-      }
-
+      this.conn.removeActorPool(this._workerTargetActorPool);
       this._workerTargetActorPool = pool;
+      this.conn.addActorPool(this._workerTargetActorPool);
 
       return {
         "from": this.actorID,
         "workers": actors.map(actor => actor.form())
       };
     });
   },
 
@@ -453,25 +457,24 @@ RootActor.prototype = {
       return { from: this.actorID, error: "noServiceWorkerRegistrations",
                message: "This root actor has no service worker registrations." };
     }
 
     // Reattach the onListChanged listener now that a client requested the list.
     registrationList.onListChanged = this._onServiceWorkerRegistrationListChanged;
 
     return registrationList.getList().then(actors => {
-      const pool = new Pool(this.conn);
+      const pool = new ActorPool(this.conn);
       for (const actor of actors) {
-        pool.manage(actor);
+        pool.addActor(actor);
       }
 
-      if (this._serviceWorkerRegistrationActorPool) {
-        this._serviceWorkerRegistrationActorPool.destroy();
-      }
+      this.conn.removeActorPool(this._serviceWorkerRegistrationActorPool);
       this._serviceWorkerRegistrationActorPool = pool;
+      this.conn.addActorPool(this._serviceWorkerRegistrationActorPool);
 
       return {
         "from": this.actorID,
         "registrations": actors.map(actor => actor.form())
       };
     });
   },
 
@@ -506,25 +509,25 @@ RootActor.prototype = {
       return { error: "wrongParameter",
                message: "getProcess requires a valid `id` attribute." };
     }
     // If the request doesn't contains id parameter or id is 0
     // (id == 0, based on onListProcesses implementation)
     if ((!("id" in request)) || request.id === 0) {
       if (this._parentProcessTargetActor && (!this._parentProcessTargetActor.docShell ||
           this._parentProcessTargetActor.docShell.isBeingDestroyed)) {
-        this._parentProcessTargetActor.destroy();
+        this._globalActorPool.removeActor(this._parentProcessTargetActor);
         this._parentProcessTargetActor = null;
       }
       if (!this._parentProcessTargetActor) {
         // Create a ParentProcessTargetActor for the parent process
         const { ParentProcessTargetActor } =
           require("devtools/server/actors/targets/parent-process");
         this._parentProcessTargetActor = new ParentProcessTargetActor(this.conn);
-        this._globalActorPool.manage(this._parentProcessTargetActor);
+        this._globalActorPool.addActor(this._parentProcessTargetActor);
       }
 
       return { form: this._parentProcessTargetActor.form() };
     }
 
     const { id } = request;
     const mm = Services.ppmm.getChildAt(id);
     if (!mm) {
@@ -551,32 +554,36 @@ RootActor.prototype = {
      */
     return Cu.cloneInto(request, {});
   },
 
   onProtocolDescription: function() {
     return require("devtools/shared/protocol").dumpProtocolSpec();
   },
 
+  /* Support for DebuggerServer.addGlobalActor. */
+  _createExtraActors: createExtraActors,
+  _appendExtraActors: appendExtraActors,
+
   /**
-   * Remove the extra actor (added by ActorRegistry.addGlobalActor or
-   * ActorRegistry.addTargetScopedActor) name |name|.
+   * Remove the extra actor (added by DebuggerServer.addGlobalActor or
+   * DebuggerServer.addTargetScopedActor) name |name|.
    */
   removeActorByName: function(name) {
     if (name in this._extraActors) {
       const actor = this._extraActors[name];
-      if (this._globalActorPool.has(actor.actorID)) {
-        actor.destroy();
+      if (this._globalActorPool.has(actor)) {
+        this._globalActorPool.removeActor(actor);
       }
       if (this._tabTargetActorPool) {
         // Iterate over BrowsingContextTargetActor instances to also remove target-scoped
         // actors created during listTabs for each document.
-        for (const tab in this._tabTargetActorPool.poolChildren()) {
+        this._tabTargetActorPool.forEach(tab => {
           tab.removeActorByName(name);
-        }
+        });
       }
       delete this._extraActors[name];
     }
   }
 };
 
 RootActor.prototype.requestTypes = {
   getRoot: RootActor.prototype.onGetRoot,
--- a/devtools/server/actors/targets/browsing-context.js
+++ b/devtools/server/actors/targets/browsing-context.js
@@ -18,33 +18,35 @@
  * For performance matters, this file should only be loaded in the targeted context's
  * process. For example, it shouldn't be evaluated in the parent process until we try to
  * debug a document living in the parent process.
  */
 
 var { Ci, Cu, Cr, Cc } = require("chrome");
 var Services = require("Services");
 const ChromeUtils = require("ChromeUtils");
-var { ActorRegistry } = require("devtools/server/actor-registry");
+var {
+  ActorPool, createExtraActors, appendExtraActors
+} = require("devtools/server/actors/common");
+var { DebuggerServer } = require("devtools/server/main");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { assert } = DevToolsUtils;
 var { TabSources } = require("devtools/server/actors/utils/TabSources");
 var makeDebugger = require("devtools/server/actors/utils/make-debugger");
 const Debugger = require("Debugger");
 const ReplayDebugger = require("devtools/server/actors/replay/debugger");
 const InspectorUtils = require("InspectorUtils");
 
 const EXTENSION_CONTENT_JSM = "resource://gre/modules/ExtensionContent.jsm";
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const STRINGS_URI = "devtools/shared/locales/browsing-context.properties";
 const L10N = new LocalizationHelper(STRINGS_URI);
 
-const { ActorClassWithSpec, Actor, Pool } = require("devtools/shared/protocol");
-const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
+const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
 const { browsingContextTargetSpec } = require("devtools/shared/specs/targets/browsing-context");
 
 loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/thread", true);
 loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/thread", true);
 loader.lazyRequireGetter(this, "WorkerTargetActorList", "devtools/server/actors/worker/worker-list", true);
 loader.lazyImporter(this, "ExtensionContent", EXTENSION_CONTENT_JSM);
 
 loader.lazyRequireGetter(this, "StyleSheetActor", "devtools/server/actors/stylesheets", true);
@@ -100,17 +102,17 @@ const browsingContextTargetPrototype = {
   /**
    * BrowsingContextTargetActor is an abstract class used by target actors that
    * hold documents, such as frames, chrome windows, etc.  The term "browsing
    * context" is defined in the HTML spec as "an environment in which `Document`
    * objects are presented to the user".  In Gecko, this means a browsing context
    * is a `docShell`.
    *
    * The main goal of this class is to expose the target-scoped actors being registered
-   * via `ActorRegistry.registerModule` and manage their lifetimes. In addition, this
+   * via `DebuggerServer.registerModule` and manage their lifetimes. In addition, this
    * class also tracks the lifetime of the targeted browsing context.
    *
    * ### Main requests:
    *
    * `attach`/`detach` requests:
    *  - start/stop document watching:
    *    Starts watching for new documents and emits `tabNavigated` and
    *    `frameUpdate` over RDP.
@@ -473,28 +475,26 @@ const browsingContextTargetPrototype = {
       response.title = this.title;
       response.url = this.url;
       response.outerWindowID = this.outerWindowID;
     }
 
     // Always use the same ActorPool, so existing actor instances
     // (created in createExtraActors) are not lost.
     if (!this._targetScopedActorPool) {
-      this._targetScopedActorPool = new LazyPool(this.conn);
+      this._targetScopedActorPool = new ActorPool(this.conn);
+      this.conn.addActorPool(this._targetScopedActorPool);
     }
 
     // Walk over target-scoped actor factories and make sure they are all
     // instantiated and added into the ActorPool.
-    const actors = createExtraActors(
-      ActorRegistry.targetScopedActorFactories,
-      this._targetScopedActorPool,
-      this
-    );
+    this._createExtraActors(DebuggerServer.targetScopedActorFactories,
+      this._targetScopedActorPool);
 
-    Object.assign(response, actors);
+    this._appendExtraActors(response);
     return response;
   },
 
   /**
    * Called when the actor is removed from the connection.
    */
   destroy() {
     Actor.prototype.destroy.call(this);
@@ -558,16 +558,20 @@ const browsingContextTargetPrototype = {
         && metadata["inner-window-id"]
         && metadata["inner-window-id"] == id) {
       return true;
     }
 
     return false;
   },
 
+  /* Support for DebuggerServer.addTargetScopedActor. */
+  _createExtraActors: createExtraActors,
+  _appendExtraActors: appendExtraActors,
+
   /**
    * Does the actual work of attaching to a browsing context.
    */
   _attach() {
     if (this._attached) {
       return;
     }
 
@@ -644,28 +648,25 @@ const browsingContextTargetPrototype = {
     if (this._workerTargetActorList === null) {
       this._workerTargetActorList = new WorkerTargetActorList(this.conn, {
         type: Ci.nsIWorkerDebugger.TYPE_DEDICATED,
         window: this.window
       });
     }
 
     return this._workerTargetActorList.getList().then((actors) => {
-      const pool = new Pool(this.conn);
+      const pool = new ActorPool(this.conn);
       for (const actor of actors) {
-        pool.manage(actor);
+        pool.addActor(actor);
       }
 
-      // Do not destroy the pool before transfering ownership to the newly created
-      // pool, so that we do not accidently destroy actors that are still in use.
-      if (this._workerTargetActorPool) {
-        this._workerTargetActorPool.destroy();
-      }
+      this.conn.removeActorPool(this._workerTargetActorPool);
+      this._workerTargetActorPool = pool;
+      this.conn.addActorPool(this._workerTargetActorPool);
 
-      this._workerTargetActorPool = pool;
       this._workerTargetActorList.onListChanged = this._onWorkerTargetActorListChanged;
 
       return {
         "from": this.actorID,
         "workers": actors.map((actor) => actor.form())
       };
     });
   },
@@ -884,28 +885,28 @@ const browsingContextTargetPrototype = {
       Services.obs.removeObserver(this, "webnavigation-destroy");
     }
 
     this._destroyThreadActor();
 
     // Shut down actors that belong to this target's pool.
     this._styleSheetActors.clear();
     if (this._targetScopedActorPool) {
-      this._targetScopedActorPool.destroy();
+      this.conn.removeActorPool(this._targetScopedActorPool);
       this._targetScopedActorPool = null;
     }
 
     // Make sure that no more workerListChanged notifications are sent.
     if (this._workerTargetActorList !== null) {
       this._workerTargetActorList.onListChanged = null;
       this._workerTargetActorList = null;
     }
 
     if (this._workerTargetActorPool !== null) {
-      this._workerTargetActorPool.destroy();
+      this.conn.removeActorPool(this._workerTargetActorPool);
       this._workerTargetActorPool = null;
     }
 
     this._attached = false;
 
     this.emit("tabDetached");
 
     return true;
@@ -1411,31 +1412,31 @@ const browsingContextTargetPrototype = {
   createStyleSheetActor(styleSheet) {
     assert(!this.exited, "Target must not be exited to create a sheet actor.");
     if (this._styleSheetActors.has(styleSheet)) {
       return this._styleSheetActors.get(styleSheet);
     }
     const actor = new StyleSheetActor(styleSheet, this);
     this._styleSheetActors.set(styleSheet, actor);
 
-    this._targetScopedActorPool.manage(actor);
+    this._targetScopedActorPool.addActor(actor);
     this.emit("stylesheet-added", actor);
 
     return actor;
   },
 
   removeActorByName(name) {
     if (name in this._extraActors) {
       const actor = this._extraActors[name];
       if (this._targetScopedActorPool.has(actor)) {
         this._targetScopedActorPool.removeActor(actor);
       }
       delete this._extraActors[name];
     }
-  }
+  },
 };
 
 exports.browsingContextTargetPrototype = browsingContextTargetPrototype;
 exports.BrowsingContextTargetActor =
   ActorClassWithSpec(browsingContextTargetSpec, browsingContextTargetPrototype);
 
 /**
  * The DebuggerProgressListener object is an nsIWebProgressListener which
--- a/devtools/server/actors/targets/content-process.js
+++ b/devtools/server/actors/targets/content-process.js
@@ -13,17 +13,16 @@
 
 const { Cc, Ci, Cu } = require("chrome");
 const Services = require("Services");
 
 const { ChromeDebuggerActor } = require("devtools/server/actors/thread");
 const { WebConsoleActor } = require("devtools/server/actors/webconsole");
 const makeDebugger = require("devtools/server/actors/utils/make-debugger");
 const { ActorPool } = require("devtools/server/actors/common");
-const { Pool } = require("devtools/shared/protocol");
 const { assert } = require("devtools/shared/DevToolsUtils");
 const { TabSources } = require("devtools/server/actors/utils/TabSources");
 
 loader.lazyRequireGetter(this, "WorkerTargetActorList", "devtools/server/actors/worker/worker-list", true);
 loader.lazyRequireGetter(this, "MemoryActor", "devtools/server/actors/memory", true);
 
 function ContentProcessTargetActor(connection) {
   this.conn = connection;
@@ -121,28 +120,25 @@ ContentProcessTargetActor.prototype = {
     };
   },
 
   onListWorkers: function() {
     if (!this._workerList) {
       this._workerList = new WorkerTargetActorList(this.conn, {});
     }
     return this._workerList.getList().then(actors => {
-      const pool = new Pool(this.conn);
+      const pool = new ActorPool(this.conn);
       for (const actor of actors) {
-        pool.manage(actor);
+        pool.addActor(actor);
       }
 
-      // Do not destroy the pool before transfering ownership to the newly created
-      // pool, so that we do not accidently destroy actors that are still in use.
-      if (this._workerTargetActorPool) {
-        this._workerTargetActorPool.destroy();
-      }
+      this.conn.removeActorPool(this._workerTargetActorPool);
+      this._workerTargetActorPool = pool;
+      this.conn.addActorPool(this._workerTargetActorPool);
 
-      this._workerTargetActorPool = pool;
       this._workerList.onListChanged = this._onWorkerListChanged;
 
       return {
         "from": this.actorID,
         "workers": actors.map(actor => actor.form())
       };
     });
   },
--- a/devtools/server/actors/utils/actor-registry-utils.js
+++ b/devtools/server/actors/utils/actor-registry-utils.js
@@ -2,17 +2,16 @@
  * 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 { Cu, CC } = require("chrome");
 
 const { DebuggerServer } = require("devtools/server/main");
-const { ActorRegistry } = require("devtools/server/actor-registry");
 
 /**
  * Support for actor registration. Main used by ActorRegistryActor
  * for dynamic registration of new actors.
  *
  * @param sourceText {String} Source of the actor implementation
  * @param fileName {String} URL of the actor module (for proper stack traces)
  * @param options {Object} Configuration object
@@ -34,25 +33,25 @@ exports.registerActorInCurrentProcess = 
   const sandbox = Cu.Sandbox(principal);
   sandbox.exports = {};
   sandbox.require = require;
 
   Cu.evalInSandbox(sourceText, sandbox, "1.8", fileName, 1);
 
   const { prefix, constructor, type } = options;
 
-  if (type.global && !ActorRegistry.globalActorFactories.hasOwnProperty(prefix)) {
-    ActorRegistry.addGlobalActor({
+  if (type.global && !DebuggerServer.globalActorFactories.hasOwnProperty(prefix)) {
+    DebuggerServer.addGlobalActor({
       constructorName: constructor,
       constructorFun: sandbox[constructor]
     }, prefix);
   }
 
-  if (type.target && !ActorRegistry.targetScopedActorFactories.hasOwnProperty(prefix)) {
-    ActorRegistry.addTargetScopedActor({
+  if (type.target && !DebuggerServer.targetScopedActorFactories.hasOwnProperty(prefix)) {
+    DebuggerServer.addTargetScopedActor({
       constructorName: constructor,
       constructorFun: sandbox[constructor]
     }, prefix);
   }
 };
 
 exports.unregisterActor = function(options) {
   // Unregister in the current process
@@ -62,15 +61,15 @@ exports.unregisterActor = function(optio
     module: "devtools/server/actors/utils/actor-registry-utils",
     setupChild: "unregisterActorInCurrentProcess",
     args: [options]
   });
 };
 
 exports.unregisterActorInCurrentProcess = function(options) {
   if (options.target) {
-    ActorRegistry.removeTargetScopedActor(options);
+    DebuggerServer.removeTargetScopedActor(options);
   }
 
   if (options.global) {
-    ActorRegistry.removeGlobalActor(options);
+    DebuggerServer.removeGlobalActor(options);
   }
 };
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -4,17 +4,16 @@
  * 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";
 
 var { Ci } = require("chrome");
 var Services = require("Services");
 var { DebuggerServer } = require("devtools/server/main");
-var { ActorRegistry } = require("devtools/server/actor-registry");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 
 loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
 loader.lazyRequireGetter(this, "FrameTargetActorProxy", "devtools/server/actors/targets/frame-proxy", true);
 loader.lazyRequireGetter(this, "AddonTargetActor", "devtools/server/actors/targets/addon", true);
 loader.lazyRequireGetter(this, "WebExtensionActor", "devtools/server/actors/addon/webextension", true);
 loader.lazyRequireGetter(this, "WorkerTargetActorList", "devtools/server/actors/worker/worker-list", true);
 loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker/worker-list", true);
@@ -58,33 +57,33 @@ function sendShutdownEvent() {
   }
 }
 
 exports.sendShutdownEvent = sendShutdownEvent;
 
 /**
  * Construct a root actor appropriate for use in a server running in a
  * browser. The returned root actor:
- * - respects the factories registered with ActorRegistry.addGlobalActor,
+ * - respects the factories registered with DebuggerServer.addGlobalActor,
  * - uses a BrowserTabList to supply target actors for tabs,
  * - sends all navigator:browser window documents a Debugger:Shutdown event
  *   when it exits.
  *
  * * @param connection DebuggerServerConnection
  *          The conection to the client.
  */
 exports.createRootActor = function createRootActor(connection) {
   return new RootActor(connection, {
     tabList: new BrowserTabList(connection),
     addonList: new BrowserAddonList(connection),
     workerList: new WorkerTargetActorList(connection, {}),
     serviceWorkerRegistrationList:
       new ServiceWorkerRegistrationActorList(connection),
     processList: new ProcessActorList(),
-    globalActorFactories: ActorRegistry.globalActorFactories,
+    globalActorFactories: DebuggerServer.globalActorFactories,
     onShutdown: sendShutdownEvent
   });
 };
 
 /**
  * A live list of FrameTargetActorProxys representing the current browser tabs,
  * to be provided to the root actor to answer 'listTabs' requests.
  *
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -5,18 +5,18 @@
 "use strict";
 
 /**
  * Toolkit glue for the remote debugging protocol, loaded into the
  * debugging global.
  */
 var { Ci, Cc } = require("chrome");
 var Services = require("Services");
-var { Pool } = require("devtools/shared/protocol");
-var { ActorRegistry } = require("devtools/server/actor-registry");
+var { ActorPool, RegisteredActorFactory,
+      ObservedActorFactory } = require("devtools/server/actors/common");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { dumpn } = DevToolsUtils;
 
 loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
 loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
 loader.lazyRequireGetter(this, "LocalDebuggerTransport", "devtools/shared/transport/local-transport", true);
 loader.lazyRequireGetter(this, "ChildDebuggerTransport", "devtools/shared/transport/child-transport", true);
 loader.lazyRequireGetter(this, "WorkerThreadWorkerDebuggerTransport", "devtools/shared/transport/worker-transport", true);
@@ -29,16 +29,18 @@ loader.lazyGetter(this, "generateUUID", 
   return generateUUID;
 });
 
 const CONTENT_PROCESS_SERVER_STARTUP_SCRIPT =
   "resource://devtools/server/startup/content-process.js";
 
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
+var gRegisteredModules = Object.create(null);
+
 /**
  * Public API
  */
 var DebuggerServer = {
   _listeners: [],
   _initialized: false,
   // Flag to check if the content process server startup script was already loaded.
   _contentProcessServerStartupScriptLoaded: false,
@@ -76,17 +78,16 @@ var DebuggerServer = {
    * Initialize the debugger server.
    */
   init() {
     if (this.initialized) {
       return;
     }
 
     this._connections = {};
-    ActorRegistry.init(this._connections);
     this._nextConnID = 0;
 
     this._initialized = true;
   },
 
   get protocol() {
     return require("devtools/shared/protocol");
   },
@@ -106,19 +107,24 @@ var DebuggerServer = {
     if (!this._initialized) {
       return;
     }
 
     for (const connID of Object.getOwnPropertyNames(this._connections)) {
       this._connections[connID].close();
     }
 
-    ActorRegistry.destroy();
+    for (const id of Object.getOwnPropertyNames(gRegisteredModules)) {
+      this.unregisterModule(id);
+    }
+    gRegisteredModules = Object.create(null);
 
     this.closeAllListeners();
+    this.globalActorFactories = {};
+    this.targetScopedActorFactories = {};
     this._initialized = false;
 
     dumpn("Debugger server is shut down.");
   },
 
   /**
    * Raises an exception if the server has not been properly initialized.
    */
@@ -144,37 +150,290 @@ var DebuggerServer = {
    *        Registers all the parent process actors useful for debugging the
    *        runtime itself, like preferences and addons actors.
    * @param target boolean
    *        Registers all the target-scoped actors like console, script, etc.
    *        for debugging a target context.
    */
   registerActors({ root, browser, target }) {
     if (browser) {
-      ActorRegistry.addBrowserActors();
+      this._addBrowserActors();
     }
 
     if (root) {
       const { createRootActor } = require("devtools/server/actors/webbrowser");
       this.setRootActor(createRootActor);
     }
 
     if (target) {
-      ActorRegistry.addTargetScopedActors();
+      this._addTargetScopedActors();
     }
   },
 
   /**
    * Register all possible actors for this DebuggerServer.
    */
   registerAllActors() {
     this.registerActors({ root: true, browser: true, target: true });
   },
 
   /**
+   * Register a CommonJS module with the debugger server.
+   * @param id string
+   *        The ID of a CommonJS module.
+   *        The actor is going to be registered immediately, but loaded only
+   *        when a client starts sending packets to an actor with the same id.
+   *
+   * @param options object
+   *        An object with 3 mandatory attributes:
+   *        - prefix (string):
+   *          The prefix of an actor is used to compute:
+   *          - the `actorID` of each new actor instance (ex: prefix1).
+   *            (See ActorPool.addActor)
+   *          - the actor name in the listTabs request. Sending a listTabs
+   *            request to the root actor returns actor IDs. IDs are in
+   *            dictionaries, with actor names as keys and actor IDs as values.
+   *            The actor name is the prefix to which the "Actor" string is
+   *            appended. So for an actor with the `console` prefix, the actor
+   *            name will be `consoleActor`.
+   *        - constructor (string):
+   *          the name of the exported symbol to be used as the actor
+   *          constructor.
+   *        - type (a dictionary of booleans with following attribute names):
+   *          - "global"
+   *            registers a global actor instance, if true.
+   *            A global actor has the root actor as its parent.
+   *          - "target"
+   *            registers a target-scoped actor instance, if true.
+   *            A new actor will be created for each target, such as a tab.
+   */
+  registerModule(id, options) {
+    if (id in gRegisteredModules) {
+      return;
+    }
+
+    if (!options) {
+      throw new Error("DebuggerServer.registerModule requires an options argument");
+    }
+    const {prefix, constructor, type} = options;
+    if (typeof (prefix) !== "string") {
+      throw new Error(`Lazy actor definition for '${id}' requires a string ` +
+                      `'prefix' option.`);
+    }
+    if (typeof (constructor) !== "string") {
+      throw new Error(`Lazy actor definition for '${id}' requires a string ` +
+                      `'constructor' option.`);
+    }
+    if (!("global" in type) && !("target" in type)) {
+      throw new Error(`Lazy actor definition for '${id}' requires a dictionary ` +
+                      `'type' option whose attributes can be 'global' or 'target'.`);
+    }
+    const name = prefix + "Actor";
+    const mod = {
+      id,
+      prefix,
+      constructorName: constructor,
+      type,
+      globalActor: type.global,
+      targetScopedActor: type.target
+    };
+    gRegisteredModules[id] = mod;
+    if (mod.targetScopedActor) {
+      this.addTargetScopedActor(mod, name);
+    }
+    if (mod.globalActor) {
+      this.addGlobalActor(mod, name);
+    }
+  },
+
+  /**
+   * Returns true if a module id has been registered.
+   */
+  isModuleRegistered(id) {
+    return (id in gRegisteredModules);
+  },
+
+  /**
+   * Unregister a previously-loaded CommonJS module from the debugger server.
+   */
+  unregisterModule(id) {
+    const mod = gRegisteredModules[id];
+    if (!mod) {
+      throw new Error("Tried to unregister a module that was not previously registered.");
+    }
+
+    // Lazy actors
+    if (mod.targetScopedActor) {
+      this.removeTargetScopedActor(mod);
+    }
+    if (mod.globalActor) {
+      this.removeGlobalActor(mod);
+    }
+
+    delete gRegisteredModules[id];
+  },
+
+  /**
+   * Install Firefox-specific actors.
+   *
+   * /!\ Be careful when adding a new actor, especially global actors.
+   * Any new global actor will be exposed and returned by the root actor.
+   */
+  _addBrowserActors() {
+    this.registerModule("devtools/server/actors/preference", {
+      prefix: "preference",
+      constructor: "PreferenceActor",
+      type: { global: true }
+    });
+    this.registerModule("devtools/server/actors/actor-registry", {
+      prefix: "actorRegistry",
+      constructor: "ActorRegistryActor",
+      type: { global: true }
+    });
+    this.registerModule("devtools/server/actors/addon/addons", {
+      prefix: "addons",
+      constructor: "AddonsActor",
+      type: { global: true }
+    });
+    this.registerModule("devtools/server/actors/device", {
+      prefix: "device",
+      constructor: "DeviceActor",
+      type: { global: true }
+    });
+    this.registerModule("devtools/server/actors/heap-snapshot-file", {
+      prefix: "heapSnapshotFile",
+      constructor: "HeapSnapshotFileActor",
+      type: { global: true }
+    });
+    // Always register this as a global module, even while there is a pref turning
+    // on and off the other performance actor. This actor shouldn't conflict with
+    // the other one. These are also lazily loaded so there shouldn't be a performance
+    // impact.
+    this.registerModule("devtools/server/actors/perf", {
+      prefix: "perf",
+      constructor: "PerfActor",
+      type: { global: true }
+    });
+  },
+
+  /**
+   * Install target-scoped actors.
+   */
+  _addTargetScopedActors() {
+    this.registerModule("devtools/server/actors/webconsole", {
+      prefix: "console",
+      constructor: "WebConsoleActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/inspector/inspector", {
+      prefix: "inspector",
+      constructor: "InspectorActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/call-watcher", {
+      prefix: "callWatcher",
+      constructor: "CallWatcherActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/canvas", {
+      prefix: "canvas",
+      constructor: "CanvasActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/webgl", {
+      prefix: "webgl",
+      constructor: "WebGLActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/webaudio", {
+      prefix: "webaudio",
+      constructor: "WebAudioActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/stylesheets", {
+      prefix: "styleSheets",
+      constructor: "StyleSheetsActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/storage", {
+      prefix: "storage",
+      constructor: "StorageActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/gcli", {
+      prefix: "gcli",
+      constructor: "GcliActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/memory", {
+      prefix: "memory",
+      constructor: "MemoryActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/framerate", {
+      prefix: "framerate",
+      constructor: "FramerateActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/reflow", {
+      prefix: "reflow",
+      constructor: "ReflowActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/css-properties", {
+      prefix: "cssProperties",
+      constructor: "CssPropertiesActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/csscoverage", {
+      prefix: "cssUsage",
+      constructor: "CSSUsageActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/timeline", {
+      prefix: "timeline",
+      constructor: "TimelineActor",
+      type: { target: true }
+    });
+    if ("nsIProfiler" in Ci &&
+        !Services.prefs.getBoolPref("devtools.performance.new-panel-enabled", false)) {
+      this.registerModule("devtools/server/actors/performance", {
+        prefix: "performance",
+        constructor: "PerformanceActor",
+        type: { target: true }
+      });
+    }
+    this.registerModule("devtools/server/actors/animation", {
+      prefix: "animations",
+      constructor: "AnimationsActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/promises", {
+      prefix: "promises",
+      constructor: "PromisesActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/emulation", {
+      prefix: "emulation",
+      constructor: "EmulationActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/addon/webextension-inspected-window", {
+      prefix: "webExtensionInspectedWindow",
+      constructor: "WebExtensionInspectedWindowActor",
+      type: { target: true }
+    });
+    this.registerModule("devtools/server/actors/accessibility", {
+      prefix: "accessibility",
+      constructor: "AccessibilityActor",
+      type: { target: true }
+    });
+  },
+
+  /**
    * Passes a set of options to the AddonTargetActors for the given ID.
    *
    * @param id string
    *        The ID of the add-on to pass the options to
    * @param options object
    *        The options.
    * @return a promise that will be resolved when complete.
    */
@@ -895,16 +1154,157 @@ var DebuggerServer = {
 
   // DebuggerServer extension API.
 
   setRootActor(actorFactory) {
     this.createRootActor = actorFactory;
   },
 
   /**
+   * Registers handlers for new target-scoped request types defined dynamically.
+   *
+   * Note that the name or actorPrefix of the request type is not allowed to clash with
+   * existing protocol packet properties, like 'title', 'url' or 'actor', since that would
+   * break the protocol.
+   *
+   * @param actor object
+   *        - constructorName: (required)
+   *          name of actor constructor, which is also used when removing the actor.
+   *        One of the following:
+   *          - id:
+   *            module ID that contains the actor
+   *          - constructorFun:
+   *            a function to construct the actor
+   * @param name string
+   *        The name of the new request type.
+   */
+  addTargetScopedActor(actor, name) {
+    if (!name) {
+      throw Error("addTargetScopedActor requires the `name` argument");
+    }
+    if (["title", "url", "actor"].includes(name)) {
+      throw Error(name + " is not allowed");
+    }
+    if (DebuggerServer.targetScopedActorFactories.hasOwnProperty(name)) {
+      throw Error(name + " already exists");
+    }
+    DebuggerServer.targetScopedActorFactories[name] =
+      new RegisteredActorFactory(actor, name);
+  },
+
+  /**
+   * Unregisters the handler for the specified target-scoped request type.
+   *
+   * When unregistering an existing target-scoped actor, we remove the actor factory as
+   * well as all existing instances of the actor.
+   *
+   * @param actor object, string
+   *        In case of object:
+   *          The `actor` object being given to related addTargetScopedActor call.
+   *        In case of string:
+   *          The `name` string being given to related addTargetScopedActor call.
+   */
+  removeTargetScopedActor(actorOrName) {
+    let name;
+    if (typeof actorOrName == "string") {
+      name = actorOrName;
+    } else {
+      const actor = actorOrName;
+      for (const factoryName in DebuggerServer.targetScopedActorFactories) {
+        const handler = DebuggerServer.targetScopedActorFactories[factoryName];
+        if ((handler.name && handler.name == actor.name) ||
+            (handler.id && handler.id == actor.id)) {
+          name = factoryName;
+          break;
+        }
+      }
+    }
+    if (!name) {
+      return;
+    }
+    delete DebuggerServer.targetScopedActorFactories[name];
+    for (const connID of Object.getOwnPropertyNames(this._connections)) {
+      // DebuggerServerConnection in child process don't have rootActor
+      if (this._connections[connID].rootActor) {
+        this._connections[connID].rootActor.removeActorByName(name);
+      }
+    }
+  },
+
+  /**
+   * Registers handlers for new browser-scoped request types defined dynamically.
+   *
+   * Note that the name or actorPrefix of the request type is not allowed to clash with
+   * existing protocol packet properties, like 'from', 'tabs' or 'selected', since that
+   * would break the protocol.
+   *
+   * @param actor object
+   *        - constructorName: (required)
+   *          name of actor constructor, which is also used when removing the actor.
+   *        One of the following:
+   *          - id:
+   *            module ID that contains the actor
+   *          - constructorFun:
+   *            a function to construct the actor
+   * @param name string
+   *        The name of the new request type.
+   */
+  addGlobalActor(actor, name) {
+    if (!name) {
+      throw Error("addGlobalActor requires the `name` argument");
+    }
+    if (["from", "tabs", "selected"].includes(name)) {
+      throw Error(name + " is not allowed");
+    }
+    if (DebuggerServer.globalActorFactories.hasOwnProperty(name)) {
+      throw Error(name + " already exists");
+    }
+    DebuggerServer.globalActorFactories[name] = new RegisteredActorFactory(actor, name);
+  },
+
+  /**
+   * Unregisters the handler for the specified browser-scoped request type.
+   *
+   * When unregistering an existing global actor, we remove the actor factory as well as
+   * all existing instances of the actor.
+   *
+   * @param actor object, string
+   *        In case of object:
+   *          The `actor` object being given to related addGlobalActor call.
+   *        In case of string:
+   *          The `name` string being given to related addGlobalActor call.
+   */
+  removeGlobalActor(actorOrName) {
+    let name;
+    if (typeof actorOrName == "string") {
+      name = actorOrName;
+    } else {
+      const actor = actorOrName;
+      for (const factoryName in DebuggerServer.globalActorFactories) {
+        const handler = DebuggerServer.globalActorFactories[factoryName];
+        if ((handler.name && handler.name == actor.name) ||
+            (handler.id && handler.id == actor.id)) {
+          name = factoryName;
+          break;
+        }
+      }
+    }
+    if (!name) {
+      return;
+    }
+    delete DebuggerServer.globalActorFactories[name];
+    for (const connID of Object.getOwnPropertyNames(this._connections)) {
+      // DebuggerServerConnection in child process don't have rootActor
+      if (this._connections[connID].rootActor) {
+        this._connections[connID].rootActor.removeActorByName(name);
+      }
+    }
+  },
+
+  /**
    * Called when DevTools are unloaded to remove the contend process server startup script
    * for the list of scripts loaded for each new content process. Will also remove message
    * listeners from already loaded scripts.
    */
   removeContentServerScript() {
     Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_SERVER_STARTUP_SCRIPT);
     try {
       Services.ppmm.broadcastAsyncMessage("debug:close-content-server");
@@ -966,17 +1366,17 @@ exports.DebuggerServer = DebuggerServer;
  *        Packet transport for the debugging protocol.
  */
 function DebuggerServerConnection(prefix, transport) {
   this._prefix = prefix;
   this._transport = transport;
   this._transport.hooks = this;
   this._nextID = 1;
 
-  this._actorPool = new Pool(this);
+  this._actorPool = new ActorPool(this);
   this._extraPools = [this._actorPool];
 
   // Responses to a given actor must be returned the the client
   // in the same order as the requests that they're replying to, but
   // Implementations might finish serving requests in a different
   // order.  To keep things in order we generate a promise for each
   // request, chained to the promise for the request before it.
   // This map stores the latest request promise in the chain, keyed
@@ -1078,24 +1478,24 @@ DebuggerServerConnection.prototype = {
       }
     }
   },
 
   /**
    * Add an actor to the default actor pool for this connection.
    */
   addActor(actor) {
-    this._actorPool.manage(actor);
+    this._actorPool.addActor(actor);
   },
 
   /**
    * Remove an actor to the default actor pool for this connection.
    */
   removeActor(actor) {
-    this._actorPool.unmanage(actor);
+    this._actorPool.removeActor(actor);
   },
 
   /**
    * Match the api expected by the protocol library.
    */
   unmanage(actor) {
     return this.removeActor(actor);
   },
@@ -1116,37 +1516,40 @@ DebuggerServerConnection.prototype = {
     if (actorID === "root") {
       return this.rootActor;
     }
 
     return null;
   },
 
   _getOrCreateActor(actorID) {
-    try {
-      const actor = this.getActor(actorID);
-      if (!actor) {
-        this.transport.send({ from: actorID ? actorID : "root",
-                              error: "noSuchActor",
-                              message: "No such actor for ID: " + actorID });
-        return null;
-      }
+    let actor = this.getActor(actorID);
+    if (!actor) {
+      this.transport.send({ from: actorID ? actorID : "root",
+                            error: "noSuchActor",
+                            message: "No such actor for ID: " + actorID });
+      return null;
+    }
 
-      if (typeof (actor) !== "object") {
-        // ActorPools should now contain only actor instances (i.e. objects)
-        throw new Error("Unexpected actor constructor/function in ActorPool " +
-                        "for actorID=" + actorID + ".");
+    // Dynamically-loaded actors have to be created lazily.
+    if (actor instanceof ObservedActorFactory) {
+      try {
+        actor = actor.createActor();
+      } catch (error) {
+        const prefix = "Error occurred while creating actor '" + actor.name;
+        this.transport.send(this._unknownError(actorID, prefix, error));
       }
+    } else if (typeof (actor) !== "object") {
+      // ActorPools should now contain only actor instances (i.e. objects)
+      // or ObservedActorFactory instances.
+      throw new Error("Unexpected actor constructor/function in ActorPool " +
+                      "for actorID=" + actorID + ".");
+    }
 
-      return actor;
-    } catch (error) {
-      const prefix = `Error occurred while creating actor' ${actorID}`;
-      this.transport.send(this._unknownError(actorID, prefix, error));
-    }
-    return null;
+    return actor;
   },
 
   poolFor(actorID) {
     for (const pool of this._extraPools) {
       if (pool.has(actorID)) {
         return pool;
       }
     }
--- a/devtools/server/moz.build
+++ b/devtools/server/moz.build
@@ -13,14 +13,13 @@ DIRS += [
     'startup',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
 DevToolsModules(
-    'actor-registry.js',
     'main.js',
 )
 
 with Files('**'):
     BUG_COMPONENT = ('DevTools', 'General')
--- a/devtools/server/tests/browser/browser_actor_error.js
+++ b/devtools/server/tests/browser/browser_actor_error.js
@@ -11,17 +11,17 @@
 
 const ACTORS_URL =
   "chrome://mochitests/content/browser/devtools/server/tests/browser/error-actor.js";
 
 async function test() {
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
 
-  ActorRegistry.registerModule(ACTORS_URL, {
+  DebuggerServer.registerModule(ACTORS_URL, {
     prefix: "error",
     constructor: "ErrorActor",
     type: { global: true },
   });
 
   const transport = DebuggerServer.connectPipe();
   const gClient = new DebuggerClient(transport);
   await gClient.connect();
--- a/devtools/server/tests/browser/head.js
+++ b/devtools/server/tests/browser/head.js
@@ -7,17 +7,16 @@
 /* eslint no-unused-vars: [2, {"vars": "local"}] */
 /* import-globals-from ../../../client/shared/test/shared-head.js */
 
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
   this);
 
 const {DebuggerClient} = require("devtools/shared/client/debugger-client");
-const { ActorRegistry } = require("devtools/server/actor-registry");
 const {DebuggerServer} = require("devtools/server/main");
 
 const PATH = "browser/devtools/server/tests/browser/";
 const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
 const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
 const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
 
 // GUID to be used as a separator in compound keys. This must match the same
--- a/devtools/server/tests/mochitest/test_connectToFrame.html
+++ b/devtools/server/tests/mochitest/test_connectToFrame.html
@@ -42,17 +42,16 @@ function runTests() {
   const mm = iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
 
   // Register a test actor in the child process so that we can know if and when
   // this fake actor is destroyed.
   mm.loadFrameScript("data:text/javascript,new " + function FrameScriptScope() {
     /* eslint-disable no-shadow */
     const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
     const { DebuggerServer } = require("devtools/server/main");
-    const { ActorRegistry } = require("devtools/server/actor-registry");
     /* eslint-enable no-shadow */
 
     DebuggerServer.init();
 
     function TestActor() {
       dump("instanciate test actor\n");
     }
     TestActor.prototype = {
@@ -63,17 +62,17 @@ function runTests() {
       },
       hello: function() {
         return {msg: "world"};
       }
     };
     TestActor.prototype.requestTypes = {
       "hello": TestActor.prototype.hello
     };
-    ActorRegistry.addTargetScopedActor({
+    DebuggerServer.addTargetScopedActor({
       constructorName: "TestActor",
       constructorFun: TestActor,
     }, "testActor");
   }, false);
 
   // Instantiate a minimal server
   DebuggerServer.init();
   if (!DebuggerServer.createRootActor) {
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -26,17 +26,16 @@ const { NetUtil } = require("resource://
 const Services = require("Services");
 // Always log packets when running tests. runxpcshelltests.py will throw
 // the output away anyway, unless you give it the --verbose flag.
 Services.prefs.setBoolPref("devtools.debugger.log", true);
 // Enable remote debugging for the relevant tests.
 Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
 
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { ActorRegistry } = require("devtools/server/actor-registry");
 const { DebuggerServer } = require("devtools/server/main");
 const { DebuggerServer: WorkerDebuggerServer } = worker.require("devtools/server/main");
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 const ObjectClient = require("devtools/shared/client/object-client");
 const { MemoryFront } = require("devtools/shared/fronts/memory");
 
 const { addDebuggerToGlobal } = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm", {});
 
@@ -72,17 +71,17 @@ function startupAddonsManager() {
  * @returns `run_test` function
  */
 function makeMemoryActorTest(testGeneratorFunction) {
   const TEST_GLOBAL_NAME = "test_MemoryActor";
 
   return function run_test() {
     do_test_pending();
     startTestDebuggerServer(TEST_GLOBAL_NAME).then(client => {
-      ActorRegistry.registerModule("devtools/server/actors/heap-snapshot-file", {
+      DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", {
         prefix: "heapSnapshotFile",
         constructor: "HeapSnapshotFileActor",
         type: { global: true }
       });
 
       getTestTab(client, TEST_GLOBAL_NAME, function(tabForm, rootForm) {
         if (!tabForm || !rootForm) {
           ok(false, "Could not attach to test tab: " + TEST_GLOBAL_NAME);
@@ -110,17 +109,17 @@ function makeMemoryActorTest(testGenerat
 /**
  * Save as makeMemoryActorTest but attaches the MemoryFront to the MemoryActor
  * scoped to the full runtime rather than to a tab.
  */
 function makeFullRuntimeMemoryActorTest(testGeneratorFunction) {
   return function run_test() {
     do_test_pending();
     startTestDebuggerServer("test_MemoryActor").then(client => {
-      ActorRegistry.registerModule("devtools/server/actors/heap-snapshot-file", {
+      DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", {
         prefix: "heapSnapshotFile",
         constructor: "HeapSnapshotFileActor",
         type: { global: true }
       });
 
       getParentProcessActors(client).then(function(form) {
         if (!form) {
           ok(false, "Could not attach to chrome actors");
--- a/devtools/server/tests/unit/test_add_actors.js
+++ b/devtools/server/tests/unit/test_add_actors.js
@@ -11,35 +11,35 @@ function getActorInstance(connID, actorI
 /**
  * The purpose of these tests is to verify that it's possible to add actors
  * both before and after the DebuggerServer has been initialized, so addons
  * that add actors don't have to poll the object for its initialization state
  * in order to add actors after initialization but rather can add actors anytime
  * regardless of the object's state.
  */
 add_task(async function() {
-  ActorRegistry.registerModule("resource://test/pre_init_global_actors.js", {
+  DebuggerServer.registerModule("resource://test/pre_init_global_actors.js", {
     prefix: "preInitGlobal",
     constructor: "PreInitGlobalActor",
     type: { global: true },
   });
-  ActorRegistry.registerModule("resource://test/pre_init_target_scoped_actors.js", {
+  DebuggerServer.registerModule("resource://test/pre_init_target_scoped_actors.js", {
     prefix: "preInitTargetScoped",
     constructor: "PreInitTargetScopedActor",
     type: { target: true },
   });
 
   const client = await startTestDebuggerServer("example tab");
 
-  ActorRegistry.registerModule("resource://test/post_init_global_actors.js", {
+  DebuggerServer.registerModule("resource://test/post_init_global_actors.js", {
     prefix: "postInitGlobal",
     constructor: "PostInitGlobalActor",
     type: { global: true },
   });
-  ActorRegistry.registerModule("resource://test/post_init_target_scoped_actors.js", {
+  DebuggerServer.registerModule("resource://test/post_init_target_scoped_actors.js", {
     prefix: "postInitTargetScoped",
     constructor: "PostInitTargetScopedActor",
     type: { target: true },
   });
 
   let actors = await client.listTabs();
   Assert.equal(actors.tabs.length, 1);
 
--- a/devtools/server/tests/unit/test_client_request.js
+++ b/devtools/server/tests/unit/test_client_request.js
@@ -22,17 +22,17 @@ TestActor.prototype = {
   }
 };
 TestActor.prototype.requestTypes = {
   "hello": TestActor.prototype.hello,
   "error": TestActor.prototype.error
 };
 
 function run_test() {
-  ActorRegistry.addGlobalActor({
+  DebuggerServer.addGlobalActor({
     constructorName: "TestActor",
     constructorFun: TestActor,
   }, "test");
 
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
 
   add_test(init);
--- a/devtools/server/tests/unit/test_promises_actor_attach.js
+++ b/devtools/server/tests/unit/test_promises_actor_attach.js
@@ -17,18 +17,19 @@ add_task(async function() {
   // We have to attach the chrome target actor before playing with the PromiseActor
   await attachTab(client, parentProcessActors);
   await testAttach(client, parentProcessActors);
 
   const response = await listTabs(client);
   const targetTab = findTab(response.tabs, "promises-actor-test");
   ok(targetTab, "Found our target tab.");
 
-  await attachTab(client, targetTab);
-  await testAttach(client, targetTab);
+  const [ tabResponse ] = await attachTab(client, targetTab);
+
+  await testAttach(client, tabResponse);
 
   await close(client);
 });
 
 async function testAttach(client, parent) {
   const promises = PromisesFront(client, parent);
 
   try {
--- a/devtools/server/tests/unit/test_promises_actor_exist.js
+++ b/devtools/server/tests/unit/test_promises_actor_exist.js
@@ -19,16 +19,18 @@ add_task(async function() {
   // Attach to the BrowsingContextTargetActor and check the response
   await new Promise(resolve => {
     client.request({ to: targetTab.actor, type: "attach" }, response => {
       Assert.ok(!("error" in response), "Expect no error in response.");
       Assert.equal(response.from, targetTab.actor,
         "Expect the target BrowsingContextTargetActor in response form field.");
       Assert.equal(response.type, "tabAttached",
         "Expect tabAttached in the response type.");
+      Assert.ok(typeof response.promisesActor === "string",
+        "Should have a tab context PromisesActor.");
       resolve();
     });
   });
 
   const parentProcessActors = await getParentProcessActors(client);
   Assert.ok(typeof parentProcessActors.promisesActor === "string",
     "Should have a chrome context PromisesActor.");
 });
--- a/devtools/server/tests/unit/test_protocol_children.js
+++ b/devtools/server/tests/unit/test_protocol_children.js
@@ -243,16 +243,18 @@ var RootActor = protocol.ActorClassWithS
     return "[root actor]";
   },
 
   initialize: function(conn) {
     rootActor = this;
     this.actorID = "root";
     this._children = {};
     protocol.Actor.prototype.initialize.call(this, conn);
+    // Root actor owns itself.
+    this.manage(this);
   },
 
   sayHello: simpleHello,
 
   getChild: function(id) {
     if (id in this._children) {
       return this._children[id];
     }
@@ -347,17 +349,17 @@ function run_test() {
                          "applicationType": "xpcshell-tests",
                          "traits": []});
     Assert.equal(applicationType, "xpcshell-tests");
 
     const rootFront = RootFront(client);
     let childFront = null;
 
     const expectRootChildren = size => {
-      Assert.equal(rootActor._poolMap.size, size);
+      Assert.equal(rootActor._poolMap.size, size + 1);
       Assert.equal(rootFront._poolMap.size, size + 1);
       if (childFront) {
         Assert.equal(childFront._poolMap.size, 0);
       }
     };
 
     rootFront.getChild("child1").then(ret => {
       trace.expectSend({"type": "getChild", "str": "child1", "to": "<actorid>"});
--- a/devtools/server/tests/unit/test_registerClient.js
+++ b/devtools/server/tests/unit/test_registerClient.js
@@ -46,17 +46,17 @@ TestClient.prototype = {
 
   detach: function(onDone) {
     this.detached = true;
     onDone();
   }
 };
 
 function run_test() {
-  ActorRegistry.addGlobalActor({
+  DebuggerServer.addGlobalActor({
     constructorName: "TestActor",
     constructorFun: TestActor,
   }, "test");
 
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
 
   add_test(init);
--- a/devtools/server/tests/unit/test_register_actor.js
+++ b/devtools/server/tests/unit/test_register_actor.js
@@ -9,36 +9,36 @@ function run_test() {
   DebuggerServer.registerAllActors();
 
   add_test(test_lazy_api);
   add_test(manual_remove);
   add_test(cleanup);
   run_next_test();
 }
 
-// Bug 988237: Test the new lazy actor actor-register
+// Bug 988237: Test the new lazy actor loading
 function test_lazy_api() {
   let isActorLoaded = false;
   let isActorInstantiated = false;
   function onActorEvent(subject, topic, data) {
     if (data == "loaded") {
       isActorLoaded = true;
     } else if (data == "instantiated") {
       isActorInstantiated = true;
     }
   }
   Services.obs.addObserver(onActorEvent, "actor");
-  ActorRegistry.registerModule("xpcshell-test/registertestactors-lazy", {
+  DebuggerServer.registerModule("xpcshell-test/registertestactors-lazy", {
     prefix: "lazy",
     constructor: "LazyActor",
     type: { global: true, target: true }
   });
   // The actor is immediatly registered, but not loaded
-  Assert.ok(ActorRegistry.targetScopedActorFactories.hasOwnProperty("lazyActor"));
-  Assert.ok(ActorRegistry.globalActorFactories.hasOwnProperty("lazyActor"));
+  Assert.ok(DebuggerServer.targetScopedActorFactories.hasOwnProperty("lazyActor"));
+  Assert.ok(DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor"));
   Assert.ok(!isActorLoaded);
   Assert.ok(!isActorInstantiated);
 
   const client = new DebuggerClient(DebuggerServer.connectPipe());
   client.connect().then(function onConnect() {
     client.listTabs().then(onListTabs);
   });
   function onListTabs(response) {
@@ -60,25 +60,25 @@ function test_lazy_api() {
     Assert.ok(isActorInstantiated);
 
     Services.obs.removeObserver(onActorEvent, "actor");
     client.close().then(() => run_next_test());
   }
 }
 
 function manual_remove() {
-  Assert.ok(ActorRegistry.globalActorFactories.hasOwnProperty("lazyActor"));
-  ActorRegistry.removeGlobalActor("lazyActor");
-  Assert.ok(!ActorRegistry.globalActorFactories.hasOwnProperty("lazyActor"));
+  Assert.ok(DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor"));
+  DebuggerServer.removeGlobalActor("lazyActor");
+  Assert.ok(!DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor"));
 
   run_next_test();
 }
 
 function cleanup() {
   DebuggerServer.destroy();
 
   // Check that all actors are unregistered on server destruction
-  Assert.ok(!ActorRegistry.targetScopedActorFactories.hasOwnProperty("lazyActor"));
-  Assert.ok(!ActorRegistry.globalActorFactories.hasOwnProperty("lazyActor"));
+  Assert.ok(!DebuggerServer.targetScopedActorFactories.hasOwnProperty("lazyActor"));
+  Assert.ok(!DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor"));
 
   run_next_test();
 }
 
--- a/devtools/server/tests/unit/testactors.js
+++ b/devtools/server/tests/unit/testactors.js
@@ -1,18 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
+const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/thread");
 const { DebuggerServer } = require("devtools/server/main");
-const { ActorRegistry } = require("devtools/server/actor-registry");
 const { TabSources } = require("devtools/server/actors/utils/TabSources");
 const makeDebugger = require("devtools/server/actors/utils/make-debugger");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(global) {
   gTestGlobals.push(global);
 };
 
@@ -36,40 +35,42 @@ DebuggerServer.getTestGlobal = function(
 function TestTabList(connection) {
   this.conn = connection;
 
   // An array of actors for each global added with
   // DebuggerServer.addTestGlobal.
   this._targetActors = [];
 
   // A pool mapping those actors' names to the actors.
-  this._targetActorPool = new LazyPool(connection);
+  this._targetActorPool = new ActorPool(connection);
 
   for (const global of gTestGlobals) {
     const actor = new TestTargetActor(connection, global);
     actor.selected = false;
     this._targetActors.push(actor);
-    this._targetActorPool.manage(actor);
+    this._targetActorPool.addActor(actor);
   }
   if (this._targetActors.length > 0) {
     this._targetActors[0].selected = true;
   }
+
+  connection.addActorPool(this._targetActorPool);
 }
 
 TestTabList.prototype = {
   constructor: TestTabList,
   getList: function() {
     return Promise.resolve([...this._targetActors]);
   }
 };
 
 exports.createRootActor = function createRootActor(connection) {
   const root = new RootActor(connection, {
     tabList: new TestTabList(connection),
-    globalActorFactories: ActorRegistry.globalActorFactories,
+    globalActorFactories: DebuggerServer.globalActorFactories,
   });
 
   root.applicationType = "xpcshell-tests";
   return root;
 };
 
 function TestTargetActor(connection, global) {
   this.conn = connection;
@@ -105,35 +106,36 @@ TestTargetActor.prototype = {
       this._sources = new TabSources(this.threadActor);
     }
     return this._sources;
   },
 
   form: function() {
     const response = { actor: this.actorID, title: this._global.__name };
 
-    // Walk over target-scoped actors and add them to a new LazyPool.
-    const actorPool = new LazyPool(this.conn);
-    const actors = createExtraActors(
-      ActorRegistry.targetScopedActorFactories,
-      actorPool,
-      this
-    );
+    // Walk over target-scoped actors and add them to a new ActorPool.
+    const actorPool = new ActorPool(this.conn);
+    this._createExtraActors(DebuggerServer.targetScopedActorFactories, actorPool);
     if (!actorPool.isEmpty()) {
       this._targetActorPool = actorPool;
       this.conn.addActorPool(this._targetActorPool);
     }
 
-    return { ...response, ...actors };
+    this._appendExtraActors(response);
+
+    return response;
   },
 
   onAttach: function(request) {
     this._attached = true;
 
-    return { type: "tabAttached", threadActor: this.threadActor.actorID };
+    const response = { type: "tabAttached", threadActor: this.threadActor.actorID };
+    this._appendExtraActors(response);
+
+    return response;
   },
 
   onDetach: function(request) {
     if (!this._attached) {
       return { "error": "wrongState" };
     }
     return { type: "detached" };
   },
@@ -147,15 +149,19 @@ TestTargetActor.prototype = {
 
   removeActorByName: function(name) {
     const actor = this._extraActors[name];
     if (this._targetActorPool) {
       this._targetActorPool.removeActor(actor);
     }
     delete this._extraActors[name];
   },
+
+  /* Support for DebuggerServer.addTargetScopedActor. */
+  _createExtraActors: createExtraActors,
+  _appendExtraActors: appendExtraActors
 };
 
 TestTargetActor.prototype.requestTypes = {
   "attach": TestTargetActor.prototype.onAttach,
   "detach": TestTargetActor.prototype.onDetach,
   "reload": TestTargetActor.prototype.onReload
 };
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -18,17 +18,16 @@ DIRS += [
     'heapsnapshot',
     'inspector',
     'jsbeautify',
     'layout',
     'locales',
     'node-properties',
     'performance',
     'platform',
-    'protocol',
     'pretty-fast',
     'qrcode',
     'security',
     'sourcemap',
     'sprintfjs',
     'specs',
     'transport',
     'webconsole',
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -821,20 +821,16 @@ var Pool = function(conn) {
 Pool.prototype = extend(EventEmitter.prototype, {
   /**
    * Return the parent pool for this client.
    */
   parent: function() {
     return this.conn.poolFor(this.actorID);
   },
 
-  poolFor: function(actorID) {
-    return this.conn.poolFor(actorID);
-  },
-
   /**
    * Override this if you want actors returned by this actor
    * to belong to a different actor by default.
    */
   marshallPool: function() {
     return this;
   },
 
@@ -854,28 +850,18 @@ Pool.prototype = extend(EventEmitter.pro
   },
 
   /**
    * Add an actor as a child of this pool.
    */
   manage: function(actor) {
     if (!actor.actorID) {
       actor.actorID = this.conn.allocID(actor.actorPrefix || actor.typeName);
-    } else {
-      // If the actor is already registerd in a pool, remove it without destroying it.
-      // This happens for example when an addon is reloaded. To see this behavior, take a
-      // look at devtools/server/tests/unit/test_addon_reload.js
+    }
 
-      // TODO: not all actors have been moved to protocol.js, so they do not all have
-      // a parent field. Remove the check for the parent once the conversion is finished
-      const parent = this.poolFor(actor.actorID);
-      if (parent) {
-        parent.unmanage(actor);
-      }
-    }
     this._poolMap.set(actor.actorID, actor);
     return actor;
   },
 
   /**
    * Remove an actor as a child of this pool.
    */
   unmanage: function(actor) {
@@ -884,29 +870,23 @@ Pool.prototype = extend(EventEmitter.pro
 
   // true if the given actor ID exists in the pool.
   has: function(actorID) {
     return this.__poolMap && this._poolMap.has(actorID);
   },
 
   // The actor for a given actor id stored in this pool
   actor: function(actorID) {
-    if (this.__poolMap) {
-      return this._poolMap.get(actorID);
-    }
-    return null;
+    return this.__poolMap ? this._poolMap.get(actorID) : null;
   },
 
   // Same as actor, should update debugger connection to use 'actor'
   // and then remove this.
   get: function(actorID) {
-    if (this.__poolMap) {
-      return this._poolMap.get(actorID);
-    }
-    return null;
+    return this.__poolMap ? this._poolMap.get(actorID) : null;
   },
 
   // True if this pool has no children.
   isEmpty: function() {
     return !this.__poolMap || this._poolMap.size == 0;
   },
 
   // Generator that yields each non-self child of the pool.
deleted file mode 100644
--- a/devtools/shared/protocol/lazy-pool.js
+++ /dev/null
@@ -1,231 +0,0 @@
-/* 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 { extend } = require("devtools/shared/extend");
-const { Pool } = require("devtools/shared/protocol");
-
-/**
- * A Special Pool for RootActor and BrowsingContextTargetActor, which allows lazy loaded
- * actors to be added to the pool.
- *
- * Like the Pool, this is a protocol object that can manage the lifetime of other protocol
- * objects. Pools are used on both sides of the connection to help coordinate lifetimes.
- *
- * @param conn
- *   Is a DebuggerServerConnection.  Must have
- *   addActorPool, removeActorPool, and poolFor.
- * @constructor
- */
-function LazyPool(conn) {
-  this.conn = conn;
-}
-
-LazyPool.prototype = extend(Pool.prototype, {
-  // The actor for a given actor id stored in this pool
-  actor: function(actorID) {
-    if (this.__poolMap) {
-      const entry = this._poolMap.get(actorID);
-      if (entry instanceof LazyActor) {
-        return entry.createActor();
-      }
-      return entry;
-    }
-    return null;
-  },
-
-  // Same as actor, should update debugger connection to use 'actor'
-  // and then remove this.
-  get: function(actorID) {
-    return this.actor(actorID);
-  },
-});
-
-exports.LazyPool = LazyPool;
-
-/**
- * Populate |parent._extraActors| as specified by |registeredActors|, reusing whatever
- * actors are already there. Add all actors in the final extra actors table to
- * |pool|. _extraActors is treated as a cache for lazy actors
- *
- * The target actor uses this to instantiate actors that other
- * parts of the browser have specified with ActorRegistry.addTargetScopedActor
- *
- * @param factories
- *     An object whose own property names are the names of properties to add to
- *     some reply packet (say, a target actor grip or the "listTabs" response
- *     form), and whose own property values are actor constructor functions, as
- *     documented for addTargetScopedActor
- *
- * @param parent
- *     The parent TargetActor with which the new actors
- *     will be associated. It should support whatever API the |factories|
- *     constructor functions might be interested in, as it is passed to them.
- *     For the sake of CommonCreateExtraActors itself, it should have at least
- *     the following properties:
- *
- *     - _extraActors
- *        An object whose own property names are factory table (and packet)
- *        property names, and whose values are no-argument actor constructors,
- *        of the sort that one can add to an ActorPool.
- *
- *     - conn
- *        The DebuggerServerConnection in which the new actors will participate.
- *
- *     - actorID
- *        The actor's name, for use as the new actors' parentID.
- * @param pool
- *     An object which implements the protocol.js Pool interface, and has the
- *     following properties
- *
- *     - manage
- *       a function which adds a given actor to an actor pool
- */
-function createExtraActors(registeredActors, pool, parent) {
-  // Walk over global actors added by extensions.
-  const nameMap = {};
-  for (const name in registeredActors) {
-    let actor = parent._extraActors[name];
-    if (!actor) {
-      // Register another factory, but this time specific to this connection.
-      // It creates a fake actor that looks like an regular actor in the pool,
-      // but without actually instantiating the actor.
-      // It will only be instantiated on the first request made to the actor.
-      actor = new LazyActor(registeredActors[name], parent, pool);
-      parent._extraActors[name] = actor;
-    }
-
-    // If the actor already exists in the pool, it may have been instantiated,
-    // so make sure not to overwrite it by a non-instantiated version.
-    if (!pool.has(actor.actorID)) {
-      pool.manage(actor);
-    }
-    nameMap[name] = actor.actorID;
-  }
-  return nameMap;
-}
-
-exports.createExtraActors = createExtraActors;
-
-/**
- * Creates an "actor-like" object which responds in the same way as an ordinary actor
- * but has fewer capabilities (ie, does not manage lifetimes or have it's own pool).
- *
- *
- * @param factories
- *     An object whose own property names are the names of properties to add to
- *     some reply packet (say, a target actor grip or the "listTabs" response
- *     form), and whose own property values are actor constructor functions, as
- *     documented for addTargetScopedActor
- *
- * @param parent
- *     The parent TargetActor with which the new actors
- *     will be associated. It should support whatever API the |factories|
- *     constructor functions might be interested in, as it is passed to them.
- *     For the sake of CommonCreateExtraActors itself, it should have at least
- *     the following properties:
- *
- *     - _extraActors
- *        An object whose own property names are factory table (and packet)
- *        property names, and whose values are no-argument actor constructors,
- *        of the sort that one can add to an ActorPool.
- *
- *     - conn
- *        The DebuggerServerConnection in which the new actors will participate.
- *
- *     - actorID
- *        The actor's name, for use as the new actors' parentID.
- * @param pool
- *     An object which implements the protocol.js Pool interface, and has the
- *     following properties
- *
- *     - manage
- *       a function which adds a given actor to an actor pool
- */
-
-function LazyActor(factory, parent, pool) {
-  this._options = factory.options;
-  this._parentActor = parent;
-  this._name = factory.name;
-  this._pool = pool;
-
-  // needed for taking a place in a pool
-  this.typeName = factory.name;
-}
-
-LazyActor.prototype = {
-  loadModule(id) {
-    const options = this._options;
-    try {
-      return require(id);
-      // Fetch the actor constructor
-    } catch (e) {
-      throw new Error(
-        `Unable to load actor module '${options.id}'\n${e.message}\n${e.stack}\n`
-      );
-    }
-  },
-
-  getConstructor() {
-    const options = this._options;
-    if (options.constructorFun) {
-      // Actor definition registered by ActorRegistryActor or testing helpers
-      return options.constructorFun;
-    }
-    // Lazy actor definition, where options contains all the information
-    // required to load the actor lazily.
-    // Exposes `name` attribute in order to allow removeXXXActor to match
-    // the actor by its actor constructor name.
-    this.name = options.constructorName;
-    const module = this.loadModule(options.id);
-    const constructor = module[options.constructorName];
-    if (!constructor) {
-      throw new Error(
-        `Unable to find actor constructor named '${this.name}'. (Is it exported?)`
-      );
-    }
-    return constructor;
-  },
-
-  /**
-   * Return the parent pool for this lazy actor.
-   */
-  parent: function() {
-    return this.conn && this.conn.poolFor(this.actorID);
-  },
-
-  /**
-   * This will only happen if the actor is destroyed before it is created
-   * We do not want to use the Pool destruction method, because this actor
-   * has no pool. However, it might have a parent that should unmange this
-   * actor
-   */
-  destroy() {
-    const parent = this.parent();
-    if (parent) {
-      parent.unmanage(this);
-    }
-  },
-
-  createActor() {
-    // Fetch the actor constructor
-    const Constructor = this.getConstructor();
-    // Instantiate a new actor instance
-    const conn = this._parentActor.conn;
-    // this should be taken care of once all actors are moved to protocol.js
-    const instance = new Constructor(conn, this._parentActor);
-    instance.conn = conn;
-
-    // We want the newly-constructed actor to completely replace the factory
-    // actor. Reusing the existing actor ID will make sure Pool.manage
-    // replaces the old actor with the new actor.
-    instance.actorID = this.actorID;
-
-    this._pool.manage(instance);
-
-    return instance;
-  }
-};
-
deleted file mode 100644
--- a/devtools/shared/protocol/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-DevToolsModules(
-    'lazy-pool.js',
-)
--- a/devtools/shared/security/tests/unit/testactors.js
+++ b/devtools/shared/security/tests/unit/testactors.js
@@ -1,17 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
 "use strict";
 
-const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
+const { ActorPool, appendExtraActors, createExtraActors } =
+  require("devtools/server/actors/common");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/thread");
 const { DebuggerServer } = require("devtools/server/main");
-const { ActorRegistry } = require("devtools/server/actor-registry");
 const promise = require("promise");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(global) {
   gTestGlobals.push(global);
 };
 
 // A mock tab list, for use by tests. This simply presents each global in
@@ -24,40 +25,42 @@ DebuggerServer.addTestGlobal = function(
 function TestTabList(connection) {
   this.conn = connection;
 
   // An array of actors for each global added with
   // DebuggerServer.addTestGlobal.
   this._targetActors = [];
 
   // A pool mapping those actors' names to the actors.
-  this._targetActorPool = new LazyPool(connection);
+  this._targetActorPool = new ActorPool(connection);
 
   for (const global of gTestGlobals) {
     const actor = new TestTargetActor(connection, global);
     actor.selected = false;
     this._targetActors.push(actor);
-    this._targetActorPool.manage(actor);
+    this._targetActorPool.addActor(actor);
   }
   if (this._targetActors.length > 0) {
     this._targetActors[0].selected = true;
   }
+
+  connection.addActorPool(this._targetActorPool);
 }
 
 TestTabList.prototype = {
   constructor: TestTabList,
   getList: function() {
     return promise.resolve([...this._targetActors]);
   }
 };
 
 exports.createRootActor = function createRootActor(connection) {
   const root = new RootActor(connection, {
     tabList: new TestTabList(connection),
-    globalActorFactories: ActorRegistry.globalActorFactories
+    globalActorFactories: DebuggerServer.globalActorFactories
   });
   root.applicationType = "xpcshell-tests";
   return root;
 };
 
 function TestTargetActor(connection, global) {
   this.conn = connection;
   this._global = global;
@@ -77,41 +80,46 @@ TestTargetActor.prototype = {
 
   get url() {
     return this._global.__name;
   },
 
   form: function() {
     const response = { actor: this.actorID, title: this._global.__name };
 
-    // Walk over target-scoped actors and add them to a new LazyPool.
-    const actorPool = new LazyPool(this.conn);
-    const actors = createExtraActors(
-      ActorRegistry.targetScopedActorFactories,
-      actorPool,
-      this
-    );
+    // Walk over target-scoped actors and add them to a new ActorPool.
+    const actorPool = new ActorPool(this.conn);
+    this._createExtraActors(DebuggerServer.targetScopedActorFactories, actorPool);
     if (!actorPool.isEmpty()) {
       this._targetActorPool = actorPool;
       this.conn.addActorPool(this._targetActorPool);
     }
 
-    return { ...response, ...actors };
+    this._appendExtraActors(response);
+
+    return response;
   },
 
   onAttach: function(request) {
     this._attached = true;
 
-    return { type: "tabAttached", threadActor: this._threadActor.actorID };
+    const response = { type: "tabAttached", threadActor: this._threadActor.actorID };
+    this._appendExtraActors(response);
+
+    return response;
   },
 
   onDetach: function(request) {
     if (!this._attached) {
       return { "error": "wrongState" };
     }
     return { type: "detached" };
-  }
+  },
+
+  /* Support for DebuggerServer.addTargetScopedActor. */
+  _createExtraActors: createExtraActors,
+  _appendExtraActors: appendExtraActors
 };
 
 TestTargetActor.prototype.requestTypes = {
   "attach": TestTargetActor.prototype.onAttach,
   "detach": TestTargetActor.prototype.onDetach
 };
--- a/devtools/shared/transport/tests/unit/head_dbg.js
+++ b/devtools/shared/transport/tests/unit/head_dbg.js
@@ -21,17 +21,16 @@ const Services = require("Services");
 // we can be sending large amounts of data. The test harness has
 // trouble dealing with logging all the data, and we end up with
 // intermittent time outs (e.g. bug 775924).
 // Services.prefs.setBoolPref("devtools.debugger.log", true);
 // Services.prefs.setBoolPref("devtools.debugger.log.verbose", true);
 // Enable remote debugging for the relevant tests.
 Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
 
-const { ActorRegistry } = require("devtools/server/actor-registry");
 const { DebuggerServer } = require("devtools/server/main");
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 
 // Convert an nsIScriptError 'flags' value into an appropriate string.
 function scriptErrorFlagsToKind(flags) {
   let kind;
   if (flags & Ci.nsIScriptError.warningFlag) {
     kind = "warning";
@@ -87,17 +86,17 @@ var listener = {
 };
 
 Services.console.registerListener(listener);
 
 /**
  * Initialize the testing debugger server.
  */
 function initTestDebuggerServer() {
-  ActorRegistry.registerModule("devtools/server/actors/thread", {
+  DebuggerServer.registerModule("devtools/server/actors/thread", {
     prefix: "script",
     constructor: "ScriptActor",
     type: { global: true, target: true }
   });
   const { createRootActor } = require("xpcshell-test/testactors");
   DebuggerServer.setRootActor(createRootActor);
   // Allow incoming connections.
   DebuggerServer.init();
--- a/devtools/shared/transport/tests/unit/test_bulk_error.js
+++ b/devtools/shared/transport/tests/unit/test_bulk_error.js
@@ -34,17 +34,17 @@ TestBulkActor.prototype = {
 
 };
 
 TestBulkActor.prototype.requestTypes = {
   "jsonReply": TestBulkActor.prototype.jsonReply
 };
 
 function add_test_bulk_actor() {
-  ActorRegistry.addGlobalActor({
+  DebuggerServer.addGlobalActor({
     constructorName: "TestBulkActor",
     constructorFun: TestBulkActor,
   }, "testBulk");
 }
 
 /** * Tests ***/
 
 var test_string_error = async function(transportFactory, onReady) {
--- a/devtools/shared/transport/tests/unit/test_client_server_bulk.js
+++ b/devtools/shared/transport/tests/unit/test_client_server_bulk.js
@@ -88,17 +88,17 @@ TestBulkActor.prototype = {
 
 TestBulkActor.prototype.requestTypes = {
   "bulkEcho": TestBulkActor.prototype.bulkEcho,
   "bulkReply": TestBulkActor.prototype.bulkReply,
   "jsonReply": TestBulkActor.prototype.jsonReply
 };
 
 function add_test_bulk_actor() {
-  ActorRegistry.addGlobalActor({
+  DebuggerServer.addGlobalActor({
     constructorName: "TestBulkActor",
     constructorFun: TestBulkActor,
   }, "testBulk");
 }
 
 /** * Reply Handlers ***/
 
 var replyHandlers = {
--- a/devtools/shared/transport/tests/unit/testactors-no-bulk.js
+++ b/devtools/shared/transport/tests/unit/testactors-no-bulk.js
@@ -1,20 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const { RootActor } = require("devtools/server/actors/root");
-const { ActorRegistry } = require("devtools/server/actor-registry");
+const { DebuggerServer } = require("devtools/server/main");
 
 /**
  * Root actor that doesn't have the bulk trait.
  */
 exports.createRootActor = function createRootActor(connection) {
   const root = new RootActor(connection, {
-    globalActorFactories: ActorRegistry.globalActorFactories
+    globalActorFactories: DebuggerServer.globalActorFactories
   });
   root.applicationType = "xpcshell-tests";
   root.traits = {
     bulk: false
   };
   return root;
 };
--- a/devtools/shared/transport/tests/unit/testactors.js
+++ b/devtools/shared/transport/tests/unit/testactors.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
-const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
+const { ActorPool, appendExtraActors, createExtraActors } =
+  require("devtools/server/actors/common");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/thread");
 const { DebuggerServer } = require("devtools/server/main");
-const { ActorRegistry } = require("devtools/server/actor-registry");
 const promise = require("promise");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(global) {
   gTestGlobals.push(global);
 };
 
 // A mock tab list, for use by tests. This simply presents each global in
@@ -24,40 +24,42 @@ DebuggerServer.addTestGlobal = function(
 function TestTabList(connection) {
   this.conn = connection;
 
   // An array of actors for each global added with
   // DebuggerServer.addTestGlobal.
   this._targetActors = [];
 
   // A pool mapping those actors' names to the actors.
-  this._targetActorPool = new LazyPool(connection);
+  this._targetActorPool = new ActorPool(connection);
 
   for (const global of gTestGlobals) {
     const actor = new TestTargetActor(connection, global);
     actor.selected = false;
     this._targetActors.push(actor);
-    this._targetActorPool.manage(actor);
+    this._targetActorPool.addActor(actor);
   }
   if (this._targetActors.length > 0) {
     this._targetActors[0].selected = true;
   }
+
+  connection.addActorPool(this._targetActorPool);
 }
 
 TestTabList.prototype = {
   constructor: TestTabList,
   getList: function() {
     return promise.resolve([...this._targetActors]);
   }
 };
 
 exports.createRootActor = function createRootActor(connection) {
   const root = new RootActor(connection, {
     tabList: new TestTabList(connection),
-    globalActorFactories: ActorRegistry.globalActorFactories
+    globalActorFactories: DebuggerServer.globalActorFactories
   });
   root.applicationType = "xpcshell-tests";
   return root;
 };
 
 function TestTargetActor(connection, global) {
   this.conn = connection;
   this._global = global;
@@ -77,40 +79,46 @@ TestTargetActor.prototype = {
 
   get url() {
     return this._global.__name;
   },
 
   form: function() {
     const response = { actor: this.actorID, title: this._global.__name };
 
-    // Walk over target-scoped actors and add them to a new LazyPool.
-    const actorPool = new LazyPool(this.conn);
-    const actors = createExtraActors(
-      ActorRegistry.targetScopedActorFactories,
-      actorPool,
-      this
-    );
+    // Walk over target-scoped actors and add them to a new ActorPool.
+    const actorPool = new ActorPool(this.conn);
+    this._createExtraActors(DebuggerServer.targetScopedActorFactories, actorPool);
     if (!actorPool.isEmpty()) {
       this._targetActorPool = actorPool;
+      this.conn.addActorPool(this._targetActorPool);
     }
 
-    return { ...response, ...actors };
+    this._appendExtraActors(response);
+
+    return response;
   },
 
   onAttach: function(request) {
     this._attached = true;
 
-    return { type: "tabAttached", threadActor: this._threadActor.actorID };
+    const response = { type: "tabAttached", threadActor: this._threadActor.actorID };
+    this._appendExtraActors(response);
+
+    return response;
   },
 
   onDetach: function(request) {
     if (!this._attached) {
       return { "error": "wrongState" };
     }
     return { type: "detached" };
-  }
+  },
+
+  /* Support for DebuggerServer.addTargetScopedActor. */
+  _createExtraActors: createExtraActors,
+  _appendExtraActors: appendExtraActors
 };
 
 TestTargetActor.prototype.requestTypes = {
   "attach": TestTargetActor.prototype.onAttach,
   "detach": TestTargetActor.prototype.onDetach
 };
--- a/mobile/android/modules/dbg-browser-actors.js
+++ b/mobile/android/modules/dbg-browser-actors.js
@@ -6,36 +6,36 @@
 /* eslint-env commonjs */
 
 "use strict";
 /**
  * Fennec-specific actors.
  */
 
 const { RootActor } = require("devtools/server/actors/root");
-const { ActorRegistry } = require("devtools/server/actor-registry");
+const { DebuggerServer } = require("devtools/server/main");
 const { BrowserTabList, BrowserAddonList, sendShutdownEvent } =
   require("devtools/server/actors/webbrowser");
 
 /**
  * Construct a root actor appropriate for use in a server running in a
  * browser on Android. The returned root actor:
- * - respects the factories registered with ActorRegistry.addGlobalActor,
+ * - respects the factories registered with DebuggerServer.addGlobalActor,
  * - uses a MobileTabList to supply tab actors,
  * - sends all navigator:browser window documents a Debugger:Shutdown event
  *   when it exits.
  *
  * * @param aConnection DebuggerServerConnection
  *        The conection to the client.
  */
 exports.createRootActor = function createRootActor(aConnection) {
   let parameters = {
     tabList: new MobileTabList(aConnection),
     addonList: new BrowserAddonList(aConnection),
-    globalActorFactories: ActorRegistry.globalActorFactories,
+    globalActorFactories: DebuggerServer.globalActorFactories,
     onShutdown: sendShutdownEvent
   };
   return new RootActor(aConnection, parameters);
 };
 
 /**
  * A live list of BrowserTabActors representing the current browser tabs,
  * to be provided to the root actor to answer 'listTabs' requests.
--- a/testing/talos/talos/tests/devtools/addon/content/tests/server/protocol.js
+++ b/testing/talos/talos/tests/devtools/addon/content/tests/server/protocol.js
@@ -29,18 +29,18 @@ module.exports = async function() {
   let tab = await testSetup(SIMPLE_URL);
   let messageManager = tab.linkedBrowser.messageManager;
 
   // Register a test actor within the content process
   messageManager.loadFrameScript("data:,(" + encodeURIComponent(
     `function () {
       const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 
-      const { ActorRegistry } = require("devtools/server/actor-registry");
-      ActorRegistry.registerModule("chrome://damp/content/tests/server/actor.js", {
+      const { DebuggerServer } = require("devtools/server/main");
+      DebuggerServer.registerModule("chrome://damp/content/tests/server/actor.js", {
         prefix: "dampTest",
         constructor: "DampTestActor",
         type: { target: true }
       });
     }`
   ) + ")()", true);
 
   // Create test payloads
--- a/testing/xpcshell/dbg-actors.js
+++ b/testing/xpcshell/dbg-actors.js
@@ -5,31 +5,30 @@
 /* globals require, exports */
 
 "use strict";
 
 const { DebuggerServer } = require("devtools/server/main");
 const { RootActor } = require("devtools/server/actors/root");
 const { BrowserTabList } = require("devtools/server/actors/webbrowser");
 const Services = require("Services");
-const { ActorRegistry } = require("devtools/server/actor-registry");
 
 /**
  * xpcshell-test (XPCST) specific actors.
  *
  */
 
 /**
  * Construct a root actor appropriate for use in a server running xpcshell
  * tests. <snip boilerplate> :)
  */
 function createRootActor(connection) {
   let parameters = {
     tabList: new XPCSTTabList(connection),
-    globalActorFactories: ActorRegistry.globalActorFactories,
+    globalActorFactories: DebuggerServer.globalActorFactories,
     onShutdown() {
       // If the user never switches to the "debugger" tab we might get a
       // shutdown before we've attached.
       Services.obs.notifyObservers(null, "xpcshell-test-devtools-shutdown");
     }
   };
   return new RootActor(connection, parameters);
 }