Bug 1000814 - Remove loader boilerplate from transport.js. r=jryans draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Wed, 11 Jul 2018 08:25:26 -0700
changeset 817212 3e3b4be1b441810a1c6858d6899413857e48b545
parent 817211 227e6c519f62cdd59e59bb788cc0e118f55d9455
child 817213 e6249c4984150ee339031939a7567c1ad49b3f78
push id115987
push userbmo:poirot.alex@gmail.com
push dateThu, 12 Jul 2018 07:12:00 +0000
reviewersjryans
bugs1000814
milestone63.0a1
Bug 1000814 - Remove loader boilerplate from transport.js. r=jryans MozReview-Commit-ID: CLrKUnDBvPy
devtools/shared/transport/transport.js
--- a/devtools/shared/transport/transport.js
+++ b/devtools/shared/transport/transport.js
@@ -1,916 +1,900 @@
 /* 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";
 
-/* global Pipe, ScriptableInputStream, uneval */
+/* global uneval */
+
+const { Cc, Cr, CC } = require("chrome");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { dumpn, dumpv } = DevToolsUtils;
+const flags = require("devtools/shared/flags");
+const StreamUtils = require("devtools/shared/transport/stream-utils");
+const { Packet, JSONPacket, BulkPacket } =
+  require("devtools/shared/transport/packets");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+
+loader.lazyGetter(this, "Pipe", () => {
+  return CC("@mozilla.org/pipe;1", "nsIPipe", "init");
+});
+
+loader.lazyGetter(this, "ScriptableInputStream", () => {
+  return CC("@mozilla.org/scriptableinputstream;1",
+          "nsIScriptableInputStream", "init");
+});
+
+const PACKET_HEADER_MAX = 200;
 
