--- 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);
+}