Bug 1473513 - create LazyActorClass based off ObservedActorFactory and RegisterdFactory classes for use in RootActor and BrowsingContextActor; r=ochameau draft
authoryulia <ystartsev@mozilla.com>
Wed, 11 Jul 2018 18:34:22 +0200
changeset 823481 efa1b7728c9a9a5e542637b28153e2d0db7170bc
parent 823480 11176b362149099d85f22cf918e04e7726b40921
child 823482 f98b9503a19c122480c3b9c222ec7daa3aa03c12
push id117694
push userbmo:ystartsev@mozilla.com
push dateFri, 27 Jul 2018 13:09:11 +0000
reviewersochameau
bugs1473513
milestone63.0a1
Bug 1473513 - create LazyActorClass based off ObservedActorFactory and RegisterdFactory classes for use in RootActor and BrowsingContextActor; r=ochameau MozReview-Commit-ID: LRuhNzBLrZh
devtools/server/actors/common.js
devtools/server/actors/root.js
devtools/server/actors/targets/browsing-context.js
devtools/server/main.js
devtools/server/tests/unit/test_promises_actor_attach.js
devtools/server/tests/unit/test_promises_actor_exist.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/testactors.js
--- a/devtools/server/actors/common.js
+++ b/devtools/server/actors/common.js
@@ -4,171 +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";
 
 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.
  *
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -3,18 +3,19 @@
 /* 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 { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
+const { ActorPool, appendExtraActors } = require("devtools/server/actors/common");
 const { Pool } = require("devtools/shared/protocol");
+const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
 const { DebuggerServer } = require("devtools/server/main");
 
 loader.lazyRequireGetter(this, "ChromeWindowTargetActor",
   "devtools/server/actors/targets/chrome-window", true);
 
 /* Root actor for the remote debugging protocol. */
 
 /**
@@ -95,18 +96,17 @@ 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 ActorPool(this.conn);
-  this.conn.addActorPool(this._globalActorPool);
+  this._globalActorPool = new LazyPool(this.conn);
 
   this._parentProcessTargetActor = null;
   this._processActors = new Map();
 }
 
 RootActor.prototype = {
   constructor: RootActor,
   applicationType: "browser",
@@ -235,20 +235,19 @@ RootActor.prototype = {
    */
   onGetRoot: function() {
     const reply = {
       from: this.actorID,
     };
 
     // Create global actors
     if (!this._globalActorPool) {
-      this._globalActorPool = new ActorPool(this.conn);
-      this.conn.addActorPool(this._globalActorPool);
+      this._globalActorPool = new LazyPool(this.conn);
     }
-    this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool);
+    createExtraActors(this._parameters.globalActorFactories, this._globalActorPool, this);
 
     // List the global actors
     this._appendExtraActors(reply);
 
     return reply;
   },
 
   /* The 'listTabs' request and the 'tabListChanged' notification. */
@@ -514,25 +513,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._globalActorPool.removeActor(this._parentProcessTargetActor);
+        this._parentProcessTargetActor.destroy();
         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.addActor(this._parentProcessTargetActor);