-// TODO: Get rid of this code once the marionette server loads transport.js as
-// an SDK module (see bug 1000814)
-(function(factory) {
-  if (this.module && module.id.includes("transport")) {
-    // require
-    factory.call(this, require, exports);
-  } else if (this.require) {
-    // loadSubScript
-    factory.call(this, require, this);
-  } else {
-    // Cu.import
-    const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
-    factory.call(this, require, this);
-  }
-}).call(this, function(require, exports) {
-  const { Cc, Cr, CC } = require("chrome");
-  const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-  const { dumpn, dumpv } = DevToolsUtils;
-  const flags = require("devtools/shared/flags");
-  const StreamUtils = require("devtools/shared/transport/stream-utils");
-  const { Packet, JSONPacket, BulkPacket } =
-  require("devtools/shared/transport/packets");
-  const promise = require("promise");
-  const defer = require("devtools/shared/defer");
+/**
+ * An adapter that handles data transfers between the debugger client and
+ * server. It can work with both nsIPipe and nsIServerSocket transports so
+ * long as the properly created input and output streams are specified.
+ * (However, for intra-process connections, LocalDebuggerTransport, below,
+ * is more efficient than using an nsIPipe pair with DebuggerTransport.)
+ *
+ * @param input nsIAsyncInputStream
+ *        The input stream.
+ * @param output nsIAsyncOutputStream
+ *        The output stream.
+ *
+ * Given a DebuggerTransport instance dt:
+ * 1) Set dt.hooks to a packet handler object (described below).
+ * 2) Call dt.ready() to begin watching for input packets.
+ * 3) Call dt.send() / dt.startBulkSend() to send packets.
+ * 4) Call dt.close() to close the connection, and disengage from the event
+ *    loop.
+ *
+ * A packet handler is an object with the following methods:
+ *
+ * - onPacket(packet) - called when we have received a complete packet.
+ *   |packet| is the parsed form of the packet --- a JavaScript value, not
+ *   a JSON-syntax string.
+ *
+ * - onBulkPacket(packet) - called when we have switched to bulk packet
+ *   receiving mode. |packet| is an object containing:
+ *   * actor:  Name of actor that will receive the packet
+ *   * type:   Name of actor's method that should be called on receipt
+ *   * length: Size of the data to be read
+ *   * stream: This input stream should only be used directly if you can ensure
+ *             that you will read exactly |length| bytes and will not close the
+ *             stream when reading is complete
+ *   * done:   If you use the stream directly (instead of |copyTo| below), you
+ *             must signal completion by resolving / rejecting this deferred.
+ *             If it's rejected, the transport will be closed.  If an Error is
+ *             supplied as a rejection value, it will be logged via |dumpn|.
+ *             If you do use |copyTo|, resolving is taken care of for you when
+ *             copying completes.
+ *   * copyTo: A helper function for getting your data out of the stream that
+ *             meets the stream handling requirements above, and has the
+ *             following signature:
+ *     @param  output nsIAsyncOutputStream
+ *             The stream to copy to.
+ *     @return Promise
+ *             The promise is resolved when copying completes or rejected if any
+ *             (unexpected) errors occur.
+ *             This object also emits "progress" events for each chunk that is
+ *             copied.  See stream-utils.js.
+ *
+ * - onClosed(reason) - called when the connection is closed. |reason| is
+ *   an optional nsresult or object, typically passed when the transport is
+ *   closed due to some error in a underlying stream.
+ *
+ * See ./packets.js and the Remote Debugging Protocol specification for more
+ * details on the format of these packets.
+ */
+function DebuggerTransport(input, output) {
+  this._input = input;
+  this._scriptableInput = new ScriptableInputStream(input);
+  this._output = output;
+
+  // The current incoming (possibly partial) header, which will determine which
+  // type of Packet |_incoming| below will become.
+  this._incomingHeader = "";
+  // The current incoming Packet object
+  this._incoming = null;
+  // A queue of outgoing Packet objects
+  this._outgoing = [];
 
-  DevToolsUtils.defineLazyGetter(this, "Pipe", () => {
-    return CC("@mozilla.org/pipe;1", "nsIPipe", "init");
-  });
+  this.hooks = null;
+  this.active = false;
+
+  this._incomingEnabled = true;
+  this._outgoingEnabled = true;
+
+  this.close = this.close.bind(this);
+}
+
+DebuggerTransport.prototype = {
+  /**
+   * Transmit an object as a JSON packet.
+   *
+   * This method returns immediately, without waiting for the entire
+   * packet to be transmitted, registering event handlers as needed to
+   * transmit the entire packet. Packets are transmitted in the order
+   * they are passed to this method.
+   */
+  send: function(object) {
+    const packet = new JSONPacket(this);
+    packet.object = object;
+    this._outgoing.push(packet);
+    this._flushOutgoing();
+  },
 
-  DevToolsUtils.defineLazyGetter(this, "ScriptableInputStream", () => {
-    return CC("@mozilla.org/scriptableinputstream;1",
-            "nsIScriptableInputStream", "init");
-  });
-
-  const PACKET_HEADER_MAX = 200;
+  /**
+   * Transmit streaming data via a bulk packet.
+   *
+   * This method initiates the bulk send process by queuing up the header data.
+   * The caller receives eventual access to a stream for writing.
+   *
+   * N.B.: Do *not* attempt to close the stream handed to you, as it will
+   * continue to be used by this transport afterwards.  Most users should
+   * instead use the provided |copyFrom| function instead.
+   *
+   * @param header Object
+   *        This is modeled after the format of JSON packets above, but does not
+   *        actually contain the data, but is instead just a routing header:
+   *          * actor:  Name of actor that will receive the packet
+   *          * type:   Name of actor's method that should be called on receipt
+   *          * length: Size of the data to be sent
+   * @return Promise
+   *         The promise will be resolved when you are allowed to write to the
+   *         stream with an object containing:
+   *           * stream:   This output stream should only be used directly if
+   *                       you can ensure that you will write exactly |length|
+   *                       bytes and will not close the stream when writing is
+   *                       complete
+   *           * done:     If you use the stream directly (instead of |copyFrom|
+   *                       below), you must signal completion by resolving /
+   *                       rejecting this deferred.  If it's rejected, the
+   *                       transport will be closed.  If an Error is supplied as
+   *                       a rejection value, it will be logged via |dumpn|.  If
+   *                       you do use |copyFrom|, resolving is taken care of for
+   *                       you when copying completes.
+   *           * copyFrom: A helper function for getting your data onto the
+   *                       stream that meets the stream handling requirements
+   *                       above, and has the following signature:
+   *             @param  input nsIAsyncInputStream
+   *                     The stream to copy from.
+   *             @return Promise
+   *                     The promise is resolved when copying completes or
+   *                     rejected if any (unexpected) errors occur.
+   *                     This object also emits "progress" events for each chunk
+   *                     that is copied.  See stream-utils.js.
+   */
+  startBulkSend: function(header) {
+    const packet = new BulkPacket(this);
+    packet.header = header;
+    this._outgoing.push(packet);
+    this._flushOutgoing();
+    return packet.streamReadyForWriting;
+  },
 
   /**
-   * An adapter that handles data transfers between the debugger client and
-   * server. It can work with both nsIPipe and nsIServerSocket transports so
-   * long as the properly created input and output streams are specified.
-   * (However, for intra-process connections, LocalDebuggerTransport, below,
-   * is more efficient than using an nsIPipe pair with DebuggerTransport.)
-   *
-   * @param input nsIAsyncInputStream
-   *        The input stream.
-   * @param output nsIAsyncOutputStream
-   *        The output stream.
-   *
-   * Given a DebuggerTransport instance dt:
-   * 1) Set dt.hooks to a packet handler object (described below).
-   * 2) Call dt.ready() to begin watching for input packets.
-   * 3) Call dt.send() / dt.startBulkSend() to send packets.
-   * 4) Call dt.close() to close the connection, and disengage from the event
-   *    loop.
-   *
-   * A packet handler is an object with the following methods:
-   *
-   * - onPacket(packet) - called when we have received a complete packet.
-   *   |packet| is the parsed form of the packet --- a JavaScript value, not
-   *   a JSON-syntax string.
-   *
-   * - onBulkPacket(packet) - called when we have switched to bulk packet
-   *   receiving mode. |packet| is an object containing:
-   *   * actor:  Name of actor that will receive the packet
-   *   * type:   Name of actor's method that should be called on receipt
-   *   * length: Size of the data to be read
-   *   * stream: This input stream should only be used directly if you can ensure
-   *             that you will read exactly |length| bytes and will not close the
-   *             stream when reading is complete
-   *   * done:   If you use the stream directly (instead of |copyTo| below), you
-   *             must signal completion by resolving / rejecting this deferred.
-   *             If it's rejected, the transport will be closed.  If an Error is
-   *             supplied as a rejection value, it will be logged via |dumpn|.
-   *             If you do use |copyTo|, resolving is taken care of for you when
-   *             copying completes.
-   *   * copyTo: A helper function for getting your data out of the stream that
-   *             meets the stream handling requirements above, and has the
-   *             following signature:
-   *     @param  output nsIAsyncOutputStream
-   *             The stream to copy to.
-   *     @return Promise
-   *             The promise is resolved when copying completes or rejected if any
-   *             (unexpected) errors occur.
-   *             This object also emits "progress" events for each chunk that is
-   *             copied.  See stream-utils.js.
-   *
-   * - onClosed(reason) - called when the connection is closed. |reason| is
-   *   an optional nsresult or object, typically passed when the transport is
-   *   closed due to some error in a underlying stream.
-   *
-   * See ./packets.js and the Remote Debugging Protocol specification for more
-   * details on the format of these packets.
+   * Close the transport.
+   * @param reason nsresult / object (optional)
+   *        The status code or error message that corresponds to the reason for
+   *        closing the transport (likely because a stream closed or failed).
    */
-  function DebuggerTransport(input, output) {
-    this._input = input;
-    this._scriptableInput = new ScriptableInputStream(input);
-    this._output = output;
+  close: function(reason) {
+    this.active = false;
+    this._input.close();
+    this._scriptableInput.close();
+    this._output.close();
+    this._destroyIncoming();
+    this._destroyAllOutgoing();
+    if (this.hooks) {
+      this.hooks.onClosed(reason);
+      this.hooks = null;
+    }
+    if (reason) {
+      dumpn("Transport closed: " + DevToolsUtils.safeErrorString(reason));
+    } else {
+      dumpn("Transport closed.");
+    }
+  },
 
-    // The current incoming (possibly partial) header, which will determine which
-    // type of Packet |_incoming| below will become.
-    this._incomingHeader = "";
-    // The current incoming Packet object
-    this._incoming = null;
-    // A queue of outgoing Packet objects
-    this._outgoing = [];
+  /**
+   * The currently outgoing packet (at the top of the queue).
+   */
+  get _currentOutgoing() {
+    return this._outgoing[0];
+  },
 
-    this.hooks = null;
-    this.active = false;
-
-    this._incomingEnabled = true;
-    this._outgoingEnabled = true;
-
-    this.close = this.close.bind(this);
-  }
+  /**
+   * Flush data to the outgoing stream.  Waits until the output stream notifies
+   * us that it is ready to be written to (via onOutputStreamReady).
+   */
+  _flushOutgoing: function() {
+    if (!this._outgoingEnabled || this._outgoing.length === 0) {
+      return;
+    }
 
-  DebuggerTransport.prototype = {
-    /**
-     * Transmit an object as a JSON packet.
-     *
-     * This method returns immediately, without waiting for the entire
-     * packet to be transmitted, registering event handlers as needed to
-     * transmit the entire packet. Packets are transmitted in the order
-     * they are passed to this method.
-     */
-    send: function(object) {
-      const packet = new JSONPacket(this);
-      packet.object = object;
-      this._outgoing.push(packet);
-      this._flushOutgoing();
-    },
+    // If the top of the packet queue has nothing more to send, remove it.
+    if (this._currentOutgoing.done) {
+      this._finishCurrentOutgoing();
+    }
+
+    if (this._outgoing.length > 0) {
+      const threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+      this._output.asyncWait(this, 0, 0, threadManager.currentThread);
+    }
+  },
+
+  /**
+   * Pause this transport's attempts to write to the output stream.  This is
+   * used when we've temporarily handed off our output stream for writing bulk
+   * data.
+   */
+  pauseOutgoing: function() {
+    this._outgoingEnabled = false;
+  },
 
-    /**
-     * Transmit streaming data via a bulk packet.
-     *
-     * This method initiates the bulk send process by queuing up the header data.
-     * The caller receives eventual access to a stream for writing.
-     *
-     * N.B.: Do *not* attempt to close the stream handed to you, as it will
-     * continue to be used by this transport afterwards.  Most users should
-     * instead use the provided |copyFrom| function instead.
-     *
-     * @param header Object
-     *        This is modeled after the format of JSON packets above, but does not
-     *        actually contain the data, but is instead just a routing header:
-     *          * actor:  Name of actor that will receive the packet
-     *          * type:   Name of actor's method that should be called on receipt
-     *          * length: Size of the data to be sent
-     * @return Promise
-     *         The promise will be resolved when you are allowed to write to the
-     *         stream with an object containing:
-     *           * stream:   This output stream should only be used directly if
-     *                       you can ensure that you will write exactly |length|
-     *                       bytes and will not close the stream when writing is
-     *                       complete
-     *           * done:     If you use the stream directly (instead of |copyFrom|
-     *                       below), you must signal completion by resolving /
-     *                       rejecting this deferred.  If it's rejected, the
-     *                       transport will be closed.  If an Error is supplied as
-     *                       a rejection value, it will be logged via |dumpn|.  If
-     *                       you do use |copyFrom|, resolving is taken care of for
-     *                       you when copying completes.
-     *           * copyFrom: A helper function for getting your data onto the
-     *                       stream that meets the stream handling requirements
-     *                       above, and has the following signature:
-     *             @param  input nsIAsyncInputStream
-     *                     The stream to copy from.
-     *             @return Promise
-     *                     The promise is resolved when copying completes or
-     *                     rejected if any (unexpected) errors occur.
-     *                     This object also emits "progress" events for each chunk
-     *                     that is copied.  See stream-utils.js.
-     */
-    startBulkSend: function(header) {
-      const packet = new BulkPacket(this);
-      packet.header = header;
-      this._outgoing.push(packet);
-      this._flushOutgoing();
-      return packet.streamReadyForWriting;
-    },
+  /**
+   * Resume this transport's attempts to write to the output stream.
+   */
+  resumeOutgoing: function() {
+    this._outgoingEnabled = true;
+    this._flushOutgoing();
+  },
 
-    /**
-     * Close the transport.
-     * @param reason nsresult / object (optional)
-     *        The status code or error message that corresponds to the reason for
-     *        closing the transport (likely because a stream closed or failed).
-     */
-    close: function(reason) {
-      this.active = false;
-      this._input.close();
-      this._scriptableInput.close();
-      this._output.close();
-      this._destroyIncoming();
-      this._destroyAllOutgoing();
-      if (this.hooks) {
-        this.hooks.onClosed(reason);
-        this.hooks = null;
-      }
-      if (reason) {
-        dumpn("Transport closed: " + DevToolsUtils.safeErrorString(reason));
-      } else {
-        dumpn("Transport closed.");
-      }
-    },
+  // nsIOutputStreamCallback
+  /**
+   * This is called when the output stream is ready for more data to be written.
+   * The current outgoing packet will attempt to write some amount of data, but
+   * may not complete.
+   */
+  onOutputStreamReady: DevToolsUtils.makeInfallible(function(stream) {
+    if (!this._outgoingEnabled || this._outgoing.length === 0) {
+      return;
+    }
 
-    /**
-     * The currently outgoing packet (at the top of the queue).
-     */
-    get _currentOutgoing() {
-      return this._outgoing[0];
-    },
-
-    /**
-     * Flush data to the outgoing stream.  Waits until the output stream notifies
-     * us that it is ready to be written to (via onOutputStreamReady).
-     */
-    _flushOutgoing: function() {
-      if (!this._outgoingEnabled || this._outgoing.length === 0) {
+    try {
+      this._currentOutgoing.write(stream);
+    } catch (e) {
+      if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+        this.close(e.result);
         return;
       }
+      throw e;
+    }
 
-      // If the top of the packet queue has nothing more to send, remove it.
-      if (this._currentOutgoing.done) {
-        this._finishCurrentOutgoing();
-      }
+    this._flushOutgoing();
+  }, "DebuggerTransport.prototype.onOutputStreamReady"),
+
+  /**
+   * Remove the current outgoing packet from the queue upon completion.
+   */
+  _finishCurrentOutgoing: function() {
+    if (this._currentOutgoing) {
+      this._currentOutgoing.destroy();
+      this._outgoing.shift();
+    }
+  },
 
-      if (this._outgoing.length > 0) {
-        const threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
-        this._output.asyncWait(this, 0, 0, threadManager.currentThread);
-      }
-    },
+  /**
+   * Clear the entire outgoing queue.
+   */
+  _destroyAllOutgoing: function() {
+    for (const packet of this._outgoing) {
+      packet.destroy();
+    }
+    this._outgoing = [];
+  },
 
-    /**
-     * Pause this transport's attempts to write to the output stream.  This is
-     * used when we've temporarily handed off our output stream for writing bulk
-     * data.
-     */
-    pauseOutgoing: function() {
-      this._outgoingEnabled = false;
-    },
+  /**
+   * Initialize the input stream for reading. Once this method has been called,
+   * we watch for packets on the input stream, and pass them to the appropriate
+   * handlers via this.hooks.
+   */
+  ready: function() {
+    this.active = true;
+    this._waitForIncoming();
+  },
 
-    /**
-     * Resume this transport's attempts to write to the output stream.
-     */
-    resumeOutgoing: function() {
-      this._outgoingEnabled = true;
-      this._flushOutgoing();
-    },
+  /**
+   * Asks the input stream to notify us (via onInputStreamReady) when it is
+   * ready for reading.
+   */
+  _waitForIncoming: function() {
+    if (this._incomingEnabled) {
+      const threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+      this._input.asyncWait(this, 0, 0, threadManager.currentThread);
+    }
+  },
+
+  /**
+   * Pause this transport's attempts to read from the input stream.  This is
+   * used when we've temporarily handed off our input stream for reading bulk
+   * data.
+   */
+  pauseIncoming: function() {
+    this._incomingEnabled = false;
+  },
 
-    // nsIOutputStreamCallback
-    /**
-     * This is called when the output stream is ready for more data to be written.
-     * The current outgoing packet will attempt to write some amount of data, but
-     * may not complete.
-     */
-    onOutputStreamReady: DevToolsUtils.makeInfallible(function(stream) {
-      if (!this._outgoingEnabled || this._outgoing.length === 0) {
-        return;
+  /**
+   * Resume this transport's attempts to read from the input stream.
+   */
+  resumeIncoming: function() {
+    this._incomingEnabled = true;
+    this._flushIncoming();
+    this._waitForIncoming();
+  },
+
+  // nsIInputStreamCallback
+  /**
+   * Called when the stream is either readable or closed.
+   */
+  onInputStreamReady: DevToolsUtils.makeInfallible(function(stream) {
+    try {
+      while (stream.available() && this._incomingEnabled &&
+             this._processIncoming(stream, stream.available())) {
+         // Loop until there is nothing more to process
       }
-
-      try {
-        this._currentOutgoing.write(stream);
-      } catch (e) {
-        if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
-          this.close(e.result);
-          return;
-        }
+      this._waitForIncoming();
+    } catch (e) {
+      if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+        this.close(e.result);
+      } else {
         throw e;
       }
-
-      this._flushOutgoing();
-    }, "DebuggerTransport.prototype.onOutputStreamReady"),
-
-    /**
-     * Remove the current outgoing packet from the queue upon completion.
-     */
-    _finishCurrentOutgoing: function() {
-      if (this._currentOutgoing) {
-        this._currentOutgoing.destroy();
-        this._outgoing.shift();
-      }
-    },
-
-    /**
-     * Clear the entire outgoing queue.
-     */
-    _destroyAllOutgoing: function() {
-      for (const packet of this._outgoing) {
-        packet.destroy();
-      }
-      this._outgoing = [];
-    },
-
-    /**
-     * Initialize the input stream for reading. Once this method has been called,
-     * we watch for packets on the input stream, and pass them to the appropriate
-     * handlers via this.hooks.
-     */
-    ready: function() {
-      this.active = true;
-      this._waitForIncoming();
-    },
-
-    /**
-     * Asks the input stream to notify us (via onInputStreamReady) when it is
-     * ready for reading.
-     */
-    _waitForIncoming: function() {
-      if (this._incomingEnabled) {
-        const threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
-        this._input.asyncWait(this, 0, 0, threadManager.currentThread);
-      }
-    },
-
-    /**
-     * Pause this transport's attempts to read from the input stream.  This is
-     * used when we've temporarily handed off our input stream for reading bulk
-     * data.
-     */
-    pauseIncoming: function() {
-      this._incomingEnabled = false;
-    },
+    }
+  }, "DebuggerTransport.prototype.onInputStreamReady"),
 
-    /**
-     * Resume this transport's attempts to read from the input stream.
-     */
-    resumeIncoming: function() {
-      this._incomingEnabled = true;
-      this._flushIncoming();
-      this._waitForIncoming();
-    },
-
-    // nsIInputStreamCallback
-    /**
-     * Called when the stream is either readable or closed.
-     */
-    onInputStreamReady: DevToolsUtils.makeInfallible(function(stream) {
-      try {
-        while (stream.available() && this._incomingEnabled &&
-               this._processIncoming(stream, stream.available())) {
-           // Loop until there is nothing more to process
-        }
-        this._waitForIncoming();
-      } catch (e) {
-        if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
-          this.close(e.result);
-        } else {
-          throw e;
-        }
-      }
-    }, "DebuggerTransport.prototype.onInputStreamReady"),
+  /**
+   * Process the incoming data.  Will create a new currently incoming Packet if
+   * needed.  Tells the incoming Packet to read as much data as it can, but
+   * reading may not complete.  The Packet signals that its data is ready for
+   * delivery by calling one of this transport's _on*Ready methods (see
+   * ./packets.js and the _on*Ready methods below).
+   * @return boolean
+   *         Whether incoming stream processing should continue for any
+   *         remaining data.
+   */
+  _processIncoming: function(stream, count) {
+    dumpv("Data available: " + count);
 
-    /**
-     * Process the incoming data.  Will create a new currently incoming Packet if
-     * needed.  Tells the incoming Packet to read as much data as it can, but
-     * reading may not complete.  The Packet signals that its data is ready for
-     * delivery by calling one of this transport's _on*Ready methods (see
-     * ./packets.js and the _on*Ready methods below).
-     * @return boolean
-     *         Whether incoming stream processing should continue for any
-     *         remaining data.
-     */
-    _processIncoming: function(stream, count) {
-      dumpv("Data available: " + count);
+    if (!count) {
+      dumpv("Nothing to read, skipping");
+      return false;
+    }
 
-      if (!count) {
-        dumpv("Nothing to read, skipping");
-        return false;
-      }
-
-      try {
-        if (!this._incoming) {
-          dumpv("Creating a new packet from incoming");
+    try {
+      if (!this._incoming) {
+        dumpv("Creating a new packet from incoming");
 
-          if (!this._readHeader(stream)) {
-            // Not enough data to read packet type
-            return false;
-          }
-
-          // Attempt to create a new Packet by trying to parse each possible
-          // header pattern.
-          this._incoming = Packet.fromHeader(this._incomingHeader, this);
-          if (!this._incoming) {
-            throw new Error("No packet types for header: " +
-                          this._incomingHeader);
-          }
+        if (!this._readHeader(stream)) {
+          // Not enough data to read packet type
+          return false;
         }
 
-        if (!this._incoming.done) {
-          // We have an incomplete packet, keep reading it.
-          dumpv("Existing packet incomplete, keep reading");
-          this._incoming.read(stream, this._scriptableInput);
+        // Attempt to create a new Packet by trying to parse each possible
+        // header pattern.
+        this._incoming = Packet.fromHeader(this._incomingHeader, this);
+        if (!this._incoming) {
+          throw new Error("No packet types for header: " +
+                        this._incomingHeader);
         }
-      } catch (e) {
-        const msg = "Error reading incoming packet: (" + e + " - " + e.stack + ")";
-        dumpn(msg);
-
-        // Now in an invalid state, shut down the transport.
-        this.close();
-        return false;
       }
 
       if (!this._incoming.done) {
-        // Still not complete, we'll wait for more data.
-        dumpv("Packet not done, wait for more");
-        return true;
+        // We have an incomplete packet, keep reading it.
+        dumpv("Existing packet incomplete, keep reading");
+        this._incoming.read(stream, this._scriptableInput);
       }
+    } catch (e) {
+      const msg = "Error reading incoming packet: (" + e + " - " + e.stack + ")";
+      dumpn(msg);
 
-      // Ready for next packet
-      this._flushIncoming();
+      // Now in an invalid state, shut down the transport.
+      this.close();
+      return false;
+    }
+
+    if (!this._incoming.done) {
+      // Still not complete, we'll wait for more data.
+      dumpv("Packet not done, wait for more");
       return true;
-    },
+    }
 
-    /**
-     * Read as far as we can into the incoming data, attempting to build up a
-     * complete packet header (which terminates with ":").  We'll only read up to
-     * PACKET_HEADER_MAX characters.
-     * @return boolean
-     *         True if we now have a complete header.
-     */
-    _readHeader: function() {
-      const amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
-      this._incomingHeader +=
-      StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead);
+    // Ready for next packet
+    this._flushIncoming();
+    return true;
+  },
+
+  /**
+   * Read as far as we can into the incoming data, attempting to build up a
+   * complete packet header (which terminates with ":").  We'll only read up to
+   * PACKET_HEADER_MAX characters.
+   * @return boolean
+   *         True if we now have a complete header.
+   */
+  _readHeader: function() {
+    const amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
+    this._incomingHeader +=
+    StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead);
+    if (flags.wantVerbose) {
+      dumpv("Header read: " + this._incomingHeader);
+    }
+
+    if (this._incomingHeader.endsWith(":")) {
       if (flags.wantVerbose) {
-        dumpv("Header read: " + this._incomingHeader);
+        dumpv("Found packet header successfully: " + this._incomingHeader);
       }
+      return true;
+    }
+
+    if (this._incomingHeader.length >= PACKET_HEADER_MAX) {
+      throw new Error("Failed to parse packet header!");
+    }
+
+    // Not enough data yet.
+    return false;
+  },
+
+  /**
+   * If the incoming packet is done, log it as needed and clear the buffer.
+   */
+  _flushIncoming: function() {
+    if (!this._incoming.done) {
+      return;
+    }
+    if (flags.wantLogging) {
+      dumpn("Got: " + this._incoming);
+    }
+    this._destroyIncoming();
+  },
 
-      if (this._incomingHeader.endsWith(":")) {
-        if (flags.wantVerbose) {
-          dumpv("Found packet header successfully: " + this._incomingHeader);
-        }
-        return true;
+  /**
+   * Handler triggered by an incoming JSONPacket completing it's |read| method.
+   * Delivers the packet to this.hooks.onPacket.
+   */
+  _onJSONObjectReady: function(object) {
+    DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+    // Ensure the transport is still alive by the time this runs.
+      if (this.active) {
+        this.hooks.onPacket(object);
       }
+    }, "DebuggerTransport instance's this.hooks.onPacket"));
+  },
+
+  /**
+   * Handler triggered by an incoming BulkPacket entering the |read| phase for
+   * the stream portion of the packet.  Delivers info about the incoming
+   * streaming data to this.hooks.onBulkPacket.  See the main comment on the
+   * transport at the top of this file for more details.
+   */
+  _onBulkReadReady: function(...args) {
+    DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+    // Ensure the transport is still alive by the time this runs.
+      if (this.active) {
+        this.hooks.onBulkPacket(...args);
+      }
+    }, "DebuggerTransport instance's this.hooks.onBulkPacket"));
+  },
+
+  /**
+   * Remove all handlers and references related to the current incoming packet,
+   * either because it is now complete or because the transport is closing.
+   */
+  _destroyIncoming: function() {
+    if (this._incoming) {
+      this._incoming.destroy();
+    }
+    this._incomingHeader = "";
+    this._incoming = null;
+  }
+
+};
+
+exports.DebuggerTransport = DebuggerTransport;
 
