Bug 1270173 - Move protocol.js from devtools/server to devtools/shared. r=ejpbruel
authorJennifer Fong <jfong@mozilla.com>
Fri, 06 May 2016 09:19:00 +0200
changeset 296458 a3d59df2a8e356daae28128310db872d61c01f8f
parent 296457 e818d8cb31a944880da225cc60f44698cbd7e6f7
child 296459 de0ebe1a027d93d9a00588eb3ec63a93c136dd25
push id30240
push usercbook@mozilla.com
push dateMon, 09 May 2016 09:16:22 +0000
treeherdermozilla-central@fd8bdef8ef75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersejpbruel
bugs1270173
milestone49.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 1270173 - Move protocol.js from devtools/server to devtools/shared. r=ejpbruel
devtools/client/fronts/storage.js
devtools/client/fronts/stylesheets.js
devtools/client/inspector/markup/test/actor_events_form.js
devtools/client/shared/test/test-actor.js
devtools/server/actors/actor-registry.js
devtools/server/actors/animation.js
devtools/server/actors/breakpoint.js
devtools/server/actors/call-watcher.js
devtools/server/actors/canvas.js
devtools/server/actors/common.js
devtools/server/actors/csscoverage.js
devtools/server/actors/device.js
devtools/server/actors/director-manager.js
devtools/server/actors/director-registry.js
devtools/server/actors/environment.js
devtools/server/actors/eventlooplag.js
devtools/server/actors/frame.js
devtools/server/actors/framerate.js
devtools/server/actors/gcli.js
devtools/server/actors/heap-snapshot-file.js
devtools/server/actors/highlighters.js
devtools/server/actors/inspector.js
devtools/server/actors/layout.js
devtools/server/actors/memory.js
devtools/server/actors/memprof.js
devtools/server/actors/performance-entries.js
devtools/server/actors/performance-recording.js
devtools/server/actors/performance.js
devtools/server/actors/preference.js
devtools/server/actors/profiler.js
devtools/server/actors/promises.js
devtools/server/actors/root.js
devtools/server/actors/script.js
devtools/server/actors/settings.js
devtools/server/actors/source.js
devtools/server/actors/storage.js
devtools/server/actors/string.js
devtools/server/actors/styleeditor.js
devtools/server/actors/styles.js
devtools/server/actors/stylesheets.js
devtools/server/actors/timeline.js
devtools/server/actors/webaudio.js
devtools/server/actors/webgl.js
devtools/server/actors/worker.js
devtools/server/docs/protocol.js.md
devtools/server/main.js
devtools/server/moz.build
devtools/server/protocol.js
devtools/server/tests/mochitest/hello-actor.js
devtools/server/tests/unit/hello-actor.js
devtools/server/tests/unit/registertestactors-03.js
devtools/server/tests/unit/test_protocol_abort.js
devtools/server/tests/unit/test_protocol_async.js
devtools/server/tests/unit/test_protocol_children.js
devtools/server/tests/unit/test_protocol_formtype.js
devtools/server/tests/unit/test_protocol_longstring.js
devtools/server/tests/unit/test_protocol_simple.js
devtools/server/tests/unit/test_protocol_stack.js
devtools/server/tests/unit/test_protocol_unregister.js
devtools/shared/moz.build
devtools/shared/protocol.js
devtools/shared/specs/storage.js
devtools/shared/specs/stylesheets.js
--- a/devtools/client/fronts/storage.js
+++ b/devtools/client/fronts/storage.js
@@ -1,14 +1,14 @@
 /* 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 protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const specs = require("devtools/shared/specs/storage");
 
 for (let childSpec of Object.values(specs.childSpecs)) {
   protocol.FrontClassWithSpec(childSpec, {
     form(form, detail) {
       if (detail === "actorid") {
         this.actorID = form;
         return null;
--- a/devtools/client/fronts/stylesheets.js
+++ b/devtools/client/fronts/stylesheets.js
@@ -1,14 +1,14 @@
 /* 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 { Front, FrontClassWithSpec } = require("devtools/server/protocol.js");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol.js");
 const {
   getIndentationFromPrefs,
   getIndentationFromString
 } = require("devtools/shared/indentation");
 const {
   originalSourceSpec,
   mediaRuleSpec,
   styleSheetSpec,
--- a/devtools/client/inspector/markup/test/actor_events_form.js
+++ b/devtools/client/inspector/markup/test/actor_events_form.js
@@ -4,17 +4,17 @@
 "use strict";
 
 // This test actor is used for testing the addition of custom form data
 // on NodeActor. Custom form property is set when 'form' event is sent
 // by NodeActor actor (see 'onNodeActorForm' method).
 
 const Events = require("sdk/event/core");
 const {ActorClass, Actor, FrontClass, Front, method} =
-  require("devtools/server/protocol");
+  require("devtools/shared/protocol");
 
 const {NodeActor} = require("devtools/server/actors/inspector");
 
 var EventsFormActor = ActorClass({
   typeName: "eventsFormActor",
 
   initialize: function () {
     Actor.prototype.initialize.apply(this, arguments);
--- a/devtools/client/shared/test/test-actor.js
+++ b/devtools/client/shared/test/test-actor.js
@@ -19,17 +19,17 @@ var loader = Cc["@mozilla.org/moz/jssubs
 // it rely on the |window| global.
 let EventUtils = {};
 EventUtils.window = {};
 EventUtils.parent = {};
 EventUtils._EU_Ci = Components.interfaces;
 EventUtils._EU_Cc = Components.classes;
 loader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {Arg, Option, method, RetVal, types} = protocol;
 
 var dumpn = msg => {
   dump(msg + "\n");
 }
 
 /**
  * Get the instance of CanvasFrameAnonymousContentHelper used by a given
--- a/devtools/server/actors/actor-registry.js
+++ b/devtools/server/actors/actor-registry.js
@@ -1,15 +1,15 @@
 /* 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 protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { method, custom, Arg, Option, RetVal } = protocol;
 
 const { Cu, CC, components } = require("chrome");
 const Services = require("Services");
 const { DebuggerServer } = require("devtools/server/main");
 const { registerActor, unregisterActor } = require("devtools/server/actors/utils/actor-registry-utils");
 
 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -23,17 +23,17 @@
  *   http://w3c.github.io/web-animations/
  * - WebAnimation WebIDL files:
  *   /dom/webidl/Animation*.webidl
  */
 
 const {Cu} = require("chrome");
 const promise = require("promise");
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {ActorClass, Actor, FrontClass, Front,
        Arg, method, RetVal, types} = protocol;
 // Make sure the nodeActor type is know here.
 const {NodeActor} = require("devtools/server/actors/inspector");
 const events = require("sdk/event/core");
 
 // Types of animations.
 const ANIMATION_TYPES = {
--- a/devtools/server/actors/breakpoint.js
+++ b/devtools/server/actors/breakpoint.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { ActorClass, method } = require("devtools/server/protocol");
+const { ActorClass, method } = require("devtools/shared/protocol");
 
 /**
  * Set breakpoints on all the given entry points with the given
  * BreakpointActor as the handler.
  *
  * @param BreakpointActor actor
  *        The actor handling the breakpoint hits.
  * @param Array entryPoints
--- a/devtools/server/actors/call-watcher.js
+++ b/devtools/server/actors/call-watcher.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {Cc, Ci, Cu, Cr} = require("chrome");
 const events = require("sdk/event/core");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {serializeStack, parseStack} = require("toolkit/loader");
 
 const {on, once, off, emit} = events;
 const {method, Arg, Option, RetVal} = protocol;
 
 /**
  * Type describing a single function call in a stack trace.
  */
--- a/devtools/server/actors/canvas.js
+++ b/devtools/server/actors/canvas.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {Cc, Ci, Cu, Cr} = require("chrome");
 const events = require("sdk/event/core");
 const promise = require("promise");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {CallWatcherActor, CallWatcherFront} = require("devtools/server/actors/call-watcher");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const {WebGLPrimitiveCounter} = require("devtools/server/primitive");
 
 const {on, once, off, emit} = events;
 const {method, custom, Arg, Option, RetVal} = protocol;
 
 const CANVAS_CONTEXTS = [
--- a/devtools/server/actors/common.js
+++ b/devtools/server/actors/common.js
@@ -2,17 +2,17 @@
 /* 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 promise = require("promise");
-const { method } = require("devtools/server/protocol");
+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
--- a/devtools/server/actors/csscoverage.js
+++ b/devtools/server/actors/csscoverage.js
@@ -5,17 +5,17 @@
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 
 const Services = require("Services");
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 
 const events = require("sdk/event/core");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { method, custom, RetVal, Arg } = protocol;
 
 loader.lazyGetter(this, "DOMUtils", () => {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
 });
 loader.lazyRequireGetter(this, "stylesheets", "devtools/server/actors/stylesheets");
 loader.lazyRequireGetter(this, "CssLogic", "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
--- a/devtools/server/actors/device.js
+++ b/devtools/server/actors/device.js
@@ -1,15 +1,15 @@
 /* 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/. */
 
 const {Cc, Ci, Cu, CC} = require("chrome");
 const Services = require("Services");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {method, RetVal} = protocol;
 const promise = require("promise");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {DebuggerServer} = require("devtools/server/main");
 const {getSystemInfo, getSetting} = require("devtools/shared/system");
 
 Cu.importGlobalProperties(["FileReader"]);
 Cu.import("resource://gre/modules/PermissionsTable.jsm")
--- a/devtools/server/actors/director-manager.js
+++ b/devtools/server/actors/director-manager.js
@@ -2,17 +2,17 @@
 /* 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 events = require("sdk/event/core");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 
 const { Cu, Ci } = require("chrome");
 
 const { on, once, off, emit } = events;
 const { method, Arg, Option, RetVal, types } = protocol;
 
 const { sandbox, evaluate } = require('sdk/loader/sandbox');
 const { Class } = require("sdk/core/heritage");
--- a/devtools/server/actors/director-registry.js
+++ b/devtools/server/actors/director-registry.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { method, Arg, Option, RetVal } = protocol;
 
 const {DebuggerServer} = require("devtools/server/main");
 
 /**
  * Error Messages
  */
 
--- a/devtools/server/actors/environment.js
+++ b/devtools/server/actors/environment.js
@@ -1,16 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { ActorClass, Arg, RetVal, method } = require("devtools/server/protocol");
+const { ActorClass, Arg, RetVal, method } = require("devtools/shared/protocol");
 const { createValueGrip } = require("devtools/server/actors/object");
 
 /**
  * Creates an EnvironmentActor. EnvironmentActors are responsible for listing
  * the bindings introduced by a lexical environment and assigning new values to
  * those identifier bindings.
  *
  * @param Debugger.Environment aEnvironment
--- a/devtools/server/actors/eventlooplag.js
+++ b/devtools/server/actors/eventlooplag.js
@@ -6,17 +6,17 @@
  * The eventLoopLag actor emits "event-loop-lag" events when the event
  * loop gets unresponsive. The event comes with a "time" property (the
  * duration of the lag in milliseconds).
  */
 
 const {Ci, Cu} = require("chrome");
 const Services = require("Services");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {method, Arg, RetVal} = protocol;
 const events = require("sdk/event/core");
 
 var EventLoopLagActor = exports.EventLoopLagActor = protocol.ActorClass({
 
   typeName: "eventLoopLag",
 
   _observerAdded: false,
--- a/devtools/server/actors/frame.js
+++ b/devtools/server/actors/frame.js
@@ -3,17 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { ActorPool } = require("devtools/server/actors/common");
 const { createValueGrip } = require("devtools/server/actors/object");
-const { ActorClass } = require("devtools/server/protocol");
+const { ActorClass } = require("devtools/shared/protocol");
 
 /**
  * An actor for a specified stack frame.
  */
 let FrameActor = ActorClass({
   typeName: "frame",
 
   /**
--- a/devtools/server/actors/framerate.js
+++ b/devtools/server/actors/framerate.js
@@ -1,14 +1,14 @@
 /* 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 protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { actorBridge } = require("devtools/server/actors/common");
 const { method, custom, Arg, Option, RetVal } = protocol;
 const { on, once, off, emit } = require("sdk/event/core");
 const { Framerate } = require("devtools/server/performance/framerate");
 
 /**
  * An actor wrapper around Framerate. Uses exposed
  * methods via bridge and provides RDP definitions.
--- a/devtools/server/actors/gcli.js
+++ b/devtools/server/actors/gcli.js
@@ -2,17 +2,17 @@
  * 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 { Task } = require("resource://gre/modules/Task.jsm");
 const {
   method, Arg, Option, RetVal, Front, FrontClass, Actor, ActorClass
-} = require("devtools/server/protocol");
+} = require("devtools/shared/protocol");
 const events = require("sdk/event/core");
 const { createSystem } = require("gcli/system");
 
 /**
  * Manage remote connections that want to talk to GCLI
  */
 const GcliActor = ActorClass({
   typeName: "gcli",
--- a/devtools/server/actors/heap-snapshot-file.js
+++ b/devtools/server/actors/heap-snapshot-file.js
@@ -1,15 +1,15 @@
 /* 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 protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { method, Arg } = protocol;
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "DevToolsUtils",
                          "devtools/shared/DevToolsUtils");
 loader.lazyRequireGetter(this, "OS", "resource://gre/modules/osfile.jsm", true);
 loader.lazyRequireGetter(this, "Task", "resource://gre/modules/Task.jsm", true);
 loader.lazyRequireGetter(this, "HeapSnapshotFileUtils",
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Ci } = require("chrome");
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const events = require("sdk/event/core");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { Arg, Option, method, RetVal } = protocol;
 const { isWindowIncluded } = require("devtools/shared/layout/utils");
 const { isXUL, isNodeValid } = require("./highlighters/utils/markup");
 const { SimpleOutlineHighlighter } = require("./highlighters/simple-outline");
 
 const HIGHLIGHTER_PICKED_TIMER = 1000;
 
 /**
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -47,17 +47,17 @@
  *
  * So to be able to answer "all the children of a given node that we have
  * seen on the client side", we guarantee that every time we've seen a node,
  * we connect it up through its parents.
  */
 
 const {Cc, Ci, Cu} = require("chrome");
 const Services = require("Services");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {Arg, Option, method, RetVal, types} = protocol;
 const {LongStringActor, ShortLongString} = require("devtools/server/actors/string");
 const promise = require("promise");
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 const object = require("sdk/util/object");
 const events = require("sdk/event/core");
 const {Class} = require("sdk/core/heritage");
 const {WalkerSearch} = require("devtools/server/actors/utils/walker-search");
--- a/devtools/server/actors/layout.js
+++ b/devtools/server/actors/layout.js
@@ -21,17 +21,17 @@
  *
  * - Dedicated observers: There's only one of them for now: ReflowObserver which
  *   listens to reflow events via the docshell,
  *   These dedicated classes are used by the LayoutChangesObserver.
  */
 
 const {Ci, Cu} = require("chrome");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {method, Arg} = protocol;
 const events = require("sdk/event/core");
 const Heritage = require("sdk/core/heritage");
 const EventEmitter = require("devtools/shared/event-emitter");
 
 /**
  * The reflow actor tracks reflows and emits events about them.
  */
--- a/devtools/server/actors/memory.js
+++ b/devtools/server/actors/memory.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci, Cu, components } = require("chrome");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { method, RetVal, Arg, types } = protocol;
 const { Memory } = require("devtools/server/performance/memory");
 const { actorBridge } = require("devtools/server/actors/common");
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "StackFrameCache",
                          "devtools/server/actors/utils/stack", true);
 loader.lazyRequireGetter(this, "FileUtils",
                          "resource://gre/modules/FileUtils.jsm", true);
--- a/devtools/server/actors/memprof.js
+++ b/devtools/server/actors/memprof.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
-var protocol = require("devtools/server/protocol");
+var protocol = require("devtools/shared/protocol");
 var { method, RetVal, Arg, types } = protocol;
 const { reportException } = require("devtools/shared/DevToolsUtils");
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 
 var MemprofActor = protocol.ActorClass({
   typeName: "memprof",
 
   initialize: function(conn) {
--- a/devtools/server/actors/performance-entries.js
+++ b/devtools/server/actors/performance-entries.js
@@ -6,17 +6,17 @@
  * The performanceEntries actor emits events corresponding to performance
  * entries. It receives `performanceentry` events containing the performance
  * entry details and emits an event containing the name, type, origin, and
  * epoch of the performance entry.
  */
 
 const {
   method, Arg, Option, RetVal, Front, FrontClass, Actor, ActorClass
-} = require("devtools/server/protocol");
+} = require("devtools/shared/protocol");
 const events = require("sdk/event/core");
 
 var PerformanceEntriesActor = exports.PerformanceEntriesActor = ActorClass({
 
   typeName: "performanceEntries",
 
   listenerAdded: false,
 
--- a/devtools/server/actors/performance-recording.js
+++ b/devtools/server/actors/performance-recording.js
@@ -1,16 +1,16 @@
 /* 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 protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { custom, method, RetVal, Arg, Option, types, preEvent } = protocol;
 const { actorBridge } = require("devtools/server/actors/common");
 
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "merge", "sdk/util/object", true);
 loader.lazyRequireGetter(this, "PerformanceIO",
   "devtools/client/performance/modules/io");
 loader.lazyRequireGetter(this, "RecordingUtils",
--- a/devtools/server/actors/performance.js
+++ b/devtools/server/actors/performance.js
@@ -1,16 +1,16 @@
 /* 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 protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { Task } = require("resource://gre/modules/Task.jsm");
 const { Actor, custom, method, RetVal, Arg, Option, types, preEvent } = protocol;
 const { actorBridge } = require("devtools/server/actors/common");
 const { PerformanceRecordingActor, PerformanceRecordingFront } = require("devtools/server/actors/performance-recording");
 
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "extend", "sdk/util/object", true);
 
--- a/devtools/server/actors/preference.js
+++ b/devtools/server/actors/preference.js
@@ -1,14 +1,14 @@
 /* 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/. */
 
 const {Cc, Ci, Cu, CC} = require("chrome");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {Arg, method, RetVal} = protocol;
 const Services = require("Services");
 
 exports.register = function(handle) {
   handle.addGlobalActor(PreferenceActor, "preferenceActor");
 };
 
 exports.unregister = function(handle) {
--- a/devtools/server/actors/profiler.js
+++ b/devtools/server/actors/profiler.js
@@ -1,16 +1,16 @@
 /* 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 protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { custom, method, RetVal, Arg, Option, types } = protocol;
 const { Profiler } = require("devtools/server/performance/profiler");
 const { actorBridge } = require("devtools/server/actors/common");
 
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "extend", "sdk/util/object", true);
 
 types.addType("profiler-data", {
--- a/devtools/server/actors/promises.js
+++ b/devtools/server/actors/promises.js
@@ -1,15 +1,15 @@
 /* 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 protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { method, RetVal, Arg, types } = protocol;
 const { expectState, ActorPool } = require("devtools/server/actors/common");
 const { ObjectActor,
         createValueGrip } = require("devtools/server/actors/object");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 
 // Teach protocol.js how to deal with legacy actor types
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -478,17 +478,17 @@ RootActor.prototype = {
     /*
      * Request packets are frozen. Copy aRequest, so that
      * DebuggerServerConnection.onPacket can attach a 'from' property.
      */
     return Cu.cloneInto(aRequest, {});
   },
 
   onProtocolDescription: function () {
-    return require("devtools/server/protocol").dumpProtocolSpec();
+    return require("devtools/shared/protocol").dumpProtocolSpec();
   },
 
   /* Support for DebuggerServer.addGlobalActor. */
   _createExtraActors: createExtraActors,
   _appendExtraActors: appendExtraActors,
 
   /**
    * Remove the extra actor (added by DebuggerServer.addGlobalActor or
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -10,17 +10,17 @@ const Services = require("Services");
 const { Cc, Ci, Cu, Cr, components, ChromeWorker } = require("chrome");
 const { ActorPool, OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
 const { BreakpointActor, setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
 const { EnvironmentActor } = require("devtools/server/actors/environment");
 const { FrameActor } = require("devtools/server/actors/frame");
 const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object");
 const { SourceActor, getSourceURL } = require("devtools/server/actors/source");
 const { DebuggerServer } = require("devtools/server/main");
-const { ActorClass } = require("devtools/server/protocol");
+const { ActorClass } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, dumpn, update, fetch } = DevToolsUtils;
 const promise = require("promise");
 const PromiseDebugging = require("PromiseDebugging");
 const xpcInspector = require("xpcInspector");
 const ScriptStore = require("./utils/ScriptStore");
 const { DevToolsWorker } = require("devtools/shared/worker/worker");
 const object = require("sdk/util/object");
--- a/devtools/server/actors/settings.js
+++ b/devtools/server/actors/settings.js
@@ -1,14 +1,14 @@
 /* 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/. */
 
 const {Cc, Ci, Cu, CC} = require("chrome");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {Arg, method, RetVal} = protocol;
 const {DebuggerServer} = require("devtools/server/main");
 const promise = require("promise");
 const Services = require("Services");
 
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci } = require("chrome");
 const { BreakpointActor, setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
 const { OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
 const { createValueGrip } = require("devtools/server/actors/object");
-const { ActorClass, Arg, RetVal, method } = require("devtools/server/protocol");
+const { ActorClass, Arg, RetVal, method } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, fetch } = DevToolsUtils;
 const { joinURI } = require("devtools/shared/path");
 const promise = require("promise");
 const { defer, resolve, reject, all } = promise;
 
 loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
 loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const events = require("sdk/event/core");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {DebuggerServer} = require("devtools/server/main");
 const Services = require("Services");
 const promise = require("promise");
 const {isWindowIncluded} = require("devtools/shared/layout/utils");
 const specs = require("devtools/shared/specs/storage");
 
 loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
--- a/devtools/server/actors/string.js
+++ b/devtools/server/actors/string.js
@@ -5,17 +5,17 @@
 "use strict";
 
 var {Cu} = require("chrome");
 var {DebuggerServer} = require("devtools/server/main");
 
 var promise = require("promise");
 var {Class} = require("sdk/core/heritage");
 
-var protocol = require("devtools/server/protocol");
+var protocol = require("devtools/shared/protocol");
 var {method, Arg, Option, RetVal} = protocol;
 
 exports.LongStringActor = protocol.ActorClass({
   typeName: "longstractor",
 
   initialize: function(conn, str) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.str = str;
--- a/devtools/server/actors/styleeditor.js
+++ b/devtools/server/actors/styleeditor.js
@@ -8,17 +8,17 @@ var { components, Cc, Ci, Cu } = require
 var Services = require("Services");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 
 const promise = require("promise");
 const events = require("sdk/event/core");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {Arg, Option, method, RetVal, types} = protocol;
 const {LongStringActor, ShortLongString} = require("devtools/server/actors/string");
 const {fetch} = require("devtools/shared/DevToolsUtils");
 
 loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic);
 
 var TRANSITION_CLASS = "moz-styleeditor-transitioning";
 var TRANSITION_DURATION_MS = 500;
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const promise = require("promise");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {Arg, Option, method, RetVal, types} = protocol;
 const events = require("sdk/event/core");
 const {Class} = require("sdk/core/heritage");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {
   getDefinedGeometryProperties
 } = require("devtools/server/actors/highlighters/geometry-editor");
 
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -11,17 +11,17 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 const promise = require("promise");
 const events = require("sdk/event/core");
 const {OriginalSourceFront, MediaRuleFront, StyleSheetFront,
        StyleSheetsFront} = require("devtools/client/fronts/stylesheets");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const {Arg, Option, method, RetVal, types} = protocol;
 const {LongStringActor, ShortLongString} = require("devtools/server/actors/string");
 const {fetch} = require("devtools/shared/DevToolsUtils");
 const {listenOnce} = require("devtools/shared/async-utils");
 const {originalSourceSpec, mediaRuleSpec, styleSheetSpec,
        styleSheetsSpec} = require("devtools/shared/specs/stylesheets");
 const {SourceMapConsumer} = require("source-map");
 
--- a/devtools/server/actors/timeline.js
+++ b/devtools/server/actors/timeline.js
@@ -11,17 +11,17 @@
  * This actor exposes this tracking mechanism to the devtools protocol.
  * Most of the logic is handled in devtools/server/performance/timeline.js
  * This just wraps that module up and exposes it via RDP.
  *
  * For more documentation:
  * @see devtools/server/performance/timeline.js
  */
 
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { method, Arg, RetVal, Option } = protocol;
 const events = require("sdk/event/core");
 const { Timeline } = require("devtools/server/performance/timeline");
 const { actorBridge } = require("devtools/server/actors/common");
 
 /**
  * Type representing an array of numbers as strings, serialized fast(er).
  * http://jsperf.com/json-stringify-parse-vs-array-join-split/3
--- a/devtools/server/actors/webaudio.js
+++ b/devtools/server/actors/webaudio.js
@@ -5,17 +5,17 @@
 
 const {Cc, Ci, Cu, Cr} = require("chrome");
 
 const Services = require("Services");
 
 const events = require("sdk/event/core");
 const promise = require("promise");
 const { on: systemOn, off: systemOff } = require("sdk/system/events");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { CallWatcherActor, CallWatcherFront } = require("devtools/server/actors/call-watcher");
 const { createValueGrip } = require("devtools/server/actors/object");
 const AutomationTimeline = require("./utils/automation-timeline");
 const { on, once, off, emit } = events;
 const { types, method, Arg, Option, RetVal, preEvent } = protocol;
 const AUDIO_NODE_DEFINITION = require("devtools/server/actors/utils/audionodes.json");
 const ENABLE_AUTOMATION = false;
 const AUTOMATION_GRANULARITY = 2000;
--- a/devtools/server/actors/webgl.js
+++ b/devtools/server/actors/webgl.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {Cc, Ci, Cu, Cr} = require("chrome");
 const events = require("sdk/event/core");
 const promise = require("promise");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { ContentObserver } = require("devtools/shared/content-observer");
 const { on, once, off, emit } = events;
 const { method, Arg, Option, RetVal } = protocol;
 
 const WEBGL_CONTEXT_NAMES = ["webgl", "experimental-webgl", "moz-webgl"];
 
 // These traits are bit masks. Make sure they're powers of 2.
 const PROGRAM_DEFAULT_TRAITS = 0;
--- a/devtools/server/actors/worker.js
+++ b/devtools/server/actors/worker.js
@@ -1,14 +1,14 @@
 "use strict";
 
 var { Ci, Cu } = require("chrome");
 var { DebuggerServer } = require("devtools/server/main");
 var Services = require("Services");
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { Arg, method, RetVal } = protocol;
 
 loader.lazyRequireGetter(this, "ChromeUtils");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(
   this, "wdm",
--- a/devtools/server/docs/protocol.js.md
+++ b/devtools/server/docs/protocol.js.md
@@ -1,17 +1,17 @@
 Writing an Actor
 ================
 
 A Simple Hello World
 --------------------
 
 Here's a simple Hello World actor.  It is a global actor (not associated with a given browser tab).
 
-    let protocol = require("devtools/server/protocol");
+    let protocol = require("devtools/shared/protocol");
     let {method, Arg, Option, RetVal} = protocol;
 
     // This will be called by the framework when you call DebuggerServer.
     // registerModule(), and adds the actor as a 'helloActor' property
     // on the root actor.
     exports.register = function(handle) {
         handle.addGlobalActor(HelloActor, "helloActor");
     }
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -187,17 +187,17 @@ var DebuggerServer = {
 
     this._connections = {};
     this._nextConnID = 0;
 
     this._initialized = true;
   },
 
   get protocol() {
-    return require("devtools/server/protocol");
+    return require("devtools/shared/protocol");
   },
 
   get initialized() {
     return this._initialized;
   },
 
   /**
    * Performs cleanup tasks before shutting down the debugger server. Such tasks
--- a/devtools/server/moz.build
+++ b/devtools/server/moz.build
@@ -29,15 +29,14 @@ SOURCES += [
 FINAL_LIBRARY = 'xul'
 
 DevToolsModules(
     'child.js',
     'content-globals.js',
     'content-server.jsm',
     'main.js',
     'primitive.js',
-    'protocol.js',
     'service-worker-child.js',
     'worker.js'
 )
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
deleted file mode 100644
--- a/devtools/server/protocol.js
+++ /dev/null
@@ -1,1541 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-var { Cu, components } = require("chrome");
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-var Services = require("Services");
-var promise = require("promise");
-var {Class} = require("sdk/core/heritage");
-var {EventTarget} = require("sdk/event/target");
-var events = require("sdk/event/core");
-var object = require("sdk/util/object");
-
-exports.emit = events.emit;
-
-/**
- * Types: named marshallers/demarshallers.
- *
- * Types provide a 'write' function that takes a js representation and
- * returns a protocol representation, and a "read" function that
- * takes a protocol representation and returns a js representation.
- *
- * The read and write methods are also passed a context object that
- * represent the actor or front requesting the translation.
- *
- * Types are referred to with a typestring.  Basic types are
- * registered by name using addType, and more complex types can
- * be generated by adding detail to the type name.
- */
-
-var types = Object.create(null);
-exports.types = types;
-
-var registeredTypes = types.registeredTypes = new Map();
-var registeredLifetimes = types.registeredLifetimes = new Map();
-
-/**
- * Return the type object associated with a given typestring.
- * If passed a type object, it will be returned unchanged.
- *
- * Types can be registered with addType, or can be created on
- * the fly with typestrings.  Examples:
- *
- *   boolean
- *   threadActor
- *   threadActor#detail
- *   array:threadActor
- *   array:array:threadActor#detail
- *
- * @param [typestring|type] type
- *    Either a typestring naming a type or a type object.
- *
- * @returns a type object.
- */
-types.getType = function(type) {
-  if (!type) {
-    return types.Primitive;
-  }
-
-  if (typeof(type) !== "string") {
-    return type;
-  }
-
-  // If already registered, we're done here.
-  let reg = registeredTypes.get(type);
-  if (reg) return reg;
-
-  // New type, see if it's a collection/lifetime type:
-  let sep = type.indexOf(":");
-  if (sep >= 0) {
-    let collection = type.substring(0, sep);
-    let subtype = types.getType(type.substring(sep + 1));
-
-    if (collection === "array") {
-      return types.addArrayType(subtype);
-    } else if (collection === "nullable") {
-      return types.addNullableType(subtype);
-    }
-
-    if (registeredLifetimes.has(collection)) {
-      return types.addLifetimeType(collection, subtype);
-    }
-
-    throw Error("Unknown collection type: " + collection);
-  }
-
-  // Not a collection, might be actor detail
-  let pieces = type.split("#", 2);
-  if (pieces.length > 1) {
-    return types.addActorDetail(type, pieces[0], pieces[1]);
-  }
-
-  // Might be a lazily-loaded type
-  if (type === "longstring") {
-    require("devtools/server/actors/string");
-    return registeredTypes.get("longstring");
-  }
-
-  throw Error("Unknown type: " + type);
-}
-
-/**
- * Don't allow undefined when writing primitive types to packets.  If
- * you want to allow undefined, use a nullable type.
- */
-function identityWrite(v) {
-  if (v === undefined) {
-    throw Error("undefined passed where a value is required");
-  }
-  // This has to handle iterator->array conversion because arrays of
-  // primitive types pass through here.
-  if (v && typeof (v) === "object" && Symbol.iterator in v) {
-    return [...v];
-  }
-  return v;
-}
-
-/**
- * Add a type to the type system.
- *
- * When registering a type, you can provide `read` and `write` methods.
- *
- * The `read` method will be passed a JS object value from the JSON
- * packet and must return a native representation.  The `write` method will
- * be passed a native representation and should provide a JSONable value.
- *
- * These methods will both be passed a context.  The context is the object
- * performing or servicing the request - on the server side it will be
- * an Actor, on the client side it will be a Front.
- *
- * @param typestring name
- *    Name to register
- * @param object typeObject
- *    An object whose properties will be stored in the type, including
- *    the `read` and `write` methods.
- * @param object options
- *    Can specify `thawed` to prevent the type from being frozen.
- *
- * @returns a type object that can be used in protocol definitions.
- */
-types.addType = function(name, typeObject={}, options={}) {
-  if (registeredTypes.has(name)) {
-    throw Error("Type '" + name + "' already exists.");
-  }
-
-  let type = object.merge({
-    toString() { return "[protocol type:" + name + "]"},
-    name: name,
-    primitive: !(typeObject.read || typeObject.write),
-    read: identityWrite,
-    write: identityWrite
-  }, typeObject);
-
-  registeredTypes.set(name, type);
-
-  return type;
-};
-
-/**
- * Remove a type previously registered with the system.
- * Primarily useful for types registered by addons.
- */
-types.removeType = function(name) {
-  // This type may still be referenced by other types, make sure
-  // those references don't work.
-  let type = registeredTypes.get(name);
-
-  type.name = "DEFUNCT:" + name;
-  type.category = "defunct";
-  type.primitive = false;
-  type.read = type.write = function() { throw new Error("Using defunct type: " + name); };
-
-  registeredTypes.delete(name);
-}
-
-/**
- * Add an array type to the type system.
- *
- * getType() will call this function if provided an "array:<type>"
- * typestring.
- *
- * @param type subtype
- *    The subtype to be held by the array.
- */
-types.addArrayType = function(subtype) {
-  subtype = types.getType(subtype);
-
-  let name = "array:" + subtype.name;
-
-  // Arrays of primitive types are primitive types themselves.
-  if (subtype.primitive) {
-    return types.addType(name);
-  }
-  return types.addType(name, {
-    category: "array",
-    read: (v, ctx) => [...v].map(i => subtype.read(i, ctx)),
-    write: (v, ctx) => [...v].map(i => subtype.write(i, ctx))
-  });
-};
-
-/**
- * Add a dict type to the type system.  This allows you to serialize
- * a JS object that contains non-primitive subtypes.
- *
- * Properties of the value that aren't included in the specializations
- * will be serialized as primitive values.
- *
- * @param object specializations
- *    A dict of property names => type
- */
-types.addDictType = function(name, specializations) {
-  return types.addType(name, {
-    category: "dict",
-    specializations: specializations,
-    read: (v, ctx) => {
-      let ret = {};
-      for (let prop in v) {
-        if (prop in specializations) {
-          ret[prop] = types.getType(specializations[prop]).read(v[prop], ctx);
-        } else {
-          ret[prop] = v[prop];
-        }
-      }
-      return ret;
-    },
-
-    write: (v, ctx) => {
-      let ret = {};
-      for (let prop in v) {
-        if (prop in specializations) {
-          ret[prop] = types.getType(specializations[prop]).write(v[prop], ctx);
-        } else {
-          ret[prop] = v[prop];
-        }
-      }
-      return ret;
-    }
-  })
-}
-
-/**
- * Register an actor type with the type system.
- *
- * Types are marshalled differently when communicating server->client
- * than they are when communicating client->server.  The server needs
- * to provide useful information to the client, so uses the actor's
- * `form` method to get a json representation of the actor.  When
- * making a request from the client we only need the actor ID string.
- *
- * This function can be called before the associated actor has been
- * constructed, but the read and write methods won't work until
- * the associated addActorImpl or addActorFront methods have been
- * called during actor/front construction.
- *
- * @param string name
- *    The typestring to register.
- */
-types.addActorType = function(name) {
-  let type = types.addType(name, {
-    _actor: true,
-    category: "actor",
-    read: (v, ctx, detail) => {
-      // If we're reading a request on the server side, just
-      // find the actor registered with this actorID.
-      if (ctx instanceof Actor) {
-        return ctx.conn.getActor(v);
-      }
-
-      // Reading a response on the client side, check for an
-      // existing front on the connection, and create the front
-      // if it isn't found.
-      let actorID = typeof(v) === "string" ? v : v.actor;
-      let front = ctx.conn.getActor(actorID);
-      if (!front) {
-        front = new type.frontClass(ctx.conn);
-        front.actorID = actorID;
-        ctx.marshallPool().manage(front);
-      }
-
-      v = type.formType(detail).read(v, front, detail);
-      front.form(v, detail, ctx);
-
-      return front;
-    },
-    write: (v, ctx, detail) => {
-      // If returning a response from the server side, make sure
-      // the actor is added to a parent object and return its form.
-      if (v instanceof Actor) {
-        if (!v.actorID) {
-          ctx.marshallPool().manage(v);
-        }
-        return type.formType(detail).write(v.form(detail), ctx, detail);
-      }
-
-      // Writing a request from the client side, just send the actor id.
-      return v.actorID;
-    },
-    formType: (detail) => {
-      if (!("formType" in type.actorSpec)) {
-        return types.Primitive;
-      }
-
-      let formAttr = "formType";
-      if (detail) {
-        formAttr += "#" + detail;
-      }
-
-      if (!(formAttr in type.actorSpec)) {
-        throw new Error("No type defined for " + formAttr);
-      }
-
-      return type.actorSpec[formAttr];
-    }
-  });
-  return type;
-}
-
-types.addNullableType = function(subtype) {
-  subtype = types.getType(subtype);
-  return types.addType("nullable:" + subtype.name, {
-    category: "nullable",
-    read: (value, ctx) => {
-      if (value == null) {
-        return value;
-      }
-      return subtype.read(value, ctx);
-    },
-    write: (value, ctx) => {
-      if (value == null) {
-        return value;
-      }
-      return subtype.write(value, ctx);
-    }
-  });
-}
-
-/**
- * Register an actor detail type.  This is just like an actor type, but
- * will pass a detail hint to the actor's form method during serialization/
- * deserialization.
- *
- * This is called by getType() when passed an 'actorType#detail' string.
- *
- * @param string name
- *   The typestring to register this type as.
- * @param type actorType
- *   The actor type you'll be detailing.
- * @param string detail
- *   The detail to pass.
- */
-types.addActorDetail = function(name, actorType, detail) {
-  actorType = types.getType(actorType);
-  if (!actorType._actor) {
-    throw Error("Details only apply to actor types, tried to add detail '" + detail + "'' to " + actorType.name + "\n");
-  }
-  return types.addType(name, {
-    _actor: true,
-    category: "detail",
-    read: (v, ctx) => actorType.read(v, ctx, detail),
-    write: (v, ctx) => actorType.write(v, ctx, detail)
-  });
-}
-
-/**
- * Register an actor lifetime.  This lets the type system find a parent
- * actor that differs from the actor fulfilling the request.
- *
- * @param string name
- *    The lifetime name to use in typestrings.
- * @param string prop
- *    The property of the actor that holds the parent that should be used.
- */
-types.addLifetime = function(name, prop) {
-  if (registeredLifetimes.has(name)) {
-    throw Error("Lifetime '" + name + "' already registered.");
-  }
-  registeredLifetimes.set(name, prop);
-}
-
-/**
- * Remove a previously-registered lifetime.  Useful for lifetimes registered
- * in addons.
- */
-types.removeLifetime = function(name) {
-  registeredLifetimes.delete(name);
-}
-
-/**
- * Register a lifetime type.  This creates an actor type tied to the given
- * lifetime.
- *
- * This is called by getType() when passed a '<lifetimeType>:<actorType>'
- * typestring.
- *
- * @param string lifetime
- *    A lifetime string previously regisered with addLifetime()
- * @param type subtype
- *    An actor type
- */
-types.addLifetimeType = function(lifetime, subtype) {
-  subtype = types.getType(subtype);
-  if (!subtype._actor) {
-    throw Error("Lifetimes only apply to actor types, tried to apply lifetime '" + lifetime + "'' to " + subtype.name);
-  }
-  let prop = registeredLifetimes.get(lifetime);
-  return types.addType(lifetime + ":" + subtype.name, {
-    category: "lifetime",
-    read: (value, ctx) => subtype.read(value, ctx[prop]),
-    write: (value, ctx) => subtype.write(value, ctx[prop])
-  })
-}
-
-// Add a few named primitive types.
-types.Primitive = types.addType("primitive");
-types.String = types.addType("string");
-types.Number = types.addType("number");
-types.Boolean = types.addType("boolean");
-types.JSON = types.addType("json");
-
-/**
- * Request/Response templates and generation
- *
- * Request packets are specified as json templates with
- * Arg and Option placeholders where arguments should be
- * placed.
- *
- * Reponse packets are also specified as json templates,
- * with a RetVal placeholder where the return value should be
- * placed.
- */
-
-/**
- * Placeholder for simple arguments.
- *
- * @param number index
- *    The argument index to place at this position.
- * @param type type
- *    The argument should be marshalled as this type.
- * @constructor
- */
-var Arg = Class({
-  initialize: function(index, type) {
-    this.index = index;
-    this.type = types.getType(type);
-  },
-
-  write: function(arg, ctx) {
-    return this.type.write(arg, ctx);
-  },
-
-  read: function(v, ctx, outArgs) {
-    outArgs[this.index] = this.type.read(v, ctx);
-  },
-
-  describe: function() {
-    return {
-      _arg: this.index,
-      type: this.type.name,
-    }
-  }
-});
-exports.Arg = Arg;
-
-/**
- * Placeholder for an options argument value that should be hoisted
- * into the packet.
- *
- * If provided in a method specification:
- *
- *   { optionArg: Option(1)}
- *
- * Then arguments[1].optionArg will be placed in the packet in this
- * value's place.
- *
- * @param number index
- *    The argument index of the options value.
- * @param type type
- *    The argument should be marshalled as this type.
- * @constructor
- */
-var Option = Class({
-  extends: Arg,
-  initialize: function(index, type) {
-    Arg.prototype.initialize.call(this, index, type)
-  },
-
-  write: function(arg, ctx, name) {
-    // Ignore if arg is undefined or null; allow other falsy values
-    if (arg == undefined || arg[name] == undefined) {
-      return undefined;
-    }
-    let v = arg[name];
-    return this.type.write(v, ctx);
-  },
-  read: function(v, ctx, outArgs, name) {
-    if (outArgs[this.index] === undefined) {
-      outArgs[this.index] = {};
-    }
-    if (v === undefined) {
-      return;
-    }
-    outArgs[this.index][name] = this.type.read(v, ctx);
-  },
-
-  describe: function() {
-    return {
-      _option: this.index,
-      type: this.type.name,
-    }
-  }
-});
-
-exports.Option = Option;
-
-/**
- * Placeholder for return values in a response template.
- *
- * @param type type
- *    The return value should be marshalled as this type.
- */
-var RetVal = Class({
-  initialize: function(type) {
-    this.type = types.getType(type);
-  },
-
-  write: function(v, ctx) {
-    return this.type.write(v, ctx);
-  },
-
-  read: function(v, ctx) {
-    return this.type.read(v, ctx);
-  },
-
-  describe: function() {
-    return {
-      _retval: this.type.name
-    }
-  }
-});
-
-exports.RetVal = RetVal;
-
-/* Template handling functions */
-
-/**
- * Get the value at a given path, or undefined if not found.
- */
-function getPath(obj, path) {
-  for (let name of path) {
-    if (!(name in obj)) {
-      return undefined;
-    }
-    obj = obj[name];
-  }
-  return obj;
-}
-
-/**
- * Find Placeholders in the template and save them along with their
- * paths.
- */
-function findPlaceholders(template, constructor, path=[], placeholders=[]) {
-  if (!template || typeof(template) != "object") {
-    return placeholders;
-  }
-
-  if (template instanceof constructor) {
-    placeholders.push({ placeholder: template, path: [...path] });
-    return placeholders;
-  }
-
-  for (let name in template) {
-    path.push(name);
-    findPlaceholders(template[name], constructor, path, placeholders);
-    path.pop();
-  }
-
-  return placeholders;
-}
-
-
-function describeTemplate(template) {
-  return JSON.parse(JSON.stringify(template, (key, value) => {
-    if (value.describe) {
-      return value.describe();
-    }
-    return value;
-  }));
-}
-
-/**
- * Manages a request template.
- *
- * @param object template
- *    The request template.
- * @construcor
- */
-var Request = Class({
-  initialize: function(template={}) {
-    this.type = template.type;
-    this.template = template;
-    this.args = findPlaceholders(template, Arg);
-  },
-
-  /**
-   * Write a request.
-   *
-   * @param array fnArgs
-   *    The function arguments to place in the request.
-   * @param object ctx
-   *    The object making the request.
-   * @returns a request packet.
-   */
-  write: function(fnArgs, ctx) {
-    let str = JSON.stringify(this.template, (key, value) => {
-      if (value instanceof Arg) {
-        return value.write(value.index in fnArgs ? fnArgs[value.index] : undefined,
-                           ctx, key);
-      }
-      return value;
-    });
-    return JSON.parse(str);
-  },
-
-  /**
-   * Read a request.
-   *
-   * @param object packet
-   *    The request packet.
-   * @param object ctx
-   *    The object making the request.
-   * @returns an arguments array
-   */
-  read: function(packet, ctx) {
-    let fnArgs = [];
-    for (let templateArg of this.args) {
-      let arg = templateArg.placeholder;
-      let path = templateArg.path;
-      let name = path[path.length - 1];
-      arg.read(getPath(packet, path), ctx, fnArgs, name);
-    }
-    return fnArgs;
-  },
-
-  describe: function() { return describeTemplate(this.template); }
-});
-
-/**
- * Manages a response template.
- *
- * @param object template
- *    The response template.
- * @construcor
- */
-var Response = Class({
-  initialize: function(template={}) {
-    this.template = template;
-    let placeholders = findPlaceholders(template, RetVal);
-    if (placeholders.length > 1) {
-      throw Error("More than one RetVal specified in response");
-    }
-    let placeholder = placeholders.shift();
-    if (placeholder) {
-      this.retVal = placeholder.placeholder;
-      this.path = placeholder.path;
-    }
-  },
-
-  /**
-   * Write a response for the given return value.
-   *
-   * @param val ret
-   *    The return value.
-   * @param object ctx
-   *    The object writing the response.
-   */
-  write: function(ret, ctx) {
-    return JSON.parse(JSON.stringify(this.template, function(key, value) {
-      if (value instanceof RetVal) {
-        return value.write(ret, ctx);
-      }
-      return value;
-    }));
-  },
-
-  /**
-   * Read a return value from the given response.
-   *
-   * @param object packet
-   *    The response packet.
-   * @param object ctx
-   *    The object reading the response.
-   */
-  read: function(packet, ctx) {
-    if (!this.retVal) {
-      return undefined;
-    }
-    let v = getPath(packet, this.path);
-    return this.retVal.read(v, ctx);
-  },
-
-  describe: function() { return describeTemplate(this.template); }
-});
-
-/**
- * Actor and Front implementations
- */
-
-/**
- * A protocol object that can manage the lifetime of other protocol
- * objects.
- */
-var Pool = Class({
-  extends: EventTarget,
-
-  /**
-   * Pools are used on both sides of the connection to help coordinate
-   * lifetimes.
-   *
-   * @param optional conn
-   *   Either a DebuggerServerConnection or a DebuggerClient.  Must have
-   *   addActorPool, removeActorPool, and poolFor.
-   *   conn can be null if the subclass provides a conn property.
-   * @constructor
-   */
-  initialize: function(conn) {
-    if (conn) {
-      this.conn = conn;
-    }
-  },
-
-  /**
-   * Return the parent pool for this client.
-   */
-  parent: function() { return this.conn.poolFor(this.actorID) },
-
-  /**
-   * Override this if you want actors returned by this actor
-   * to belong to a different actor by default.
-   */
-  marshallPool: function() { return this; },
-
-  /**
-   * Pool is the base class for all actors, even leaf nodes.
-   * If the child map is actually referenced, go ahead and create
-   * the stuff needed by the pool.
-   */
-  __poolMap: null,
-  get _poolMap() {
-    if (this.__poolMap) return this.__poolMap;
-    this.__poolMap = new Map();
-    this.conn.addActorPool(this);
-    return this.__poolMap;
-  },
-
-  /**
-   * Add an actor as a child of this pool.
-   */
-  manage: function(actor) {
-    if (!actor.actorID) {
-      actor.actorID = this.conn.allocID(actor.actorPrefix || actor.typeName);
-    }
-
-    this._poolMap.set(actor.actorID, actor);
-    return actor;
-  },
-
-  /**
-   * Remove an actor as a child of this pool.
-   */
-  unmanage: function(actor) {
-    this.__poolMap && this.__poolMap.delete(actor.actorID);
-  },
-
-  // 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;
-  },
-
-  // 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;
-  },
-
-  // True if this pool has no children.
-  isEmpty: function() {
-    return !this.__poolMap || this._poolMap.size == 0;
-  },
-
-  /**
-   * Destroy this item, removing it from a parent if it has one,
-   * and destroying all children if necessary.
-   */
-  destroy: function() {
-    let parent = this.parent();
-    if (parent) {
-      parent.unmanage(this);
-    }
-    if (!this.__poolMap) {
-      return;
-    }
-    for (let actor of this.__poolMap.values()) {
-      // Self-owned actors are ok, but don't need destroying twice.
-      if (actor === this) {
-        continue;
-      }
-      let destroy = actor.destroy;
-      if (destroy) {
-        // Disconnect destroy while we're destroying in case of (misbehaving)
-        // circular ownership.
-        actor.destroy = null;
-        destroy.call(actor);
-        actor.destroy = destroy;
-      }
-    };
-    this.conn.removeActorPool(this, true);
-    this.__poolMap.clear();
-    this.__poolMap = null;
-  },
-
-  /**
-   * For getting along with the debugger server pools, should be removable
-   * eventually.
-   */
-  cleanup: function() {
-    this.destroy();
-  }
-});
-exports.Pool = Pool;
-
-/**
- * An actor in the actor tree.
- */
-var Actor = Class({
-  extends: Pool,
-
-  // Will contain the actor's ID
-  actorID: null,
-
-  /**
-   * Initialize an actor.
-   *
-   * @param optional conn
-   *   Either a DebuggerServerConnection or a DebuggerClient.  Must have
-   *   addActorPool, removeActorPool, and poolFor.
-   *   conn can be null if the subclass provides a conn property.
-   * @constructor
-   */
-  initialize: function(conn) {
-    Pool.prototype.initialize.call(this, conn);
-
-    // Forward events to the connection.
-    if (this._actorSpec && this._actorSpec.events) {
-      for (let key of this._actorSpec.events.keys()) {
-        let name = key;
-        let sendEvent = this._sendEvent.bind(this, name)
-        this.on(name, (...args) => {
-          sendEvent.apply(null, args);
-        });
-      }
-    }
-  },
-
-  toString: function() { return "[Actor " + this.typeName + "/" + this.actorID + "]" },
-
-  _sendEvent: function(name, ...args) {
-    if (!this._actorSpec.events.has(name)) {
-      // It's ok to emit events that don't go over the wire.
-      return;
-    }
-    let request = this._actorSpec.events.get(name);
-    let packet;
-    try {
-      packet = request.write(args, this);
-    } catch(ex) {
-      console.error("Error sending event: " + name);
-      throw ex;
-    }
-    packet.from = packet.from || this.actorID;
-    this.conn.send(packet);
-  },
-
-  destroy: function() {
-    Pool.prototype.destroy.call(this);
-    this.actorID = null;
-  },
-
-  /**
-   * Override this method in subclasses to serialize the actor.
-   * @param [optional] string hint
-   *   Optional string to customize the form.
-   * @returns A jsonable object.
-   */
-  form: function(hint) {
-    return { actor: this.actorID }
-  },
-
-  writeError: function(error) {
-    console.error(error);
-    if (error.stack) {
-      dump(error.stack);
-    }
-    this.conn.send({
-      from: this.actorID,
-      error: error.error || "unknownError",
-      message: error.message
-    });
-  },
-
-  _queueResponse: function(create) {
-    let pending = this._pendingResponse || promise.resolve(null);
-    let response = create(pending);
-    this._pendingResponse = response;
-  }
-});
-exports.Actor = Actor;
-
-/**
- * Tags a prtotype method as an actor method implementation.
- *
- * @param function fn
- *    The implementation function, will be returned.
- * @param spec
- *    The method specification, with the following (optional) properties:
- *      request (object): a request template.
- *      response (object): a response template.
- *      oneway (bool): 'true' if no response should be sent.
- *      telemetry (string): Telemetry probe ID for measuring completion time.
- */
-exports.method = function(fn, spec={}) {
-  fn._methodSpec = Object.freeze(spec);
-  if (spec.request) Object.freeze(spec.request);
-  if (spec.response) Object.freeze(spec.response);
-  return fn;
-}
-
-/**
- * Generates an actor specification from an actor description.
- */
-var generateActorSpec = function(actorDesc) {
-  let actorSpec = {
-    typeName: actorDesc.typeName,
-    methods: []
-  };
-
-  // Find method and form specifications attached to properties.
-  for (let name of Object.getOwnPropertyNames(actorDesc)) {
-    let desc = Object.getOwnPropertyDescriptor(actorDesc, name);
-    if (!desc.value) {
-      continue;
-    }
-
-    if (name.startsWith("formType")) {
-      if (typeof(desc.value) === "string") {
-        actorSpec[name] = types.getType(desc.value);
-      } else if (desc.value.name && registeredTypes.has(desc.value.name)) {
-        actorSpec[name] = desc.value;
-      } else {
-        // Shorthand for a newly-registered DictType.
-        actorSpec[name] = types.addDictType(actorDesc.typeName + "__" + name, desc.value);
-      }
-    }
-
-    if (desc.value._methodSpec) {
-      let methodSpec = desc.value._methodSpec;
-      let spec = {};
-      spec.name = methodSpec.name || name;
-      spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined));
-      spec.response = Response(methodSpec.response || undefined);
-      spec.telemetry = methodSpec.telemetry;
-      spec.release = methodSpec.release;
-      spec.oneway = methodSpec.oneway;
-
-      actorSpec.methods.push(spec);
-    }
-  }
-
-  // Find additional method specifications
-  if (actorDesc.methods) {
-    for (let name in actorDesc.methods) {
-      let methodSpec = actorDesc.methods[name];
-      let spec = {};
-
-      spec.name = methodSpec.name || name;
-      spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined));
-      spec.response = Response(methodSpec.response || undefined);
-      spec.telemetry = methodSpec.telemetry;
-      spec.release = methodSpec.release;
-      spec.oneway = methodSpec.oneway;
-
-      actorSpec.methods.push(spec);
-    }
-  }
-
-  // Find event specifications
-  if (actorDesc.events) {
-    actorSpec.events = new Map();
-    for (let name in actorDesc.events) {
-      let eventRequest = actorDesc.events[name];
-      Object.freeze(eventRequest);
-      actorSpec.events.set(name, Request(object.merge({type: name}, eventRequest)));
-    }
-  }
-
-  if (!registeredTypes.has(actorSpec.typeName)) {
-    types.addActorType(actorSpec.typeName);
-  }
-  registeredTypes.get(actorSpec.typeName).actorSpec = actorSpec;
-
-  return actorSpec;
-};
-exports.generateActorSpec = generateActorSpec;
-
-/**
- * Generates request handlers as described by the given actor specification on
- * the given actor prototype. Returns the actor prototype.
- */
-var generateRequestHandlers = function(actorSpec, actorProto) {
-  if (actorProto._actorSpec) {
-    throw new Error("actorProto called twice on the same actor prototype!");
-  }
-
-  actorProto.typeName = actorSpec.typeName;
-
-  // Generate request handlers for each method definition
-  actorProto.requestTypes = Object.create(null);
-  actorSpec.methods.forEach(spec => {
-    let handler = function(packet, conn) {
-      try {
-        let args;
-        try {
-          args = spec.request.read(packet, this);
-        } catch(ex) {
-          console.error("Error reading request: " + packet.type);
-          throw ex;
-        }
-
-        let ret = this[spec.name].apply(this, args);
-
-        let sendReturn = (ret) => {
-          if (spec.oneway) {
-            // No need to send a response.
-            return;
-          }
-
-          let response;
-          try {
-            response = spec.response.write(ret, this);
-          } catch(ex) {
-            console.error("Error writing response to: " + spec.name);
-            throw ex;
-          }
-          response.from = this.actorID;
-          // If spec.release has been specified, destroy the object.
-          if (spec.release) {
-            try {
-              this.destroy();
-            } catch(e) {
-              this.writeError(e);
-              return;
-            }
-          }
-
-          conn.send(response);
-        };
-
-        this._queueResponse(p => {
-          return p
-            .then(() => ret)
-            .then(sendReturn)
-            .then(null, this.writeError.bind(this));
-        })
-      } catch(e) {
-        this._queueResponse(p => {
-          return p.then(() => this.writeError(e));
-        });
-      }
-    };
-
-    actorProto.requestTypes[spec.request.type] = handler;
-  });
-
-  actorProto._actorSpec = actorSpec;
-
-  return actorProto;
-}
-
-/**
- * Create an actor class for the given actor prototype.
- *
- * @param object actorProto
- *    The actor prototype.  Must have a 'typeName' property,
- *    should have method definitions, can have event definitions.
- */
-exports.ActorClass = function (actorProto) {
-  return ActorClassWithSpec(generateActorSpec(actorProto), actorProto);
-};
-
-/**
- * Create an actor class for the given actor specification and prototype.
- *
- * @param object actorSpec
- *    The actor specification. Must have a 'typeName' property.
- * @param object actorProto
- *    The actor prototype. Should have method definitions, can have event
- *    definitions.
- */
-var ActorClassWithSpec = function(actorSpec, actorProto) {
-  if (!actorSpec.typeName) {
-    throw Error("Actor specification must have a typeName member.");
-  }
-
-  actorProto.extends = Actor;
-  let cls = Class(generateRequestHandlers(actorSpec, actorProto));
-
-  return cls;
-};
-exports.ActorClassWithSpec = ActorClassWithSpec;
-
-/**
- * Base class for client-side actor fronts.
- */
-var Front = Class({
-  extends: Pool,
-
-  actorID: null,
-
-  /**
-   * The base class for client-side actor fronts.
-   *
-   * @param optional conn
-   *   Either a DebuggerServerConnection or a DebuggerClient.  Must have
-   *   addActorPool, removeActorPool, and poolFor.
-   *   conn can be null if the subclass provides a conn property.
-   * @param optional form
-   *   The json form provided by the server.
-   * @constructor
-   */
-  initialize: function(conn=null, form=null, detail=null, context=null) {
-    Pool.prototype.initialize.call(this, conn);
-    this._requests = [];
-
-    // protocol.js no longer uses this data in the constructor, only external
-    // uses do.  External usage of manually-constructed fronts will be
-    // drastically reduced if we convert the root and tab actors to
-    // protocol.js, in which case this can probably go away.
-    if (form) {
-      this.actorID = form.actor;
-      form = types.getType(this.typeName).formType(detail).read(form, this, detail);
-      this.form(form, detail, context);
-    }
-  },
-
-  destroy: function() {
-    // Reject all outstanding requests, they won't make sense after
-    // the front is destroyed.
-    while (this._requests && this._requests.length > 0) {
-      let { deferred, to, type, stack } = this._requests.shift();
-      let msg = "Connection closed, pending request to " + to +
-                ", type " + type + " failed" +
-                "\n\nRequest stack:\n" + stack.formattedStack;
-      deferred.reject(new Error(msg));
-    }
-    Pool.prototype.destroy.call(this);
-    this.actorID = null;
-  },
-
-  manage: function(front) {
-    if (!front.actorID) {
-      throw new Error("Can't manage front without an actor ID.\n" +
-                      "Ensure server supports " + front.typeName + ".");
-    }
-    return Pool.prototype.manage.call(this, front);
-  },
-
-  /**
-   * @returns a promise that will resolve to the actorID this front
-   * represents.
-   */
-  actor: function() { return promise.resolve(this.actorID) },
-
-  toString: function() { return "[Front for " + this.typeName + "/" + this.actorID + "]" },
-
-  /**
-   * Update the actor from its representation.
-   * Subclasses should override this.
-   */
-  form: function(form) {},
-
-  /**
-   * Send a packet on the connection.
-   */
-  send: function(packet) {
-    if (packet.to) {
-      this.conn._transport.send(packet);
-    } else {
-      this.actor().then(actorID => {
-        packet.to = actorID;
-        this.conn._transport.send(packet);
-      }).then(null, e => DevToolsUtils.reportException("Front.prototype.send", e));
-    }
-  },
-
-  /**
-   * Send a two-way request on the connection.
-   */
-  request: function(packet) {
-    let deferred = promise.defer();
-    // Save packet basics for debugging
-    let { to, type } = packet;
-    this._requests.push({
-      deferred,
-      to: to || this.actorID,
-      type,
-      stack: components.stack,
-    });
-    this.send(packet);
-    return deferred.promise;
-  },
-
-  /**
-   * Handler for incoming packets from the client's actor.
-   */
-  onPacket: function(packet) {
-    // Pick off event packets
-    let type = packet.type || undefined;
-    if (this._clientSpec.events && this._clientSpec.events.has(type)) {
-      let event = this._clientSpec.events.get(packet.type);
-      let args;
-      try {
-        args = event.request.read(packet, this);
-      } catch(ex) {
-        console.error("Error reading event: " + packet.type);
-        console.exception(ex);
-        throw ex;
-      }
-      if (event.pre) {
-        let results = event.pre.map(pre => pre.apply(this, args));
-
-        // Check to see if any of the preEvents returned a promise -- if so,
-        // wait for their resolution before emitting. Otherwise, emit synchronously.
-        if (results.some(result => result && typeof result.then === "function")) {
-          promise.all(results).then(() => events.emit.apply(null, [this, event.name].concat(args)));
-          return;
-        }
-      }
-
-      events.emit.apply(null, [this, event.name].concat(args));
-      return;
-    }
-
-    // Remaining packets must be responses.
-    if (this._requests.length === 0) {
-      let msg = "Unexpected packet " + this.actorID + ", " + JSON.stringify(packet);
-      let err = Error(msg);
-      console.error(err);
-      throw err;
-    }
-
-    let { deferred, stack } = this._requests.shift();
-    Cu.callFunctionWithAsyncStack(() => {
-      if (packet.error) {
-        // "Protocol error" is here to avoid TBPL heuristics. See also
-        // https://mxr.mozilla.org/webtools-central/source/tbpl/php/inc/GeneralErrorFilter.php
-        let message;
-        if (packet.error && packet.message) {
-          message = "Protocol error (" + packet.error + "): " + packet.message;
-        } else {
-          message = packet.error;
-        }
-        deferred.reject(message);
-      } else {
-        deferred.resolve(packet);
-      }
-    }, stack, "DevTools RDP");
-  }
-});
-exports.Front = Front;
-
-/**
- * A method tagged with preEvent will be called after recieving a packet
- * for that event, and before the front emits the event.
- */
-exports.preEvent = function(eventName, fn) {
-  fn._preEvent = eventName;
-  return fn;
-}
-
-/**
- * Mark a method as a custom front implementation, replacing the generated
- * front method.
- *
- * @param function fn
- *    The front implementation, will be returned.
- * @param object options
- *    Options object:
- *      impl (string): If provided, the generated front method will be
- *        stored as this property on the prototype.
- */
-exports.custom = function(fn, options={}) {
-  fn._customFront = options;
-  return fn;
-}
-
-function prototypeOf(obj) {
-  return typeof(obj) === "function" ? obj.prototype : obj;
-}
-
-/**
- * Generates request methods as described by the given actor specification on
- * the given front prototype. Returns the front prototype.
- */
-var generateRequestMethods = function(actorSpec, frontProto) {
-  if (frontProto._actorSpec) {
-    throw new Error("frontProto called twice on the same front prototype!");
-  }
-
-  frontProto.typeName = actorSpec.typeName;
-
-  // Generate request methods.
-  let methods = actorSpec.methods;
-  methods.forEach(spec => {
-    let name = spec.name;
-
-    // If there's already a property by this name in the front, it must
-    // be a custom front method.
-    if (name in frontProto) {
-      let custom = frontProto[spec.name]._customFront;
-      if (custom === undefined) {
-        throw Error("Existing method for " + spec.name + " not marked customFront while processing " + actorType.typeName + ".");
-      }
-      // If the user doesn't need the impl don't generate it.
-      if (!custom.impl) {
-        return;
-      }
-      name = custom.impl;
-    }
-
-    frontProto[name] = function(...args) {
-      let histogram, startTime;
-      if (spec.telemetry) {
-        if (spec.oneway) {
-          // That just doesn't make sense.
-          throw Error("Telemetry specified for a oneway request");
-        }
-        let transportType = this.conn.localTransport
-          ? "LOCAL_"
-          : "REMOTE_";
-        let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
-          + transportType + spec.telemetry + "_MS";
-        try {
-          histogram = Services.telemetry.getHistogramById(histogramId);
-          startTime = new Date();
-        } catch(ex) {
-          // XXX: Is this expected in xpcshell tests?
-          console.error(ex);
-          spec.telemetry = false;
-        }
-      }
-
-      let packet;
-      try {
-        packet = spec.request.write(args, this);
-      } catch(ex) {
-        console.error("Error writing request: " + name);
-        throw ex;
-      }
-      if (spec.oneway) {
-        // Fire-and-forget oneway packets.
-        this.send(packet);
-        return undefined;
-      }
-
-      return this.request(packet).then(response => {
-        let ret;
-        try {
-          ret = spec.response.read(response, this);
-        } catch(ex) {
-          console.error("Error reading response to: " + name);
-          throw ex;
-        }
-
-        if (histogram) {
-          histogram.add(+new Date - startTime);
-        }
-
-        return ret;
-      });
-    }
-
-    // Release methods should call the destroy function on return.
-    if (spec.release) {
-      let fn = frontProto[name];
-      frontProto[name] = function(...args) {
-        return fn.apply(this, args).then(result => {
-          this.destroy();
-          return result;
-        })
-      }
-    }
-  });
-
-
-  // Process event specifications
-  frontProto._clientSpec = {};
-
-  let events = actorSpec.events;
-  if (events) {
-    // This actor has events, scan the prototype for preEvent handlers...
-    let preHandlers = new Map();
-    for (let name of Object.getOwnPropertyNames(frontProto)) {
-      let desc = Object.getOwnPropertyDescriptor(frontProto, name);
-      if (!desc.value) {
-        continue;
-      }
-      if (desc.value._preEvent) {
-        let preEvent = desc.value._preEvent;
-        if (!events.has(preEvent)) {
-          throw Error("preEvent for event that doesn't exist: " + preEvent);
-        }
-        let handlers = preHandlers.get(preEvent);
-        if (!handlers) {
-          handlers = [];
-          preHandlers.set(preEvent, handlers);
-        }
-        handlers.push(desc.value);
-      }
-    }
-
-    frontProto._clientSpec.events = new Map();
-
-    for (let [name, request] of events) {
-      frontProto._clientSpec.events.set(request.type, {
-        name: name,
-        request: request,
-        pre: preHandlers.get(name)
-      });
-    }
-  }
-
-  frontProto._actorSpec = actorSpec;
-
-  return frontProto;
-}
-
-/**
- * Create a front class for the given actor class and front prototype.
- *
- * @param ActorClass actorType
- *    The actor class you're creating a front for.
- * @param object frontProto
- *    The front prototype.  Must have a 'typeName' property,
- *    should have method definitions, can have event definitions.
- */
-exports.FrontClass = function(actorType, frontProto) {
-  return FrontClassWithSpec(prototypeOf(actorType)._actorSpec, frontProto);
-}
-
-/**
- * Create a front class for the given actor specification and front prototype.
- *
- * @param object actorSpec
- *    The actor specification you're creating a front for.
- * @param object proto
- *    The object prototype.  Must have a 'typeName' property,
- *    should have method definitions, can have event definitions.
- */
-var FrontClassWithSpec = function(actorSpec, frontProto) {
-  frontProto.extends = Front;
-  let cls = Class(generateRequestMethods(actorSpec, frontProto));
-
-  if (!registeredTypes.has(actorSpec.typeName)) {
-    types.addActorType(actorSpec.typeName);
-  }
-  registeredTypes.get(actorSpec.typeName).frontClass = cls;
-
-  return cls;
-}
-exports.FrontClassWithSpec = FrontClassWithSpec;
-
-exports.dumpActorSpec = function(type) {
-  let actorSpec = type.actorSpec;
-  let ret = {
-    category: "actor",
-    typeName: type.name,
-    methods: [],
-    events: {}
-  };
-
-  for (let method of actorSpec.methods) {
-    ret.methods.push({
-      name: method.name,
-      release: method.release || undefined,
-      oneway: method.oneway || undefined,
-      request: method.request.describe(),
-      response: method.response.describe()
-    });
-  }
-
-  if (actorSpec.events) {
-    for (let [name, request] of actorSpec.events) {
-      ret.events[name] = request.describe();
-    }
-  }
-
-
-  JSON.stringify(ret);
-
-  return ret;
-}
-
-exports.dumpProtocolSpec = function() {
-  let ret = {
-    types: {},
-  };
-
-  for (let [name, type] of registeredTypes) {
-    // Force lazy instantiation if needed.
-    type = types.getType(name);
-    let category = type.category || undefined;
-    if (category === "dict") {
-      ret.types[name] = {
-        category: "dict",
-        typeName: name,
-        specializations: type.specializations
-      }
-    } else if (category === "actor") {
-      ret.types[name] = exports.dumpActorSpec(type);
-    }
-  }
-
-  return ret;
-}
--- a/devtools/server/tests/mochitest/hello-actor.js
+++ b/devtools/server/tests/mochitest/hello-actor.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 
 var HelloActor = protocol.ActorClass({
   typeName: "helloActor",
 
   initialize: function() {
     protocol.Actor.prototype.initialize.apply(this, arguments);
     this.counter = 0;
   },
--- a/devtools/server/tests/unit/hello-actor.js
+++ b/devtools/server/tests/unit/hello-actor.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 
 var HelloActor = protocol.ActorClass({
   typeName: "helloActor",
 
   hello: protocol.method(function () {
     return;
   }, {
     request: {},
--- a/devtools/server/tests/unit/registertestactors-03.js
+++ b/devtools/server/tests/unit/registertestactors-03.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var {method, RetVal, Actor, ActorClass, Front, FrontClass} =
-  require("devtools/server/protocol");
+  require("devtools/shared/protocol");
 var Services = require("Services");
 
 exports.LazyActor = ActorClass({
   typeName: "lazy",
 
   initialize: function(conn, id) {
     Actor.prototype.initialize.call(this, conn);
 
--- a/devtools/server/tests/unit/test_protocol_abort.js
+++ b/devtools/server/tests/unit/test_protocol_abort.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Outstanding requests should be rejected when the connection aborts
  * unexpectedly.
  */
 
-var protocol = require("devtools/server/protocol");
+var protocol = require("devtools/shared/protocol");
 var {method, Arg, Option, RetVal} = protocol;
 var events = require("sdk/event/core");
 
 function simpleHello() {
   return {
     from: "root",
     applicationType: "xpcshell-tests",
     traits: [],
--- a/devtools/server/tests/unit/test_protocol_async.js
+++ b/devtools/server/tests/unit/test_protocol_async.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Make sure we get replies in the same order that we sent their
  * requests even when earlier requests take several event ticks to
  * complete.
  */
 
-var protocol = require("devtools/server/protocol");
+var protocol = require("devtools/shared/protocol");
 var {method, Arg, Option, RetVal} = protocol;
 var events = require("sdk/event/core");
 
 function simpleHello() {
   return {
     from: "root",
     applicationType: "xpcshell-tests",
     traits: [],
--- a/devtools/server/tests/unit/test_protocol_children.js
+++ b/devtools/server/tests/unit/test_protocol_children.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test simple requests using the protocol helpers.
  */
-var protocol = require("devtools/server/protocol");
+var protocol = require("devtools/shared/protocol");
 var {method, preEvent, types, Arg, Option, RetVal} = protocol;
 
 var events = require("sdk/event/core");
 
 function simpleHello() {
   return {
     from: "root",
     applicationType: "xpcshell-tests",
--- a/devtools/server/tests/unit/test_protocol_formtype.js
+++ b/devtools/server/tests/unit/test_protocol_formtype.js
@@ -1,9 +1,9 @@
-var protocol = require("devtools/server/protocol");
+var protocol = require("devtools/shared/protocol");
 var {method, Arg, Option, RetVal} = protocol;
 
 protocol.types.addActorType("child");
 protocol.types.addActorType("root");
 
 // The child actor doesn't provide a form description
 var ChildActor = protocol.ActorClass({
   typeName: "child",
--- a/devtools/server/tests/unit/test_protocol_longstring.js
+++ b/devtools/server/tests/unit/test_protocol_longstring.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test simple requests using the protocol helpers.
  */
-var protocol = require("devtools/server/protocol");
+var protocol = require("devtools/shared/protocol");
 var {method, RetVal, Arg, Option} = protocol;
 var events = require("sdk/event/core");
 var {LongStringActor} = require("devtools/server/actors/string");
 
 function simpleHello() {
   return {
     from: "root",
     applicationType: "xpcshell-tests",
--- a/devtools/server/tests/unit/test_protocol_simple.js
+++ b/devtools/server/tests/unit/test_protocol_simple.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test simple requests using the protocol helpers.
  */
 
-var protocol = require("devtools/server/protocol");
+var protocol = require("devtools/shared/protocol");
 var {method, Arg, Option, RetVal} = protocol;
 var events = require("sdk/event/core");
 
 function simpleHello() {
   return {
     from: "root",
     applicationType: "xpcshell-tests",
     traits: [],
--- a/devtools/server/tests/unit/test_protocol_stack.js
+++ b/devtools/server/tests/unit/test_protocol_stack.js
@@ -5,17 +5,17 @@
 
 /**
  * Client request stacks should span the entire process from before making the
  * request to handling the reply from the server.  The server frames are not
  * included, nor can they be in most cases, since the server can be a remote
  * device.
  */
 
-var protocol = require("devtools/server/protocol");
+var protocol = require("devtools/shared/protocol");
 var {method, Arg, Option, RetVal} = protocol;
 var events = require("sdk/event/core");
 
 function simpleHello() {
   return {
     from: "root",
     applicationType: "xpcshell-tests",
     traits: [],
--- a/devtools/server/tests/unit/test_protocol_unregister.js
+++ b/devtools/server/tests/unit/test_protocol_unregister.js
@@ -1,9 +1,9 @@
-const {types} = require("devtools/server/protocol");
+const {types} = require("devtools/shared/protocol");
 
 
 function run_test()
 {
   types.addType("test", {
     read: (v) => { return "successful read: " + v },
     write: (v) => { return "successful write: " + v }
   });
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -44,11 +44,12 @@ DevToolsModules(
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'event-emitter.js',
     'event-parsers.js',
     'indentation.js',
     'Loader.jsm',
     'Parser.jsm',
     'path.js',
+    'protocol.js',
     'system.js',
     'ThreadSafeDevToolsUtils.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/shared/protocol.js
@@ -0,0 +1,1541 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { Cu, components } = require("chrome");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var Services = require("Services");
+var promise = require("promise");
+var {Class} = require("sdk/core/heritage");
+var {EventTarget} = require("sdk/event/target");
+var events = require("sdk/event/core");
+var object = require("sdk/util/object");
+
+exports.emit = events.emit;
+
+/**
+ * Types: named marshallers/demarshallers.
+ *
+ * Types provide a 'write' function that takes a js representation and
+ * returns a protocol representation, and a "read" function that
+ * takes a protocol representation and returns a js representation.
+ *
+ * The read and write methods are also passed a context object that
+ * represent the actor or front requesting the translation.
+ *
+ * Types are referred to with a typestring.  Basic types are
+ * registered by name using addType, and more complex types can
+ * be generated by adding detail to the type name.
+ */
+
+var types = Object.create(null);
+exports.types = types;
+
+var registeredTypes = types.registeredTypes = new Map();
+var registeredLifetimes = types.registeredLifetimes = new Map();
+
+/**
+ * Return the type object associated with a given typestring.
+ * If passed a type object, it will be returned unchanged.
+ *
+ * Types can be registered with addType, or can be created on
+ * the fly with typestrings.  Examples:
+ *
+ *   boolean
+ *   threadActor
+ *   threadActor#detail
+ *   array:threadActor
+ *   array:array:threadActor#detail
+ *
+ * @param [typestring|type] type
+ *    Either a typestring naming a type or a type object.
+ *
+ * @returns a type object.
+ */
+types.getType = function(type) {
+  if (!type) {
+    return types.Primitive;
+  }
+
+  if (typeof(type) !== "string") {
+    return type;
+  }
+
+  // If already registered, we're done here.
+  let reg = registeredTypes.get(type);
+  if (reg) return reg;
+
+  // New type, see if it's a collection/lifetime type:
+  let sep = type.indexOf(":");
+  if (sep >= 0) {
+    let collection = type.substring(0, sep);
+    let subtype = types.getType(type.substring(sep + 1));
+
+    if (collection === "array") {
+      return types.addArrayType(subtype);
+    } else if (collection === "nullable") {
+      return types.addNullableType(subtype);
+    }
+
+    if (registeredLifetimes.has(collection)) {
+      return types.addLifetimeType(collection, subtype);
+    }
+
+    throw Error("Unknown collection type: " + collection);
+  }
+
+  // Not a collection, might be actor detail
+  let pieces = type.split("#", 2);
+  if (pieces.length > 1) {
+    return types.addActorDetail(type, pieces[0], pieces[1]);
+  }
+
+  // Might be a lazily-loaded type
+  if (type === "longstring") {
+    require("devtools/server/actors/string");
+    return registeredTypes.get("longstring");
+  }
+
+  throw Error("Unknown type: " + type);
+}
+
+/**
+ * Don't allow undefined when writing primitive types to packets.  If
+ * you want to allow undefined, use a nullable type.
+ */
+function identityWrite(v) {
+  if (v === undefined) {
+    throw Error("undefined passed where a value is required");
+  }
+  // This has to handle iterator->array conversion because arrays of
+  // primitive types pass through here.
+  if (v && typeof (v) === "object" && Symbol.iterator in v) {
+    return [...v];
+  }
+  return v;
+}
+
+/**
+ * Add a type to the type system.
+ *
+ * When registering a type, you can provide `read` and `write` methods.
+ *
+ * The `read` method will be passed a JS object value from the JSON
+ * packet and must return a native representation.  The `write` method will
+ * be passed a native representation and should provide a JSONable value.
+ *
+ * These methods will both be passed a context.  The context is the object
+ * performing or servicing the request - on the server side it will be
+ * an Actor, on the client side it will be a Front.
+ *
+ * @param typestring name
+ *    Name to register
+ * @param object typeObject
+ *    An object whose properties will be stored in the type, including
+ *    the `read` and `write` methods.
+ * @param object options
+ *    Can specify `thawed` to prevent the type from being frozen.
+ *
+ * @returns a type object that can be used in protocol definitions.
+ */
+types.addType = function(name, typeObject={}, options={}) {
+  if (registeredTypes.has(name)) {
+    throw Error("Type '" + name + "' already exists.");
+  }
+
+  let type = object.merge({
+    toString() { return "[protocol type:" + name + "]"},
+    name: name,
+    primitive: !(typeObject.read || typeObject.write),
+    read: identityWrite,
+    write: identityWrite
+  }, typeObject);
+
+  registeredTypes.set(name, type);
+
+  return type;
+};
+
+/**
+ * Remove a type previously registered with the system.
+ * Primarily useful for types registered by addons.
+ */
+types.removeType = function(name) {
+  // This type may still be referenced by other types, make sure
+  // those references don't work.
+  let type = registeredTypes.get(name);
+
+  type.name = "DEFUNCT:" + name;
+  type.category = "defunct";
+  type.primitive = false;
+  type.read = type.write = function() { throw new Error("Using defunct type: " + name); };
+
+  registeredTypes.delete(name);
+}
+
+/**
+ * Add an array type to the type system.
+ *
+ * getType() will call this function if provided an "array:<type>"
+ * typestring.
+ *
+ * @param type subtype
+ *    The subtype to be held by the array.
+ */
+types.addArrayType = function(subtype) {
+  subtype = types.getType(subtype);
+
+  let name = "array:" + subtype.name;
+
+  // Arrays of primitive types are primitive types themselves.
+  if (subtype.primitive) {
+    return types.addType(name);
+  }
+  return types.addType(name, {
+    category: "array",
+    read: (v, ctx) => [...v].map(i => subtype.read(i, ctx)),
+    write: (v, ctx) => [...v].map(i => subtype.write(i, ctx))
+  });
+};
+
+/**
+ * Add a dict type to the type system.  This allows you to serialize
+ * a JS object that contains non-primitive subtypes.
+ *
+ * Properties of the value that aren't included in the specializations
+ * will be serialized as primitive values.
+ *
+ * @param object specializations
+ *    A dict of property names => type
+ */
+types.addDictType = function(name, specializations) {
+  return types.addType(name, {
+    category: "dict",
+    specializations: specializations,
+    read: (v, ctx) => {
+      let ret = {};
+      for (let prop in v) {
+        if (prop in specializations) {
+          ret[prop] = types.getType(specializations[prop]).read(v[prop], ctx);
+        } else {
+          ret[prop] = v[prop];
+        }
+      }
+      return ret;
+    },
+
+    write: (v, ctx) => {
+      let ret = {};
+      for (let prop in v) {
+        if (prop in specializations) {
+          ret[prop] = types.getType(specializations[prop]).write(v[prop], ctx);
+        } else {
+          ret[prop] = v[prop];
+        }
+      }
+      return ret;
+    }
+  })
+}
+
+/**
+ * Register an actor type with the type system.
+ *
+ * Types are marshalled differently when communicating server->client
+ * than they are when communicating client->server.  The server needs
+ * to provide useful information to the client, so uses the actor's
+ * `form` method to get a json representation of the actor.  When
+ * making a request from the client we only need the actor ID string.
+ *
+ * This function can be called before the associated actor has been
+ * constructed, but the read and write methods won't work until
+ * the associated addActorImpl or addActorFront methods have been
+ * called during actor/front construction.
+ *
+ * @param string name
+ *    The typestring to register.
+ */
+types.addActorType = function(name) {
+  let type = types.addType(name, {
+    _actor: true,
+    category: "actor",
+    read: (v, ctx, detail) => {
+      // If we're reading a request on the server side, just
+      // find the actor registered with this actorID.
+      if (ctx instanceof Actor) {
+        return ctx.conn.getActor(v);
+      }
+
+      // Reading a response on the client side, check for an
+      // existing front on the connection, and create the front
+      // if it isn't found.
+      let actorID = typeof(v) === "string" ? v : v.actor;
+      let front = ctx.conn.getActor(actorID);
+      if (!front) {
+        front = new type.frontClass(ctx.conn);
+        front.actorID = actorID;
+        ctx.marshallPool().manage(front);
+      }
+
+      v = type.formType(detail).read(v, front, detail);
+      front.form(v, detail, ctx);
+
+      return front;
+    },
+    write: (v, ctx, detail) => {
+      // If returning a response from the server side, make sure
+      // the actor is added to a parent object and return its form.
+      if (v instanceof Actor) {
+        if (!v.actorID) {
+          ctx.marshallPool().manage(v);
+        }
+        return type.formType(detail).write(v.form(detail), ctx, detail);
+      }
+
+      // Writing a request from the client side, just send the actor id.
+      return v.actorID;
+    },
+    formType: (detail) => {
+      if (!("formType" in type.actorSpec)) {
+        return types.Primitive;
+      }
+
+      let formAttr = "formType";
+      if (detail) {
+        formAttr += "#" + detail;
+      }
+
+      if (!(formAttr in type.actorSpec)) {
+        throw new Error("No type defined for " + formAttr);
+      }
+
+      return type.actorSpec[formAttr];
+    }
+  });
+  return type;
+}
+
+types.addNullableType = function(subtype) {
+  subtype = types.getType(subtype);
+  return types.addType("nullable:" + subtype.name, {
+    category: "nullable",
+    read: (value, ctx) => {
+      if (value == null) {
+        return value;
+      }
+      return subtype.read(value, ctx);
+    },
+    write: (value, ctx) => {
+      if (value == null) {
+        return value;
+      }
+      return subtype.write(value, ctx);
+    }
+  });
+}
+
+/**
+ * Register an actor detail type.  This is just like an actor type, but
+ * will pass a detail hint to the actor's form method during serialization/
+ * deserialization.
+ *
+ * This is called by getType() when passed an 'actorType#detail' string.
+ *
+ * @param string name
+ *   The typestring to register this type as.
+ * @param type actorType
+ *   The actor type you'll be detailing.
+ * @param string detail
+ *   The detail to pass.
+ */
+types.addActorDetail = function(name, actorType, detail) {
+  actorType = types.getType(actorType);
+  if (!actorType._actor) {
+    throw Error("Details only apply to actor types, tried to add detail '" + detail + "'' to " + actorType.name + "\n");
+  }
+  return types.addType(name, {
+    _actor: true,
+    category: "detail",
+    read: (v, ctx) => actorType.read(v, ctx, detail),
+    write: (v, ctx) => actorType.write(v, ctx, detail)
+  });
+}
+
+/**
+ * Register an actor lifetime.  This lets the type system find a parent
+ * actor that differs from the actor fulfilling the request.
+ *
+ * @param string name
+ *    The lifetime name to use in typestrings.
+ * @param string prop
+ *    The property of the actor that holds the parent that should be used.
+ */
+types.addLifetime = function(name, prop) {
+  if (registeredLifetimes.has(name)) {
+    throw Error("Lifetime '" + name + "' already registered.");
+  }
+  registeredLifetimes.set(name, prop);
+}
+
+/**
+ * Remove a previously-registered lifetime.  Useful for lifetimes registered
+ * in addons.
+ */
+types.removeLifetime = function(name) {
+  registeredLifetimes.delete(name);
+}
+
+/**
+ * Register a lifetime type.  This creates an actor type tied to the given
+ * lifetime.
+ *
+ * This is called by getType() when passed a '<lifetimeType>:<actorType>'
+ * typestring.
+ *
+ * @param string lifetime
+ *    A lifetime string previously regisered with addLifetime()
+ * @param type subtype
+ *    An actor type
+ */
+types.addLifetimeType = function(lifetime, subtype) {
+  subtype = types.getType(subtype);
+  if (!subtype._actor) {
+    throw Error("Lifetimes only apply to actor types, tried to apply lifetime '" + lifetime + "'' to " + subtype.name);
+  }
+  let prop = registeredLifetimes.get(lifetime);
+  return types.addType(lifetime + ":" + subtype.name, {
+    category: "lifetime",
+    read: (value, ctx) => subtype.read(value, ctx[prop]),
+    write: (value, ctx) => subtype.write(value, ctx[prop])
+  })
+}
+
+// Add a few named primitive types.
+types.Primitive = types.addType("primitive");
+types.String = types.addType("string");
+types.Number = types.addType("number");
+types.Boolean = types.addType("boolean");
+types.JSON = types.addType("json");
+
+/**
+ * Request/Response templates and generation
+ *
+ * Request packets are specified as json templates with
+ * Arg and Option placeholders where arguments should be
+ * placed.
+ *
+ * Reponse packets are also specified as json templates,
+ * with a RetVal placeholder where the return value should be
+ * placed.
+ */
+
+/**
+ * Placeholder for simple arguments.
+ *
+ * @param number index
+ *    The argument index to place at this position.
+ * @param type type
+ *    The argument should be marshalled as this type.
+ * @constructor
+ */
+var Arg = Class({
+  initialize: function(index, type) {
+    this.index = index;
+    this.type = types.getType(type);
+  },
+
+  write: function(arg, ctx) {
+    return this.type.write(arg, ctx);
+  },
+
+  read: function(v, ctx, outArgs) {
+    outArgs[this.index] = this.type.read(v, ctx);
+  },
+
+  describe: function() {
+    return {
+      _arg: this.index,
+      type: this.type.name,
+    }
+  }
+});
+exports.Arg = Arg;
+
+/**
+ * Placeholder for an options argument value that should be hoisted
+ * into the packet.
+ *
+ * If provided in a method specification:
+ *
+ *   { optionArg: Option(1)}
+ *
+ * Then arguments[1].optionArg will be placed in the packet in this
+ * value's place.
+ *
+ * @param number index
+ *    The argument index of the options value.
+ * @param type type
+ *    The argument should be marshalled as this type.
+ * @constructor
+ */
+var Option = Class({
+  extends: Arg,
+  initialize: function(index, type) {
+    Arg.prototype.initialize.call(this, index, type)
+  },
+
+  write: function(arg, ctx, name) {
+    // Ignore if arg is undefined or null; allow other falsy values
+    if (arg == undefined || arg[name] == undefined) {
+      return undefined;
+    }
+    let v = arg[name];
+    return this.type.write(v, ctx);
+  },
+  read: function(v, ctx, outArgs, name) {
+    if (outArgs[this.index] === undefined) {
+      outArgs[this.index] = {};
+    }
+    if (v === undefined) {
+      return;
+    }
+    outArgs[this.index][name] = this.type.read(v, ctx);
+  },
+
+  describe: function() {
+    return {
+      _option: this.index,
+      type: this.type.name,
+    }
+  }
+});
+
+exports.Option = Option;
+
+/**
+ * Placeholder for return values in a response template.
+ *
+ * @param type type
+ *    The return value should be marshalled as this type.
+ */
+var RetVal = Class({
+  initialize: function(type) {
+    this.type = types.getType(type);
+  },
+
+  write: function(v, ctx) {
+    return this.type.write(v, ctx);
+  },
+
+  read: function(v, ctx) {
+    return this.type.read(v, ctx);
+  },
+
+  describe: function() {
+    return {
+      _retval: this.type.name
+    }
+  }
+});
+
+exports.RetVal = RetVal;
+
+/* Template handling functions */
+
+/**
+ * Get the value at a given path, or undefined if not found.
+ */
+function getPath(obj, path) {
+  for (let name of path) {
+    if (!(name in obj)) {
+      return undefined;
+    }
+    obj = obj[name];
+  }
+  return obj;
+}
+
+/**
+ * Find Placeholders in the template and save them along with their
+ * paths.
+ */
+function findPlaceholders(template, constructor, path=[], placeholders=[]) {
+  if (!template || typeof(template) != "object") {
+    return placeholders;
+  }
+
+  if (template instanceof constructor) {
+    placeholders.push({ placeholder: template, path: [...path] });
+    return placeholders;
+  }
+
+  for (let name in template) {
+    path.push(name);
+    findPlaceholders(template[name], constructor, path, placeholders);
+    path.pop();
+  }
+
+  return placeholders;
+}
+
+
+function describeTemplate(template) {
+  return JSON.parse(JSON.stringify(template, (key, value) => {
+    if (value.describe) {
+      return value.describe();
+    }
+    return value;
+  }));
+}
+
+/**
+ * Manages a request template.
+ *
+ * @param object template
+ *    The request template.
+ * @construcor
+ */
+var Request = Class({
+  initialize: function(template={}) {
+    this.type = template.type;
+    this.template = template;
+    this.args = findPlaceholders(template, Arg);
+  },
+
+  /**
+   * Write a request.
+   *
+   * @param array fnArgs
+   *    The function arguments to place in the request.
+   * @param object ctx
+   *    The object making the request.
+   * @returns a request packet.
+   */
+  write: function(fnArgs, ctx) {
+    let str = JSON.stringify(this.template, (key, value) => {
+      if (value instanceof Arg) {
+        return value.write(value.index in fnArgs ? fnArgs[value.index] : undefined,
+                           ctx, key);
+      }
+      return value;
+    });
+    return JSON.parse(str);
+  },
+
+  /**
+   * Read a request.
+   *
+   * @param object packet
+   *    The request packet.
+   * @param object ctx
+   *    The object making the request.
+   * @returns an arguments array
+   */
+  read: function(packet, ctx) {
+    let fnArgs = [];
+    for (let templateArg of this.args) {
+      let arg = templateArg.placeholder;
+      let path = templateArg.path;
+      let name = path[path.length - 1];
+      arg.read(getPath(packet, path), ctx, fnArgs, name);
+    }
+    return fnArgs;
+  },
+
+  describe: function() { return describeTemplate(this.template); }
+});
+
+/**
+ * Manages a response template.
+ *
+ * @param object template
+ *    The response template.
+ * @construcor
+ */
+var Response = Class({
+  initialize: function(template={}) {
+    this.template = template;
+    let placeholders = findPlaceholders(template, RetVal);
+    if (placeholders.length > 1) {
+      throw Error("More than one RetVal specified in response");
+    }
+    let placeholder = placeholders.shift();
+    if (placeholder) {
+      this.retVal = placeholder.placeholder;
+      this.path = placeholder.path;
+    }
+  },
+
+  /**
+   * Write a response for the given return value.
+   *
+   * @param val ret
+   *    The return value.
+   * @param object ctx
+   *    The object writing the response.
+   */
+  write: function(ret, ctx) {
+    return JSON.parse(JSON.stringify(this.template, function(key, value) {
+      if (value instanceof RetVal) {
+        return value.write(ret, ctx);
+      }
+      return value;
+    }));
+  },
+
+  /**
+   * Read a return value from the given response.
+   *
+   * @param object packet
+   *    The response packet.
+   * @param object ctx
+   *    The object reading the response.
+   */
+  read: function(packet, ctx) {
+    if (!this.retVal) {
+      return undefined;
+    }
+    let v = getPath(packet, this.path);
+    return this.retVal.read(v, ctx);
+  },
+
+  describe: function() { return describeTemplate(this.template); }
+});
+
+/**
+ * Actor and Front implementations
+ */
+
+/**
+ * A protocol object that can manage the lifetime of other protocol
+ * objects.
+ */
+var Pool = Class({
+  extends: EventTarget,
+
+  /**
+   * Pools are used on both sides of the connection to help coordinate
+   * lifetimes.
+   *
+   * @param optional conn
+   *   Either a DebuggerServerConnection or a DebuggerClient.  Must have
+   *   addActorPool, removeActorPool, and poolFor.
+   *   conn can be null if the subclass provides a conn property.
+   * @constructor
+   */
+  initialize: function(conn) {
+    if (conn) {
+      this.conn = conn;
+    }
+  },
+
+  /**
+   * Return the parent pool for this client.
+   */
+  parent: function() { return this.conn.poolFor(this.actorID) },
+
+  /**
+   * Override this if you want actors returned by this actor
+   * to belong to a different actor by default.
+   */
+  marshallPool: function() { return this; },
+
+  /**
+   * Pool is the base class for all actors, even leaf nodes.
+   * If the child map is actually referenced, go ahead and create
+   * the stuff needed by the pool.
+   */
+  __poolMap: null,
+  get _poolMap() {
+    if (this.__poolMap) return this.__poolMap;
+    this.__poolMap = new Map();
+    this.conn.addActorPool(this);
+    return this.__poolMap;
+  },
+
+  /**
+   * Add an actor as a child of this pool.
+   */
+  manage: function(actor) {
+    if (!actor.actorID) {
+      actor.actorID = this.conn.allocID(actor.actorPrefix || actor.typeName);
+    }
+
+    this._poolMap.set(actor.actorID, actor);
+    return actor;
+  },
+
+  /**
+   * Remove an actor as a child of this pool.
+   */
+  unmanage: function(actor) {
+    this.__poolMap && this.__poolMap.delete(actor.actorID);
+  },
+
+  // 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;
+  },
+
+  // 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;
+  },
+
+  // True if this pool has no children.
+  isEmpty: function() {
+    return !this.__poolMap || this._poolMap.size == 0;
+  },
+
+  /**
+   * Destroy this item, removing it from a parent if it has one,
+   * and destroying all children if necessary.
+   */
+  destroy: function() {
+    let parent = this.parent();
+    if (parent) {
+      parent.unmanage(this);
+    }
+    if (!this.__poolMap) {
+      return;
+    }
+    for (let actor of this.__poolMap.values()) {
+      // Self-owned actors are ok, but don't need destroying twice.
+      if (actor === this) {
+        continue;
+      }
+      let destroy = actor.destroy;
+      if (destroy) {
+        // Disconnect destroy while we're destroying in case of (misbehaving)
+        // circular ownership.
+        actor.destroy = null;
+        destroy.call(actor);
+        actor.destroy = destroy;
+      }
+    };
+    this.conn.removeActorPool(this, true);
+    this.__poolMap.clear();
+    this.__poolMap = null;
+  },
+
+  /**
+   * For getting along with the debugger server pools, should be removable
+   * eventually.
+   */
+  cleanup: function() {
+    this.destroy();
+  }
+});
+exports.Pool = Pool;
+
+/**
+ * An actor in the actor tree.
+ */
+var Actor = Class({
+  extends: Pool,
+
+  // Will contain the actor's ID
+  actorID: null,
+
+  /**
+   * Initialize an actor.
+   *
+   * @param optional conn
+   *   Either a DebuggerServerConnection or a DebuggerClient.  Must have
+   *   addActorPool, removeActorPool, and poolFor.
+   *   conn can be null if the subclass provides a conn property.
+   * @constructor
+   */
+  initialize: function(conn) {
+    Pool.prototype.initialize.call(this, conn);
+
+    // Forward events to the connection.
+    if (this._actorSpec && this._actorSpec.events) {
+      for (let key of this._actorSpec.events.keys()) {
+        let name = key;
+        let sendEvent = this._sendEvent.bind(this, name)
+        this.on(name, (...args) => {
+          sendEvent.apply(null, args);
+        });
+      }
+    }
+  },
+
+  toString: function() { return "[Actor " + this.typeName + "/" + this.actorID + "]" },
+
+  _sendEvent: function(name, ...args) {
+    if (!this._actorSpec.events.has(name)) {
+      // It's ok to emit events that don't go over the wire.
+      return;
+    }
+    let request = this._actorSpec.events.get(name);
+    let packet;
+    try {
+      packet = request.write(args, this);
+    } catch(ex) {
+      console.error("Error sending event: " + name);
+      throw ex;
+    }
+    packet.from = packet.from || this.actorID;
+    this.conn.send(packet);
+  },
+
+  destroy: function() {
+    Pool.prototype.destroy.call(this);
+    this.actorID = null;
+  },
+
+  /**
+   * Override this method in subclasses to serialize the actor.
+   * @param [optional] string hint
+   *   Optional string to customize the form.
+   * @returns A jsonable object.
+   */
+  form: function(hint) {
+    return { actor: this.actorID }
+  },
+
+  writeError: function(error) {
+    console.error(error);
+    if (error.stack) {
+      dump(error.stack);
+    }
+    this.conn.send({
+      from: this.actorID,
+      error: error.error || "unknownError",
+      message: error.message
+    });
+  },
+
+  _queueResponse: function(create) {
+    let pending = this._pendingResponse || promise.resolve(null);
+    let response = create(pending);
+    this._pendingResponse = response;
+  }
+});
+exports.Actor = Actor;
+
+/**
+ * Tags a prtotype method as an actor method implementation.
+ *
+ * @param function fn
+ *    The implementation function, will be returned.
+ * @param spec
+ *    The method specification, with the following (optional) properties:
+ *      request (object): a request template.
+ *      response (object): a response template.
+ *      oneway (bool): 'true' if no response should be sent.
+ *      telemetry (string): Telemetry probe ID for measuring completion time.
+ */
+exports.method = function(fn, spec={}) {
+  fn._methodSpec = Object.freeze(spec);
+  if (spec.request) Object.freeze(spec.request);
+  if (spec.response) Object.freeze(spec.response);
+  return fn;
+}
+
+/**
+ * Generates an actor specification from an actor description.
+ */
+var generateActorSpec = function(actorDesc) {
+  let actorSpec = {
+    typeName: actorDesc.typeName,
+    methods: []
+  };
+
+  // Find method and form specifications attached to properties.
+  for (let name of Object.getOwnPropertyNames(actorDesc)) {
+    let desc = Object.getOwnPropertyDescriptor(actorDesc, name);
+    if (!desc.value) {
+      continue;
+    }
+
+    if (name.startsWith("formType")) {
+      if (typeof(desc.value) === "string") {
+        actorSpec[name] = types.getType(desc.value);
+      } else if (desc.value.name && registeredTypes.has(desc.value.name)) {
+        actorSpec[name] = desc.value;
+      } else {
+        // Shorthand for a newly-registered DictType.
+        actorSpec[name] = types.addDictType(actorDesc.typeName + "__" + name, desc.value);
+      }
+    }
+
+    if (desc.value._methodSpec) {
+      let methodSpec = desc.value._methodSpec;
+      let spec = {};
+      spec.name = methodSpec.name || name;
+      spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined));
+      spec.response = Response(methodSpec.response || undefined);
+      spec.telemetry = methodSpec.telemetry;
+      spec.release = methodSpec.release;
+      spec.oneway = methodSpec.oneway;
+
+      actorSpec.methods.push(spec);
+    }
+  }
+
+  // Find additional method specifications
+  if (actorDesc.methods) {
+    for (let name in actorDesc.methods) {
+      let methodSpec = actorDesc.methods[name];
+      let spec = {};
+
+      spec.name = methodSpec.name || name;
+      spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined));
+      spec.response = Response(methodSpec.response || undefined);
+      spec.telemetry = methodSpec.telemetry;
+      spec.release = methodSpec.release;
+      spec.oneway = methodSpec.oneway;
+
+      actorSpec.methods.push(spec);
+    }
+  }
+
+  // Find event specifications
+  if (actorDesc.events) {
+    actorSpec.events = new Map();
+    for (let name in actorDesc.events) {
+      let eventRequest = actorDesc.events[name];
+      Object.freeze(eventRequest);
+      actorSpec.events.set(name, Request(object.merge({type: name}, eventRequest)));
+    }
+  }
+
+  if (!registeredTypes.has(actorSpec.typeName)) {
+    types.addActorType(actorSpec.typeName);
+  }
+  registeredTypes.get(actorSpec.typeName).actorSpec = actorSpec;
+
+  return actorSpec;
+};
+exports.generateActorSpec = generateActorSpec;
+
+/**
+ * Generates request handlers as described by the given actor specification on
+ * the given actor prototype. Returns the actor prototype.
+ */
+var generateRequestHandlers = function(actorSpec, actorProto) {
+  if (actorProto._actorSpec) {
+    throw new Error("actorProto called twice on the same actor prototype!");
+  }
+
+  actorProto.typeName = actorSpec.typeName;
+
+  // Generate request handlers for each method definition
+  actorProto.requestTypes = Object.create(null);
+  actorSpec.methods.forEach(spec => {
+    let handler = function(packet, conn) {
+      try {
+        let args;
+        try {
+          args = spec.request.read(packet, this);
+        } catch(ex) {
+          console.error("Error reading request: " + packet.type);
+          throw ex;
+        }
+
+        let ret = this[spec.name].apply(this, args);
+
+        let sendReturn = (ret) => {
+          if (spec.oneway) {
+            // No need to send a response.
+            return;
+          }
+
+          let response;
+          try {
+            response = spec.response.write(ret, this);
+          } catch(ex) {
+            console.error("Error writing response to: " + spec.name);
+            throw ex;
+          }
+          response.from = this.actorID;
+          // If spec.release has been specified, destroy the object.
+          if (spec.release) {
+            try {
+              this.destroy();
+            } catch(e) {
+              this.writeError(e);
+              return;
+            }
+          }
+
+          conn.send(response);
+        };
+
+        this._queueResponse(p => {
+          return p
+            .then(() => ret)
+            .then(sendReturn)
+            .then(null, this.writeError.bind(this));
+        })
+      } catch(e) {
+        this._queueResponse(p => {
+          return p.then(() => this.writeError(e));
+        });
+      }
+    };
+
+    actorProto.requestTypes[spec.request.type] = handler;
+  });
+
+  actorProto._actorSpec = actorSpec;
+
+  return actorProto;
+}
+
+/**
+ * Create an actor class for the given actor prototype.
+ *
+ * @param object actorProto
+ *    The actor prototype.  Must have a 'typeName' property,
+ *    should have method definitions, can have event definitions.
+ */
+exports.ActorClass = function (actorProto) {
+  return ActorClassWithSpec(generateActorSpec(actorProto), actorProto);
+};
+
+/**
+ * Create an actor class for the given actor specification and prototype.
+ *
+ * @param object actorSpec
+ *    The actor specification. Must have a 'typeName' property.
+ * @param object actorProto
+ *    The actor prototype. Should have method definitions, can have event
+ *    definitions.
+ */
+var ActorClassWithSpec = function(actorSpec, actorProto) {
+  if (!actorSpec.typeName) {
+    throw Error("Actor specification must have a typeName member.");
+  }
+
+  actorProto.extends = Actor;
+  let cls = Class(generateRequestHandlers(actorSpec, actorProto));
+
+  return cls;
+};
+exports.ActorClassWithSpec = ActorClassWithSpec;
+
+/**
+ * Base class for client-side actor fronts.
+ */
+var Front = Class({
+  extends: Pool,
+
+  actorID: null,
+
+  /**
+   * The base class for client-side actor fronts.
+   *
+   * @param optional conn
+   *   Either a DebuggerServerConnection or a DebuggerClient.  Must have
+   *   addActorPool, removeActorPool, and poolFor.
+   *   conn can be null if the subclass provides a conn property.
+   * @param optional form
+   *   The json form provided by the server.
+   * @constructor
+   */
+  initialize: function(conn=null, form=null, detail=null, context=null) {
+    Pool.prototype.initialize.call(this, conn);
+    this._requests = [];
+
+    // protocol.js no longer uses this data in the constructor, only external
+    // uses do.  External usage of manually-constructed fronts will be
+    // drastically reduced if we convert the root and tab actors to
+    // protocol.js, in which case this can probably go away.
+    if (form) {
+      this.actorID = form.actor;
+      form = types.getType(this.typeName).formType(detail).read(form, this, detail);
+      this.form(form, detail, context);
+    }
+  },
+
+  destroy: function() {
+    // Reject all outstanding requests, they won't make sense after
+    // the front is destroyed.
+    while (this._requests && this._requests.length > 0) {
+      let { deferred, to, type, stack } = this._requests.shift();
+      let msg = "Connection closed, pending request to " + to +
+                ", type " + type + " failed" +
+                "\n\nRequest stack:\n" + stack.formattedStack;
+      deferred.reject(new Error(msg));
+    }
+    Pool.prototype.destroy.call(this);
+    this.actorID = null;
+  },
+
+  manage: function(front) {
+    if (!front.actorID) {
+      throw new Error("Can't manage front without an actor ID.\n" +
+                      "Ensure server supports " + front.typeName + ".");
+    }
+    return Pool.prototype.manage.call(this, front);
+  },
+
+  /**
+   * @returns a promise that will resolve to the actorID this front
+   * represents.
+   */
+  actor: function() { return promise.resolve(this.actorID) },
+
+  toString: function() { return "[Front for " + this.typeName + "/" + this.actorID + "]" },
+
+  /**
+   * Update the actor from its representation.
+   * Subclasses should override this.
+   */
+  form: function(form) {},
+
+  /**
+   * Send a packet on the connection.
+   */
+  send: function(packet) {
+    if (packet.to) {
+      this.conn._transport.send(packet);
+    } else {
+      this.actor().then(actorID => {
+        packet.to = actorID;
+        this.conn._transport.send(packet);
+      }).then(null, e => DevToolsUtils.reportException("Front.prototype.send", e));
+    }
+  },
+
+  /**
+   * Send a two-way request on the connection.
+   */
+  request: function(packet) {
+    let deferred = promise.defer();
+    // Save packet basics for debugging
+    let { to, type } = packet;
+    this._requests.push({
+      deferred,
+      to: to || this.actorID,
+      type,
+      stack: components.stack,
+    });
+    this.send(packet);
+    return deferred.promise;
+  },
+
+  /**
+   * Handler for incoming packets from the client's actor.
+   */
+  onPacket: function(packet) {
+    // Pick off event packets
+    let type = packet.type || undefined;
+    if (this._clientSpec.events && this._clientSpec.events.has(type)) {
+      let event = this._clientSpec.events.get(packet.type);
+      let args;
+      try {
+        args = event.request.read(packet, this);
+      } catch(ex) {
+        console.error("Error reading event: " + packet.type);
+        console.exception(ex);
+        throw ex;
+      }
+      if (event.pre) {
+        let results = event.pre.map(pre => pre.apply(this, args));
+
+        // Check to see if any of the preEvents returned a promise -- if so,
+        // wait for their resolution before emitting. Otherwise, emit synchronously.
+        if (results.some(result => result && typeof result.then === "function")) {
+          promise.all(results).then(() => events.emit.apply(null, [this, event.name].concat(args)));
+          return;
+        }
+      }
+
+      events.emit.apply(null, [this, event.name].concat(args));
+      return;
+    }
+
+    // Remaining packets must be responses.
+    if (this._requests.length === 0) {
+      let msg = "Unexpected packet " + this.actorID + ", " + JSON.stringify(packet);
+      let err = Error(msg);
+      console.error(err);
+      throw err;
+    }
+
+    let { deferred, stack } = this._requests.shift();
+    Cu.callFunctionWithAsyncStack(() => {
+      if (packet.error) {
+        // "Protocol error" is here to avoid TBPL heuristics. See also
+        // https://mxr.mozilla.org/webtools-central/source/tbpl/php/inc/GeneralErrorFilter.php
+        let message;
+        if (packet.error && packet.message) {
+          message = "Protocol error (" + packet.error + "): " + packet.message;
+        } else {
+          message = packet.error;
+        }
+        deferred.reject(message);
+      } else {
+        deferred.resolve(packet);
+      }
+    }, stack, "DevTools RDP");
+  }
+});
+exports.Front = Front;
+
+/**
+ * A method tagged with preEvent will be called after recieving a packet
+ * for that event, and before the front emits the event.
+ */
+exports.preEvent = function(eventName, fn) {
+  fn._preEvent = eventName;
+  return fn;
+}
+
+/**
+ * Mark a method as a custom front implementation, replacing the generated
+ * front method.
+ *
+ * @param function fn
+ *    The front implementation, will be returned.
+ * @param object options
+ *    Options object:
+ *      impl (string): If provided, the generated front method will be
+ *        stored as this property on the prototype.
+ */
+exports.custom = function(fn, options={}) {
+  fn._customFront = options;
+  return fn;
+}
+
+function prototypeOf(obj) {
+  return typeof(obj) === "function" ? obj.prototype : obj;
+}
+
+/**
+ * Generates request methods as described by the given actor specification on
+ * the given front prototype. Returns the front prototype.
+ */
+var generateRequestMethods = function(actorSpec, frontProto) {
+  if (frontProto._actorSpec) {
+    throw new Error("frontProto called twice on the same front prototype!");
+  }
+
+  frontProto.typeName = actorSpec.typeName;
+
+  // Generate request methods.
+  let methods = actorSpec.methods;
+  methods.forEach(spec => {
+    let name = spec.name;
+
+    // If there's already a property by this name in the front, it must
+    // be a custom front method.
+    if (name in frontProto) {
+      let custom = frontProto[spec.name]._customFront;
+      if (custom === undefined) {
+        throw Error("Existing method for " + spec.name + " not marked customFront while processing " + actorType.typeName + ".");
+      }
+      // If the user doesn't need the impl don't generate it.
+      if (!custom.impl) {
+        return;
+      }
+      name = custom.impl;
+    }
+
+    frontProto[name] = function(...args) {
+      let histogram, startTime;
+      if (spec.telemetry) {
+        if (spec.oneway) {
+          // That just doesn't make sense.
+          throw Error("Telemetry specified for a oneway request");
+        }
+        let transportType = this.conn.localTransport
+          ? "LOCAL_"
+          : "REMOTE_";
+        let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
+          + transportType + spec.telemetry + "_MS";
+        try {
+          histogram = Services.telemetry.getHistogramById(histogramId);
+          startTime = new Date();
+        } catch(ex) {
+          // XXX: Is this expected in xpcshell tests?
+          console.error(ex);
+          spec.telemetry = false;
+        }
+      }
+
+      let packet;
+      try {
+        packet = spec.request.write(args, this);
+      } catch(ex) {
+        console.error("Error writing request: " + name);
+        throw ex;
+      }
+      if (spec.oneway) {
+        // Fire-and-forget oneway packets.
+        this.send(packet);
+        return undefined;
+      }
+
+      return this.request(packet).then(response => {
+        let ret;
+        try {
+          ret = spec.response.read(response, this);
+        } catch(ex) {
+          console.error("Error reading response to: " + name);
+          throw ex;
+        }
+
+        if (histogram) {
+          histogram.add(+new Date - startTime);
+        }
+
+        return ret;
+      });
+    }
+
+    // Release methods should call the destroy function on return.
+    if (spec.release) {
+      let fn = frontProto[name];
+      frontProto[name] = function(...args) {
+        return fn.apply(this, args).then(result => {
+          this.destroy();
+          return result;
+        })
+      }
+    }
+  });
+
+
+  // Process event specifications
+  frontProto._clientSpec = {};
+
+  let events = actorSpec.events;
+  if (events) {
+    // This actor has events, scan the prototype for preEvent handlers...
+    let preHandlers = new Map();
+    for (let name of Object.getOwnPropertyNames(frontProto)) {
+      let desc = Object.getOwnPropertyDescriptor(frontProto, name);
+      if (!desc.value) {
+        continue;
+      }
+      if (desc.value._preEvent) {
+        let preEvent = desc.value._preEvent;
+        if (!events.has(preEvent)) {
+          throw Error("preEvent for event that doesn't exist: " + preEvent);
+        }
+        let handlers = preHandlers.get(preEvent);
+        if (!handlers) {
+          handlers = [];
+          preHandlers.set(preEvent, handlers);
+        }
+        handlers.push(desc.value);
+      }
+    }
+
+    frontProto._clientSpec.events = new Map();
+
+    for (let [name, request] of events) {
+      frontProto._clientSpec.events.set(request.type, {
+        name: name,
+        request: request,
+        pre: preHandlers.get(name)
+      });
+    }
+  }
+
+  frontProto._actorSpec = actorSpec;
+
+  return frontProto;
+}
+
+/**
+ * Create a front class for the given actor class and front prototype.
+ *
+ * @param ActorClass actorType
+ *    The actor class you're creating a front for.
+ * @param object frontProto
+ *    The front prototype.  Must have a 'typeName' property,
+ *    should have method definitions, can have event definitions.
+ */
+exports.FrontClass = function(actorType, frontProto) {
+  return FrontClassWithSpec(prototypeOf(actorType)._actorSpec, frontProto);
+}
+
+/**
+ * Create a front class for the given actor specification and front prototype.
+ *
+ * @param object actorSpec
+ *    The actor specification you're creating a front for.
+ * @param object proto
+ *    The object prototype.  Must have a 'typeName' property,
+ *    should have method definitions, can have event definitions.
+ */
+var FrontClassWithSpec = function(actorSpec, frontProto) {
+  frontProto.extends = Front;
+  let cls = Class(generateRequestMethods(actorSpec, frontProto));
+
+  if (!registeredTypes.has(actorSpec.typeName)) {
+    types.addActorType(actorSpec.typeName);
+  }
+  registeredTypes.get(actorSpec.typeName).frontClass = cls;
+
+  return cls;
+}
+exports.FrontClassWithSpec = FrontClassWithSpec;
+
+exports.dumpActorSpec = function(type) {
+  let actorSpec = type.actorSpec;
+  let ret = {
+    category: "actor",
+    typeName: type.name,
+    methods: [],
+    events: {}
+  };
+
+  for (let method of actorSpec.methods) {
+    ret.methods.push({
+      name: method.name,
+      release: method.release || undefined,
+      oneway: method.oneway || undefined,
+      request: method.request.describe(),
+      response: method.response.describe()
+    });
+  }
+
+  if (actorSpec.events) {
+    for (let [name, request] of actorSpec.events) {
+      ret.events[name] = request.describe();
+    }
+  }
+
+
+  JSON.stringify(ret);
+
+  return ret;
+}
+
+exports.dumpProtocolSpec = function() {
+  let ret = {
+    types: {},
+  };
+
+  for (let [name, type] of registeredTypes) {
+    // Force lazy instantiation if needed.
+    type = types.getType(name);
+    let category = type.category || undefined;
+    if (category === "dict") {
+      ret.types[name] = {
+        category: "dict",
+        typeName: name,
+        specializations: type.specializations
+      }
+    } else if (category === "actor") {
+      ret.types[name] = exports.dumpActorSpec(type);
+    }
+  }
+
+  return ret;
+}
--- a/devtools/shared/specs/storage.js
+++ b/devtools/shared/specs/storage.js
@@ -1,14 +1,14 @@
 /* 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 protocol = require("devtools/server/protocol");
+const protocol = require("devtools/shared/protocol");
 const { Arg, RetVal, types } = protocol;
 
 let childSpecs = {};
 
 function createStorageSpec(options) {
   // common methods for all storage types
   let methods = {
     getStoreObjects: {
--- a/devtools/shared/specs/stylesheets.js
+++ b/devtools/shared/specs/stylesheets.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {
   Arg,
   RetVal,
   generateActorSpec,
   types
-} = require("devtools/server/protocol.js");
+} = require("devtools/shared/protocol.js");
 
 const originalSourceSpec = generateActorSpec({
   typeName: "originalsource",
 
   methods: {
     getText: {
       response: {
         text: RetVal("longstring")