+        this._globalActorPool.manage(this._parentProcessTargetActor);
       }
 
       return { form: this._parentProcessTargetActor.form() };
     }
 
     const { id } = request;
     const mm = Services.ppmm.getChildAt(id);
     if (!mm) {
@@ -560,28 +559,27 @@ 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 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)) {
-        this._globalActorPool.removeActor(actor);
+      if (this._globalActorPool.has(actor.actorID)) {
+        actor.destroy();
       }
       if (this._tabTargetActorPool) {
         // Iterate over BrowsingContextTargetActor instances to also remove target-scoped
         // actors created during listTabs for each document.
         this._tabTargetActorPool.forEach(tab => {
           tab.removeActorByName(name);
         });
       }
--- a/devtools/server/actors/targets/browsing-context.js
+++ b/devtools/server/actors/targets/browsing-context.js
@@ -18,35 +18,34 @@
  * 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 {
-  ActorPool, createExtraActors, appendExtraActors
-} = require("devtools/server/actors/common");
+var { 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 { 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);
@@ -482,26 +481,28 @@ 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 ActorPool(this.conn);
-      this.conn.addActorPool(this._targetScopedActorPool);
+      this._targetScopedActorPool = new LazyPool(this.conn);
     }
 
     // Walk over target-scoped actor factories and make sure they are all
     // instantiated and added into the ActorPool.
-    this._createExtraActors(DebuggerServer.targetScopedActorFactories,
-      this._targetScopedActorPool);
+    const addedActors = createExtraActors(
+      DebuggerServer.targetScopedActorFactories,
+      this._targetScopedActorPool,
+      this
+    );
 
-    this._appendExtraActors(response);
+    Object.assign(response, addedActors);
     return response;
   },
 
   /**
    * Called when the actor is removed from the connection.
    */
   destroy() {
     Actor.prototype.destroy.call(this);
@@ -566,17 +567,16 @@ const browsingContextTargetPrototype = {
         && 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;
@@ -895,17 +895,17 @@ 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.conn.removeActorPool(this._targetScopedActorPool);
+      this._targetScopedActorPool.destroy();
       this._targetScopedActorPool = null;
     }
 
     // Make sure that no more workerListChanged notifications are sent.
     if (this._workerTargetActorList !== null) {
       this._workerTargetActorList.onListChanged = null;
       this._workerTargetActorList = null;
     }
@@ -1424,31 +1424,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.addActor(actor);
+    this._targetScopedActorPool.manage(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/main.js
+++ b/devtools/server/main.js
@@ -5,18 +5,17 @@
 "use strict";
 
 /**
  * Toolkit glue for the remote debugging protocol, loaded into the
  * debugging global.
  */
 var { Ci, Cc } = require("chrome");
 var Services = require("Services");
-var { ActorPool, RegisteredActorFactory,
-      ObservedActorFactory } = require("devtools/server/actors/common");
+var { ActorPool } = 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);
@@ -1160,39 +1159,38 @@ var DebuggerServer = {
 
   /**
    * 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
+   * @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(actor, name) {
+  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 (DebuggerServer.targetScopedActorFactories.hasOwnProperty(name)) {
       throw Error(name + " already exists");
     }
-    DebuggerServer.targetScopedActorFactories[name] =
-      new RegisteredActorFactory(actor, name);
+    DebuggerServer.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.
    *
@@ -1205,18 +1203,18 @@ var DebuggerServer = {
   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)) {
+        if ((handler.options.constructorName == actor.name) ||
+            (handler.options.id == actor.id)) {
           name = factoryName;
           break;
         }
       }
     }
     if (!name) {
       return;
     }
@@ -1231,38 +1229,38 @@ var DebuggerServer = {
 
   /**
    * 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
+   * @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(actor, name) {
+  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 (DebuggerServer.globalActorFactories.hasOwnProperty(name)) {
       throw Error(name + " already exists");
     }
-    DebuggerServer.globalActorFactories[name] = new RegisteredActorFactory(actor, name);
+    DebuggerServer.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.
    *
@@ -1275,18 +1273,18 @@ var DebuggerServer = {
   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)) {
+        if ((handler.options.constructorName == actor.name) ||
+            (handler.options.id == actor.id)) {
           name = factoryName;
           break;
         }
       }
     }
     if (!name) {
       return;
     }
@@ -1516,40 +1514,37 @@ DebuggerServerConnection.prototype = {
     if (actorID === "root") {
       return this.rootActor;
     }
 
     return null;
   },
 
   _getOrCreateActor(actorID) {
-    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;
-    }
+    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;
+      }
 
-    // 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));
+      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 + ".");
       }
-    } 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 actor;
+    return null;
   },
 
   poolFor(actorID) {
     for (const pool of this._extraPools) {
       if (pool.has(actorID)) {
         return pool;
       }
     }
--- a/devtools/server/tests/unit/test_promises_actor_attach.js
+++ b/devtools/server/tests/unit/test_promises_actor_attach.js
@@ -17,19 +17,18 @@ 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.");
 
-  const [ tabResponse ] = await attachTab(client, targetTab);
-
-  await testAttach(client, tabResponse);
+  await attachTab(client, targetTab);
+  await testAttach(client, targetTab);
 
   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,18 +19,16 @@ 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/testactors.js
+++ b/devtools/server/tests/unit/testactors.js
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
+const { appendExtraActors } = require("devtools/server/actors/common");
+const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/thread");
 const { DebuggerServer } = require("devtools/server/main");
 const { TabSources } = require("devtools/server/actors/utils/TabSources");
 const makeDebugger = require("devtools/server/actors/utils/make-debugger");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(global) {
@@ -35,29 +36,27 @@ 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 ActorPool(connection);
+  this._targetActorPool = new LazyPool(connection);
 
   for (const global of gTestGlobals) {
     const actor = new TestTargetActor(connection, global);
     actor.selected = false;
     this._targetActors.push(actor);
-    this._targetActorPool.addActor(actor);
+    this._targetActorPool.manage(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]);
   }
 };
@@ -106,36 +105,35 @@ 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 ActorPool.
-    const actorPool = new ActorPool(this.conn);
-    this._createExtraActors(DebuggerServer.targetScopedActorFactories, actorPool);
+    // Walk over target-scoped actors and add them to a new LazyPool.
+    const actorPool = new LazyPool(this.conn);
+    const actors = createExtraActors(
+      DebuggerServer.targetScopedActorFactories,
+      actorPool,
+      this
+    );
     if (!actorPool.isEmpty()) {
       this._targetActorPool = actorPool;
       this.conn.addActorPool(this._targetActorPool);
     }
 
-    this._appendExtraActors(response);
-
-    return response;
+    return { ...response, ...actors };
   },
 
   onAttach: function(request) {
     this._attached = true;
 
-    const response = { type: "tabAttached", threadActor: this.threadActor.actorID };
-    this._appendExtraActors(response);
-
-    return response;
+    return { type: "tabAttached", threadActor: this.threadActor.actorID };
   },
 
   onDetach: function(request) {
     if (!this._attached) {
       return { "error": "wrongState" };
     }
     return { type: "detached" };
   },
@@ -151,17 +149,16 @@ TestTargetActor.prototype = {
     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
@@ -17,16 +17,17 @@ 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,16 +821,20 @@ 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;
   },
 
@@ -853,17 +857,19 @@ 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);
     }
 
     // If the actor is already in a pool, remove it without destroying it.
-    const parent = actor.parent();
+    // 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;
   },
 
@@ -876,23 +882,29 @@ 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) {
-    return this.__poolMap ? this._poolMap.get(actorID) : null;
+    if (this.__poolMap) {
+      return this._poolMap.get(actorID);
+    }
+    return null;
   },
 
   // Same as actor, should update debugger connection to use 'actor'
   // and then remove this.
   get: function(actorID) {
-    return this.__poolMap ? this._poolMap.get(actorID) : null;
+    if (this.__poolMap) {
+      return this._poolMap.get(actorID);
+    }
+    return 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.
copy from devtools/server/actors/common.js
copy to devtools/shared/protocol/lazy-pool.js
--- a/devtools/server/actors/common.js
+++ b/devtools/shared/protocol/lazy-pool.js
@@ -1,505 +1,231 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { method } = require("devtools/shared/protocol");
+const { extend } = require("devtools/shared/extend");
+const { Pool } = 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
+ * A Special Pool for RootActor and BrowsingContextTargetActor, which allows lazy loaded
+ * actors to be added to the pool.
  *
- * @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
+ * 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 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");
+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();
       }
-      // 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;
+      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;
 
 /**
- * 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.
+ * 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
  *
- * 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.
+ * The target actor uses this to instantiate actors that other
+ * parts of the browser have specified with DebuggerServer.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 and addGlobalActor.
+ *     documented for addTargetScopedActor
  *
- * @param this
- *     The RootActor or BrowsingContextTargetActor with which the new actors
+ * @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
  */
-exports.createExtraActors = function createExtraActors(factories, pool) {
+function createExtraActors(registeredActors, pool, parent) {
   // Walk over global actors added by extensions.
-  for (const name in factories) {
-    let actor = this._extraActors[name];
+  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 = factories[name].createObservedActorFactory(this.conn, this);
-      this._extraActors[name] = 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.addActor(actor);
+      pool.manage(actor);
     }
+    nameMap[name] = actor.actorID;
   }
-};
+  return nameMap;
+}
+
+exports.createExtraActors = createExtraActors;
 
 /**
- * Append the extra actors in |this._extraActors|, constructed by a prior call
- * to CommonCreateExtraActors, to |object|.
+ * 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:
  *
- * @param object
- *     The object to which the extra actors should be added, under the
- *     property names given in the |factories| table passed to
- *     CommonCreateExtraActors.
+ *     - _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.
  *
- * @param this
- *     The RootActor or BrowsingContextTargetActor whose |_extraActors| table we
- *     should use; see above.
+ *     - 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
  */
-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;
-  this._actors = {};
+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;
 }
 