-      if (this._incomingHeader.length >= PACKET_HEADER_MAX) {
-        throw new Error("Failed to parse packet header!");
-      }
+/**
+ * An adapter that handles data transfers between the debugger client and
+ * server when they both run in the same process. It presents the same API as
+ * DebuggerTransport, but instead of transmitting serialized messages across a
+ * connection it merely calls the packet dispatcher of the other side.
+ *
+ * @param other LocalDebuggerTransport
+ *        The other endpoint for this debugger connection.
+ *
+ * @see DebuggerTransport
+ */
+function LocalDebuggerTransport(other) {
+  this.other = other;
+  this.hooks = null;
+
+  // A packet number, shared between this and this.other. This isn't used by the
+  // protocol at all, but it makes the packet traces a lot easier to follow.
+  this._serial = this.other ? this.other._serial : { count: 0 };
+  this.close = this.close.bind(this);
+}
 
-      // Not enough data yet.
-      return false;
-    },
+LocalDebuggerTransport.prototype = {
+  /**
+   * Transmit a message by directly calling the onPacket handler of the other
+   * endpoint.
+   */
+  send: function(packet) {
+    const serial = this._serial.count++;
+    if (flags.wantLogging) {
+      // Check 'from' first, as 'echo' packets have both.
+      if (packet.from) {
+        dumpn("Packet " + serial + " sent from " + uneval(packet.from));
+      } else if (packet.to) {
+        dumpn("Packet " + serial + " sent to " + uneval(packet.to));
+      }
+    }
+    this._deepFreeze(packet);
+    const other = this.other;
+    if (other) {
+      DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+        // Avoid the cost of JSON.stringify() when logging is disabled.
+        if (flags.wantLogging) {
+          dumpn("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
+        }
+        if (other.hooks) {
+          other.hooks.onPacket(packet);
+        }
+      }, "LocalDebuggerTransport instance's this.other.hooks.onPacket"));
+    }
+  },
 
