Bug 1473513 - create LazyActorClass based off ObservedActorFactory and RegisterdFactory classes for use in RootActor and BrowsingContextActor; r=ochameau
☠☠ backed out by 98b5ff9533ee ☠ ☠
authoryulia <ystartsev@mozilla.com>
Wed, 11 Jul 2018 18:34:22 +0200
changeset 490587 7acc52a7f81f9a69eaa768ce13768cd7fb75b4da
parent 490586 4e1e283b347e15910c87610577ffeba931de3eeb
child 490588 ce86ea60a31cc1944347306e28aaff1fbff38f59
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1473513
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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);
@@ -475,26 +474,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);
@@ -559,17 +560,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;
@@ -888,17 +888,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;
     }
@@ -1415,31 +1415,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
@@ -18,16 +18,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
 };