-ActorPool.prototype = {
-  /**
-   * Destroy the pool. This will remove all actors from the pool.
-   */
-  destroy: function APDestroy() {
-    for (const id in this._actors) {
-      this.removeActor(this._actors[id]);
+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`
+      );
     }
   },
 
-  /**
-   * Add an actor to the pool. If the actor doesn't have an ID, allocate one
-   * from the connection.
-   *
-   * @param Object actor
-   *        The actor to be added to the pool.
-   */
-  addActor: function APAddActor(actor) {
-    actor.conn = this.conn;
-    if (!actor.actorID) {
-      // Older style actors use actorPrefix, while protocol.js-based actors use typeName
-      const prefix = actor.actorPrefix || actor.typeName;
-      if (!prefix) {
-        throw new Error("Actor should precify either `actorPrefix` or `typeName` " +
-                        "attribute");
-      }
-      actor.actorID = this.conn.allocID(prefix || undefined);
-    }
-
-    // If the actor is already in a pool, remove it without destroying it.
-    if (actor.registeredPool) {
-      delete actor.registeredPool._actors[actor.actorID];
+  getConstructor() {
+    const options = this._options;
+    if (options.constructorFun) {
+      // Actor definition registered by ActorRegistryActor or testing helpers
+      return options.constructorFun;
     }
-    actor.registeredPool = this;
-
-    this._actors[actor.actorID] = actor;
-  },
-
-  /**
-   * Remove an actor from the pool. If the actor has a destroy method, call it.
-   */
-  removeActor(actor) {
-    delete this._actors[actor.actorID];
-    if (actor.destroy) {
-      actor.destroy();
-      return;
+    // 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?)`
+      );
     }
-    // Obsolete destruction method name (might still be used by custom actors)
-    if (actor.disconnect) {
-      actor.disconnect();
-    }
-  },
-
-  get: function APGet(actorID) {
-    return this._actors[actorID] || undefined;
-  },
-
-  has: function APHas(actorID) {
-    return actorID in this._actors;
-  },
-
-  /**
-   * Returns true if the pool is empty.
-   */
-  isEmpty: function APIsEmpty() {
-    return Object.keys(this._actors).length == 0;
+    return constructor;
   },
 
   /**
-   * Match the api expected by the protocol library.
+   * Return the parent pool for this lazy actor.
    */