-    /**
-     * If the incoming packet is done, log it as needed and clear the buffer.
-     */
-    _flushIncoming: function() {
-      if (!this._incoming.done) {
+  /**
+   * Send a streaming bulk packet directly to the onBulkPacket handler of the
+   * other endpoint.
+   *
+   * This case is much simpler than the full DebuggerTransport, since there is
+   * no primary stream we have to worry about managing while we hand it off to
+   * others temporarily.  Instead, we can just make a single use pipe and be
+   * done with it.
+   */
+  startBulkSend: function({actor, type, length}) {
+    const serial = this._serial.count++;
+
+    dumpn("Sent bulk packet " + serial + " for actor " + actor);
+    if (!this.other) {
+      const error = new Error("startBulkSend: other side of transport missing");
+      return promise.reject(error);
+    }
+
+    const pipe = new Pipe(true, true, 0, 0, null);
+
+    DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+      dumpn("Received bulk packet " + serial);
+      if (!this.other.hooks) {
         return;
       }
-      if (flags.wantLogging) {
-        dumpn("Got: " + this._incoming);
-      }
-      this._destroyIncoming();
-    },
 
-    /**
-     * Handler triggered by an incoming JSONPacket completing it's |read| method.
-     * Delivers the packet to this.hooks.onPacket.
-     */
-    _onJSONObjectReady: function(object) {
-      DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
-      // Ensure the transport is still alive by the time this runs.
-        if (this.active) {
-          this.hooks.onPacket(object);
-        }
-      }, "DebuggerTransport instance's this.hooks.onPacket"));
-    },
+      // Receiver
+      const deferred = defer();
+      const packet = {
+        actor: actor,
+        type: type,
+        length: length,
+        copyTo: (output) => {
+          const copying =
+          StreamUtils.copyStream(pipe.inputStream, output, length);
+          deferred.resolve(copying);
+          return copying;
+        },
+        stream: pipe.inputStream,
+        done: deferred
+      };
+
+      this.other.hooks.onBulkPacket(packet);
+
+      // Await the result of reading from the stream
+      deferred.promise.then(() => pipe.inputStream.close(), this.close);
+    }, "LocalDebuggerTransport instance's this.other.hooks.onBulkPacket"));
+
+    // Sender
+    const sendDeferred = defer();
+
+    // The remote transport is not capable of resolving immediately here, so we
+    // shouldn't be able to either.
+    DevToolsUtils.executeSoon(() => {
+      const copyDeferred = defer();
 
-    /**
-     * Handler triggered by an incoming BulkPacket entering the |read| phase for
-     * the stream portion of the packet.  Delivers info about the incoming
-     * streaming data to this.hooks.onBulkPacket.  See the main comment on the
-     * transport at the top of this file for more details.
-     */
-    _onBulkReadReady: function(...args) {
-      DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
-      // Ensure the transport is still alive by the time this runs.
-        if (this.active) {
-          this.hooks.onBulkPacket(...args);
-        }
-      }, "DebuggerTransport instance's this.hooks.onBulkPacket"));
-    },
+      sendDeferred.resolve({
+        copyFrom: (input) => {
+          const copying =
+          StreamUtils.copyStream(input, pipe.outputStream, length);
+          copyDeferred.resolve(copying);
+          return copying;
+        },
+        stream: pipe.outputStream,
+        done: copyDeferred
+      });
+
+      // Await the result of writing to the stream
+      copyDeferred.promise.then(() => pipe.outputStream.close(), this.close);
+    });
+
+    return sendDeferred.promise;
+  },
 
-    /**
-     * Remove all handlers and references related to the current incoming packet,
-     * either because it is now complete or because the transport is closing.
-     */
-    _destroyIncoming: function() {
-      if (this._incoming) {
-        this._incoming.destroy();
+  /**
+   * Close the transport.
+   */
+  close: function() {
+    if (this.other) {
+      // Remove the reference to the other endpoint before calling close(), to
+      // avoid infinite recursion.
+      const other = this.other;
+      this.other = null;
+      other.close();
+    }
+    if (this.hooks) {
+      try {
+        this.hooks.onClosed();
+      } catch (ex) {
+        console.error(ex);
       }
-      this._incomingHeader = "";
-      this._incoming = null;
+      this.hooks = null;
     }
+  },
 
-  };
-
-  exports.DebuggerTransport = DebuggerTransport;
+  /**
+   * An empty method for emulating the DebuggerTransport API.
+   */
+  ready: function() {},
 
   /**
-   * An adapter that handles data transfers between the debugger client and
-   * server when they both run in the same process. It presents the same API as
-   * DebuggerTransport, but instead of transmitting serialized messages across a
-   * connection it merely calls the packet dispatcher of the other side.
-   *
-   * @param other LocalDebuggerTransport
-   *        The other endpoint for this debugger connection.
-   *
-   * @see DebuggerTransport
+   * Helper function that makes an object fully immutable.
    */
-  function LocalDebuggerTransport(other) {
-    this.other = other;
-    this.hooks = null;
+  _deepFreeze: function(object) {
+    Object.freeze(object);
+    for (const prop in object) {
+      // Freeze the properties that are objects, not on the prototype, and not
+      // already frozen. Note that this might leave an unfrozen reference
+      // somewhere in the object if there is an already frozen object containing
+      // an unfrozen object.
+      if (object.hasOwnProperty(prop) && typeof object === "object" &&
+          !Object.isFrozen(object)) {
+        this._deepFreeze(object[prop]);
+      }
+    }
+  }
+};
+
+exports.LocalDebuggerTransport = LocalDebuggerTransport;
 
-    // A packet number, shared between this and this.other. This isn't used by the
-    // protocol at all, but it makes the packet traces a lot easier to follow.
-    this._serial = this.other ? this.other._serial : { count: 0 };
-    this.close = this.close.bind(this);
-  }
+/**
+ * A transport for the debugging protocol that uses nsIMessageManagers to
+ * exchange packets with servers running in child processes.
+ *
+ * In the parent process, |mm| should be the nsIMessageSender for the
+ * child process. In a child process, |mm| should be the child process
+ * message manager, which sends packets to the parent.
+ *
+ * |prefix| is a string included in the message names, to distinguish
+ * multiple servers running in the same child process.
+ *
+ * This transport exchanges messages named 'debug:<prefix>:packet', where
+ * <prefix> is |prefix|, whose data is the protocol packet.
+ */
+function ChildDebuggerTransport(mm, prefix) {
+  this._mm = mm;
+  this._messageName = "debug:" + prefix + ":packet";
+}
 