-  unmanage: function(actor) {
-    return this.removeActor(actor);
+  parent: function() {
+    return this.conn && this.conn.poolFor(this.actorID);
   },
 
-  forEach: function(callback) {
-    for (const name in this._actors) {
-      callback(this._actors[name]);
+  /**
+   * 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);
     }
   },
-};
-
-exports.ActorPool = ActorPool;
-
-/**
- * An OriginalLocation represents a location in an original source.
- *
- * @param SourceActor actor
- *        A SourceActor representing an original source.
- * @param Number line
- *        A line within the given source.
- * @param Number column
- *        A column within the given line.
- * @param String name
- *        The name of the symbol corresponding to this OriginalLocation.
- */
-function OriginalLocation(actor, line, column, name) {
-  this._connection = actor ? actor.conn : null;
-  this._actorID = actor ? actor.actorID : undefined;
-  this._line = line;
-  this._column = column;
-  this._name = name;
-}
-
-OriginalLocation.fromGeneratedLocation = function(generatedLocation) {
-  return new OriginalLocation(
-    generatedLocation.generatedSourceActor,
-    generatedLocation.generatedLine,
-    generatedLocation.generatedColumn
-  );
-};
-
-OriginalLocation.prototype = {
-  get originalSourceActor() {
-    return this._connection ? this._connection.getActor(this._actorID) : null;
-  },
 
-  get originalUrl() {
-    const actor = this.originalSourceActor;
-    const source = actor.source;
-    return source ? source.url : actor._originalUrl;
-  },
-
-  get originalLine() {
-    return this._line;
-  },
-
-  get originalColumn() {
-    return this._column;
-  },
-
-  get originalName() {
-    return this._name;
-  },
-
-  get generatedSourceActor() {
-    throw new Error("Shouldn't  access generatedSourceActor from an OriginalLocation");
-  },
+  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;
 
-  get generatedLine() {
-    throw new Error("Shouldn't access generatedLine from an OriginalLocation");
-  },
-
-  get generatedColumn() {
-    throw new Error("Shouldn't access generatedColumn from an Originallocation");
-  },
+    // 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;
 
-  equals: function(other) {
-    return this.originalSourceActor.url == other.originalSourceActor.url &&
-           this.originalLine === other.originalLine &&
-           (this.originalColumn === undefined ||
-            other.originalColumn === undefined ||
-            this.originalColumn === other.originalColumn);
-  },
+    this._pool.manage(instance);
 
-  toJSON: function() {
-    return {
-      source: this.originalSourceActor.form(),
-      line: this.originalLine,
-      column: this.originalColumn
-    };
+    return instance;
   }
 };
 
-exports.OriginalLocation = OriginalLocation;
-
-/**
- * A GeneratedLocation represents a location in a generated source.
- *
- * @param SourceActor actor
- *        A SourceActor representing a generated source.
- * @param Number line
- *        A line within the given source.
- * @param Number column
- *        A column within the given line.
- */
-function GeneratedLocation(actor, line, column, lastColumn) {
-  this._connection = actor ? actor.conn : null;
-  this._actorID = actor ? actor.actorID : undefined;
-  this._line = line;
-  this._column = column;
-  this._lastColumn = (lastColumn !== undefined) ? lastColumn : column + 1;
-}
-
-GeneratedLocation.fromOriginalLocation = function(originalLocation) {
-  return new GeneratedLocation(
-    originalLocation.originalSourceActor,
-    originalLocation.originalLine,
-    originalLocation.originalColumn
-  );
-};
-
-GeneratedLocation.prototype = {
-  get originalSourceActor() {
-    throw new Error();
-  },
-
-  get originalUrl() {
-    throw new Error("Shouldn't access originalUrl from a GeneratedLocation");
-  },
-
-  get originalLine() {
-    throw new Error("Shouldn't access originalLine from a GeneratedLocation");
-  },
-
-  get originalColumn() {
-    throw new Error("Shouldn't access originalColumn from a GeneratedLocation");
-  },
-
-  get originalName() {
-    throw new Error("Shouldn't access originalName from a GeneratedLocation");
-  },
-
-  get generatedSourceActor() {
-    return this._connection ? this._connection.getActor(this._actorID) : null;
-  },
-
-  get generatedLine() {
-    return this._line;
-  },
-
-  get generatedColumn() {
-    return this._column;
-  },
-
-  get generatedLastColumn() {
-    return this._lastColumn;
-  },
-
-  equals: function(other) {
-    return this.generatedSourceActor.url == other.generatedSourceActor.url &&
-           this.generatedLine === other.generatedLine &&
-           (this.generatedColumn === undefined ||
-            other.generatedColumn === undefined ||
-            this.generatedColumn === other.generatedColumn);
-  },
-
-  toJSON: function() {
-    return {
-      source: this.generatedSourceActor.form(),
-      line: this.generatedLine,
-      column: this.generatedColumn,
-      lastColumn: this.generatedLastColumn
-    };
-  }
-};
-
-exports.GeneratedLocation = GeneratedLocation;
-
-/**
- * A method decorator that ensures the actor is in the expected state before
- * proceeding. If the actor is not in the expected state, the decorated method
- * returns a rejected promise.
- *
- * The actor's state must be at this.state property.
- *
- * @param String expectedState
- *        The expected state.
- * @param String activity
- *        Additional info about what's going on.
- * @param Function methodFunc
- *        The actor method to proceed with when the actor is in the expected
- *        state.
- *
- * @returns Function
- *          The decorated method.
- */
-function expectState(expectedState, methodFunc, activity) {
-  return function(...args) {
-    if (this.state !== expectedState) {
-      const msg = `Wrong state while ${activity}:` +
-                  `Expected '${expectedState}', ` +
-                  `but current state is '${this.state}'.`;
-      return Promise.reject(new Error(msg));
-    }
-
-    return methodFunc.apply(this, args);
-  };
-}
-
-exports.expectState = expectState;
-
-/**
- * Proxies a call from an actor to an underlying module, stored
- * as `bridge` on the actor. This allows a module to be defined in one
- * place, usable by other modules/actors on the server, but a separate
- * module defining the actor/RDP definition.
- *
- * @see Framerate implementation: devtools/server/performance/framerate.js
- * @see Framerate actor definition: devtools/server/actors/framerate.js
- */
-function actorBridge(methodName, definition = {}) {
-  return method(function() {
-    return this.bridge[methodName].apply(this.bridge, arguments);
-  }, definition);
-}
-exports.actorBridge = actorBridge;
-
-/**
- * Like `actorBridge`, but without a spec definition, for when the actor is
- * created with `ActorClassWithSpec` rather than vanilla `ActorClass`.
- */
-function actorBridgeWithSpec(methodName) {
-  return method(function() {
-    return this.bridge[methodName].apply(this.bridge, arguments);
-  });
-}
-exports.actorBridgeWithSpec = actorBridgeWithSpec;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/protocol/moz.build
@@ -0,0 +1,9 @@
+# -*- 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,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const { ActorPool, appendExtraActors, createExtraActors } =
-  require("devtools/server/actors/common");
+const { appendExtraActors } = require("devtools/server/actors/common");
+const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/thread");
 const { DebuggerServer } = require("devtools/server/main");
 const promise = require("promise");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(global) {
   gTestGlobals.push(global);
@@ -25,29 +25,27 @@ 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 ActorPool(connection);
+  this._targetActorPool = new LazyPool(connection);
 
   for (const global of gTestGlobals) {
     const actor = new TestTargetActor(connection, global);
     actor.selected = false;
     this._targetActors.push(actor);
-    this._targetActorPool.addActor(actor);
+    this._targetActorPool.manage(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]);
   }
 };
@@ -80,46 +78,44 @@ 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 ActorPool.
-    const actorPool = new ActorPool(this.conn);
-    this._createExtraActors(DebuggerServer.targetScopedActorFactories, actorPool);
+    // Walk over target-scoped actors and add them to a new LazyPool.
+    const actorPool = new LazyPool(this.conn);
+    const actors = createExtraActors(
+      DebuggerServer.targetScopedActorFactories,
+      actorPool,
+      this
+    );
     if (!actorPool.isEmpty()) {
       this._targetActorPool = actorPool;
       this.conn.addActorPool(this._targetActorPool);
     }
 
-    this._appendExtraActors(response);
-
-    return response;
+    return { ...response, ...actors };
   },
 
   onAttach: function(request) {
     this._attached = true;
 
-    const response = { type: "tabAttached", threadActor: this._threadActor.actorID };
-    this._appendExtraActors(response);
-
-    return response;
+    return { type: "tabAttached", threadActor: this._threadActor.actorID };
   },
 
   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/testactors.js
+++ b/devtools/shared/transport/tests/unit/testactors.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
-const { ActorPool, appendExtraActors, createExtraActors } =
-  require("devtools/server/actors/common");
+const { appendExtraActors } = require("devtools/server/actors/common");
+const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/thread");
 const { DebuggerServer } = require("devtools/server/main");
 const promise = require("promise");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(global) {
   gTestGlobals.push(global);
@@ -24,29 +24,27 @@ 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 ActorPool(connection);
+  this._targetActorPool = new LazyPool(connection);
 
   for (const global of gTestGlobals) {
     const actor = new TestTargetActor(connection, global);
     actor.selected = false;
     this._targetActors.push(actor);
-    this._targetActorPool.addActor(actor);
+    this._targetActorPool.manage(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]);
   }
 };
@@ -79,46 +77,44 @@ 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 ActorPool.
-    const actorPool = new ActorPool(this.conn);
-    this._createExtraActors(DebuggerServer.targetScopedActorFactories, actorPool);
+    // Walk over target-scoped actors and add them to a new LazyPool.
+    const actorPool = new LazyPool(this.conn);
+    const actors = createExtraActors(
+      DebuggerServer.targetScopedActorFactories,
+      actorPool,
+      this
+    );
     if (!actorPool.isEmpty()) {
       this._targetActorPool = actorPool;
       this.conn.addActorPool(this._targetActorPool);
     }
 
-    this._appendExtraActors(response);
-
-    return response;
+    return { ...response, ...actors };
   },
 
   onAttach: function(request) {
     this._attached = true;
 
-    const response = { type: "tabAttached", threadActor: this._threadActor.actorID };
-    this._appendExtraActors(response);
-
-    return response;
+    return { type: "tabAttached", threadActor: this._threadActor.actorID };
   },
 
   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
 };