-  LocalDebuggerTransport.prototype = {
-    /**
-     * Transmit a message by directly calling the onPacket handler of the other
-     * endpoint.
-     */
-    send: function(packet) {
-      const serial = this._serial.count++;
-      if (flags.wantLogging) {
-        // Check 'from' first, as 'echo' packets have both.
-        if (packet.from) {
-          dumpn("Packet " + serial + " sent from " + uneval(packet.from));
-        } else if (packet.to) {
-          dumpn("Packet " + serial + " sent to " + uneval(packet.to));
-        }
+/*
+ * To avoid confusion, we use 'message' to mean something that
+ * nsIMessageSender conveys, and 'packet' to mean a remote debugging
+ * protocol packet.
+ */
+ChildDebuggerTransport.prototype = {
+  constructor: ChildDebuggerTransport,
+
+  hooks: null,
+
+  _addListener() {
+    this._mm.addMessageListener(this._messageName, this);
+  },
+
+  _removeListener() {
+    try {
+      this._mm.removeMessageListener(this._messageName, this);
+    } catch (e) {
+      if (e.result != Cr.NS_ERROR_NULL_POINTER) {
+        throw e;
       }
-      this._deepFreeze(packet);
-      const other = this.other;
-      if (other) {
-        DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
-          // Avoid the cost of JSON.stringify() when logging is disabled.
-          if (flags.wantLogging) {
-            dumpn("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
-          }
-          if (other.hooks) {
-            other.hooks.onPacket(packet);
-          }
-        }, "LocalDebuggerTransport instance's this.other.hooks.onPacket"));
+      // In some cases, especially when using messageManagers in non-e10s mode, we reach
+      // this point with a dead messageManager which only throws errors but does not
+      // seem to indicate in any other way that it is dead.
+    }
+  },
+
+  ready: function() {
+    this._addListener();
+  },
+
+  close: function() {
+    this._removeListener();
+    this.hooks.onClosed();
+  },
+
+  receiveMessage: function({data}) {
+    this.hooks.onPacket(data);
+  },
+
+  /**
+   * Helper method to ensure a given `object` can be sent across message manager
+   * without being serialized to JSON.
+   * See https://searchfox.org/mozilla-central/rev/6bfadf95b4a6aaa8bb3b2a166d6c3545983e179a/dom/base/nsFrameMessageManager.cpp#458-469
+   */
+  _canBeSerialized: function(object) {
+    try {
+      const holder = new StructuredCloneHolder(object);
+      holder.deserialize(this);
+    } catch (e) {
+      return false;
+    }
+    return true;
+  },
+
+  pathToUnserializable: function(object) {
+    for (const key in object) {
+      const value = object[key];
+      if (!this._canBeSerialized(value)) {
+        if (typeof value == "object") {
+          return [key].concat(this.pathToUnserializable(value));
+        }
+        return [key];
       }
-    },
+    }
+    return [];
+  },
+
+  send: function(packet) {
+    if (flags.testing && !this._canBeSerialized(packet)) {
+      const attributes = this.pathToUnserializable(packet);
+      let msg = "Following packet can't be serialized: " + JSON.stringify(packet);
+      msg += "\nBecause of attributes: " + attributes.join(", ") + "\n";
+      msg += "Did you pass a function or an XPCOM object in it?";
+      throw new Error(msg);
+    }
+    try {
+      this._mm.sendAsyncMessage(this._messageName, packet);
+    } catch (e) {
+      if (e.result != Cr.NS_ERROR_NULL_POINTER) {
+        throw e;
+      }
+      // In some cases, especially when using messageManagers in non-e10s mode, we reach
+      // this point with a dead messageManager which only throws errors but does not
+      // seem to indicate in any other way that it is dead.
+    }
+  },
 
+  startBulkSend: function() {
+    throw new Error("Can't send bulk data to child processes.");
+  },
+
+  swapBrowser(mm) {
+    this._removeListener();
+    this._mm = mm;
+    this._addListener();
+  },
+};
+
+exports.ChildDebuggerTransport = ChildDebuggerTransport;
+
+// WorkerDebuggerTransport is defined differently depending on whether we are
+// on the main thread or a worker thread. In the former case, we are required
+// by the devtools loader, and isWorker will be false. Otherwise, we are
+// required by the worker loader, and isWorker will be true.
+//
+// Each worker debugger supports only a single connection to the main thread.
+// However, its theoretically possible for multiple servers to connect to the
+// same worker. Consequently, each transport has a connection id, to allow
+// messages from multiple connections to be multiplexed on a single channel.
+
+if (!this.isWorker) {
+  // Main thread
+  (function() {
     /**
-     * Send a streaming bulk packet directly to the onBulkPacket handler of the
-     * other endpoint.
-     *
-     * This case is much simpler than the full DebuggerTransport, since there is
-     * no primary stream we have to worry about managing while we hand it off to
-     * others temporarily.  Instead, we can just make a single use pipe and be
-     * done with it.
+     * A transport that uses a WorkerDebugger to send packets from the main
+     * thread to a worker thread.
      */
-    startBulkSend: function({actor, type, length}) {
-      const serial = this._serial.count++;
+    function WorkerDebuggerTransport(dbg, id) {
+      this._dbg = dbg;
+      this._id = id;
+      this.onMessage = this._onMessage.bind(this);
+    }
+
+    WorkerDebuggerTransport.prototype = {
+      constructor: WorkerDebuggerTransport,
+
+      ready: function() {
+        this._dbg.addListener(this);
+      },
 
-      dumpn("Sent bulk packet " + serial + " for actor " + actor);
-      if (!this.other) {
-        const error = new Error("startBulkSend: other side of transport missing");
-        return promise.reject(error);
-      }
+      close: function() {
+        this._dbg.removeListener(this);
+        if (this.hooks) {
+          this.hooks.onClosed();
+        }
+      },
 
-      const pipe = new Pipe(true, true, 0, 0, null);
+      send: function(packet) {
+        this._dbg.postMessage(JSON.stringify({
+          type: "message",
+          id: this._id,
+          message: packet
+        }));
+      },
 
-      DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
-        dumpn("Received bulk packet " + serial);
-        if (!this.other.hooks) {
+      startBulkSend: function() {
+        throw new Error("Can't send bulk data from worker threads!");
+      },
+
+      _onMessage: function(message) {
+        const packet = JSON.parse(message);
+        if (packet.type !== "message" || packet.id !== this._id) {
           return;
         }
 
-        // Receiver
-        const deferred = defer();
-        const packet = {
-          actor: actor,
-          type: type,
-          length: length,
-          copyTo: (output) => {
-            const copying =
-            StreamUtils.copyStream(pipe.inputStream, output, length);
-            deferred.resolve(copying);
-            return copying;
-          },
-          stream: pipe.inputStream,
-          done: deferred
-        };
-
-        this.other.hooks.onBulkPacket(packet);
-
-        // Await the result of reading from the stream
-        deferred.promise.then(() => pipe.inputStream.close(), this.close);
-      }, "LocalDebuggerTransport instance's this.other.hooks.onBulkPacket"));
-
-      // Sender
-      const sendDeferred = defer();
-
-      // The remote transport is not capable of resolving immediately here, so we
-      // shouldn't be able to either.
-      DevToolsUtils.executeSoon(() => {
-        const copyDeferred = defer();
-
-        sendDeferred.resolve({
-          copyFrom: (input) => {
-            const copying =
-            StreamUtils.copyStream(input, pipe.outputStream, length);
-            copyDeferred.resolve(copying);
-            return copying;
-          },
-          stream: pipe.outputStream,
-          done: copyDeferred
-        });
-
-        // Await the result of writing to the stream
-        copyDeferred.promise.then(() => pipe.outputStream.close(), this.close);
-      });
-
-      return sendDeferred.promise;
-    },
-
-    /**
-     * Close the transport.
-     */
-    close: function() {
-      if (this.other) {
-        // Remove the reference to the other endpoint before calling close(), to
-        // avoid infinite recursion.
-        const other = this.other;
-        this.other = null;
-        other.close();
-      }
-      if (this.hooks) {
-        try {
-          this.hooks.onClosed();
-        } catch (ex) {
-          console.error(ex);
-        }
-        this.hooks = null;
-      }
-    },
-
-    /**
-     * An empty method for emulating the DebuggerTransport API.
-     */
-    ready: function() {},
-
-    /**
-     * Helper function that makes an object fully immutable.
-     */
-    _deepFreeze: function(object) {
-      Object.freeze(object);
-      for (const prop in object) {
-        // Freeze the properties that are objects, not on the prototype, and not
-        // already frozen. Note that this might leave an unfrozen reference
-        // somewhere in the object if there is an already frozen object containing
-        // an unfrozen object.
-        if (object.hasOwnProperty(prop) && typeof object === "object" &&
-            !Object.isFrozen(object)) {
-          this._deepFreeze(object[prop]);
+        if (this.hooks) {
+          this.hooks.onPacket(packet.message);
         }
       }
-    }
-  };
-
-  exports.LocalDebuggerTransport = LocalDebuggerTransport;
+    };
 
-  /**
-   * A transport for the debugging protocol that uses nsIMessageManagers to
-   * exchange packets with servers running in child processes.
-   *
-   * In the parent process, |mm| should be the nsIMessageSender for the
-   * child process. In a child process, |mm| should be the child process
-   * message manager, which sends packets to the parent.
-   *
-   * |prefix| is a string included in the message names, to distinguish
-   * multiple servers running in the same child process.
-   *
-   * This transport exchanges messages named 'debug:<prefix>:packet', where
-   * <prefix> is |prefix|, whose data is the protocol packet.
-   */
-  function ChildDebuggerTransport(mm, prefix) {
-    this._mm = mm;
-    this._messageName = "debug:" + prefix + ":packet";
-  }
+    exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
+  }).call(this);
+} else {
+  // Worker thread
+  (function() {
+    /**
+     * A transport that uses a WorkerDebuggerGlobalScope to send packets from a
+     * worker thread to the main thread.
+     */
+    function WorkerDebuggerTransport(scope, id) {
+      this._scope = scope;
+      this._id = id;
+      this._onMessage = this._onMessage.bind(this);
+    }
 
-  /*
-   * To avoid confusion, we use 'message' to mean something that
-   * nsIMessageSender conveys, and 'packet' to mean a remote debugging
-   * protocol packet.
-   */
-  ChildDebuggerTransport.prototype = {
-    constructor: ChildDebuggerTransport,
+    WorkerDebuggerTransport.prototype = {
+      constructor: WorkerDebuggerTransport,
 
-    hooks: null,
-
-    _addListener() {
-      this._mm.addMessageListener(this._messageName, this);
-    },
+      ready: function() {
+        this._scope.addEventListener("message", this._onMessage);
+      },
 
-    _removeListener() {
-      try {
-        this._mm.removeMessageListener(this._messageName, this);
-      } catch (e) {
-        if (e.result != Cr.NS_ERROR_NULL_POINTER) {
-          throw e;
+      close: function() {
+        this._scope.removeEventListener("message", this._onMessage);
+        if (this.hooks) {
+          this.hooks.onClosed();
         }
-        // In some cases, especially when using messageManagers in non-e10s mode, we reach
-        // this point with a dead messageManager which only throws errors but does not
-        // seem to indicate in any other way that it is dead.
-      }
-    },
-
-    ready: function() {
-      this._addListener();
-    },
-
-    close: function() {
-      this._removeListener();
-      this.hooks.onClosed();
-    },
+      },
 
-    receiveMessage: function({data}) {
-      this.hooks.onPacket(data);
-    },
+      send: function(packet) {
+        this._scope.postMessage(JSON.stringify({
+          type: "message",
+          id: this._id,
+          message: packet
+        }));
+      },
 
-    /**
-     * Helper method to ensure a given `object` can be sent across message manager
-     * without being serialized to JSON.
-     * See https://searchfox.org/mozilla-central/rev/6bfadf95b4a6aaa8bb3b2a166d6c3545983e179a/dom/base/nsFrameMessageManager.cpp#458-469
-     */
-    _canBeSerialized: function(object) {
-      try {
-        const holder = new StructuredCloneHolder(object);
-        holder.deserialize(this);
-      } catch (e) {
-        return false;
-      }
-      return true;
-    },
+      startBulkSend: function() {
+        throw new Error("Can't send bulk data from worker threads!");
+      },
 
-    pathToUnserializable: function(object) {
-      for (const key in object) {
-        const value = object[key];
-        if (!this._canBeSerialized(value)) {
-          if (typeof value == "object") {
-            return [key].concat(this.pathToUnserializable(value));
-          }
-          return [key];
+      _onMessage: function(event) {
+        const packet = JSON.parse(event.data);
+        if (packet.type !== "message" || packet.id !== this._id) {
+          return;
+        }
+
+        if (this.hooks) {
+          this.hooks.onPacket(packet.message);
         }
       }
-      return [];
-    },
-
-    send: function(packet) {
-      if (flags.testing && !this._canBeSerialized(packet)) {
-        const attributes = this.pathToUnserializable(packet);
-        let msg = "Following packet can't be serialized: " + JSON.stringify(packet);
-        msg += "\nBecause of attributes: " + attributes.join(", ") + "\n";
-        msg += "Did you pass a function or an XPCOM object in it?";
-        throw new Error(msg);
-      }
-      try {
-        this._mm.sendAsyncMessage(this._messageName, packet);
-      } catch (e) {
-        if (e.result != Cr.NS_ERROR_NULL_POINTER) {
-          throw e;
-        }
-        // In some cases, especially when using messageManagers in non-e10s mode, we reach
-        // this point with a dead messageManager which only throws errors but does not
-        // seem to indicate in any other way that it is dead.
-      }
-    },
-
-    startBulkSend: function() {
-      throw new Error("Can't send bulk data to child processes.");
-    },
-
-    swapBrowser(mm) {
-      this._removeListener();
-      this._mm = mm;
-      this._addListener();
-    },
-  };
-
-  exports.ChildDebuggerTransport = ChildDebuggerTransport;
-
-  // WorkerDebuggerTransport is defined differently depending on whether we are
-  // on the main thread or a worker thread. In the former case, we are required
-  // by the devtools loader, and isWorker will be false. Otherwise, we are
-  // required by the worker loader, and isWorker will be true.
-  //
-  // Each worker debugger supports only a single connection to the main thread.
-  // However, its theoretically possible for multiple servers to connect to the
-  // same worker. Consequently, each transport has a connection id, to allow
-  // messages from multiple connections to be multiplexed on a single channel.
-
-  if (!this.isWorker) {
-    // Main thread
-    (function() {
-      /**
-       * A transport that uses a WorkerDebugger to send packets from the main
-       * thread to a worker thread.
-       */
-      function WorkerDebuggerTransport(dbg, id) {
-        this._dbg = dbg;
-        this._id = id;
-        this.onMessage = this._onMessage.bind(this);
-      }
-
-      WorkerDebuggerTransport.prototype = {
-        constructor: WorkerDebuggerTransport,
-
-        ready: function() {
-          this._dbg.addListener(this);
-        },
-
-        close: function() {
-          this._dbg.removeListener(this);
-          if (this.hooks) {
-            this.hooks.onClosed();
-          }
-        },
+    };
 
-        send: function(packet) {
-          this._dbg.postMessage(JSON.stringify({
-            type: "message",
-            id: this._id,
-            message: packet
-          }));
-        },
-
-        startBulkSend: function() {
-          throw new Error("Can't send bulk data from worker threads!");
-        },
-
-        _onMessage: function(message) {
-          const packet = JSON.parse(message);
-          if (packet.type !== "message" || packet.id !== this._id) {
-            return;
-          }
-
-          if (this.hooks) {
-            this.hooks.onPacket(packet.message);
-          }
-        }
-      };
-
-      exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
-    }).call(this);
-  } else {
-    // Worker thread
-    (function() {
-      /**
-       * A transport that uses a WorkerDebuggerGlobalScope to send packets from a
-       * worker thread to the main thread.
-       */
-      function WorkerDebuggerTransport(scope, id) {
-        this._scope = scope;
-        this._id = id;
-        this._onMessage = this._onMessage.bind(this);
-      }
-
-      WorkerDebuggerTransport.prototype = {
-        constructor: WorkerDebuggerTransport,
-
-        ready: function() {
-          this._scope.addEventListener("message", this._onMessage);
-        },
-
-        close: function() {
-          this._scope.removeEventListener("message", this._onMessage);
-          if (this.hooks) {
-            this.hooks.onClosed();
-          }
-        },
-
-        send: function(packet) {
-          this._scope.postMessage(JSON.stringify({
-            type: "message",
-            id: this._id,
-            message: packet
-          }));
-        },
-
-        startBulkSend: function() {
-          throw new Error("Can't send bulk data from worker threads!");
-        },
-
-        _onMessage: function(event) {
-          const packet = JSON.parse(event.data);
-          if (packet.type !== "message" || packet.id !== this._id) {
-            return;
-          }
-
-          if (this.hooks) {
-            this.hooks.onPacket(packet.message);
-          }
-        }
-      };
-
-      exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
-    }).call(this);
-  }
-});
+    exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
+  }).call(this);
+}