Bug 1000814 - Move ChildDebuggerTransport to its own module. r=jryans
MozReview-Commit-ID: FgTVee7NLyC
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -7,24 +7,25 @@
/**
* Toolkit glue for the remote debugging protocol, loaded into the
* debugging global.
*/
var { Ci, Cc } = require("chrome");
var Services = require("Services");
var { ActorPool, OriginalLocation, RegisteredActorFactory,
ObservedActorFactory } = require("devtools/server/actors/common");
-var { ChildDebuggerTransport, WorkerDebuggerTransport } =
+var { WorkerDebuggerTransport } =
require("devtools/shared/transport/transport");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { dumpn } = DevToolsUtils;
loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
loader.lazyRequireGetter(this, "LocalDebuggerTransport", "devtools/shared/transport/local-transport", true);
+loader.lazyRequireGetter(this, "ChildDebuggerTransport", "devtools/shared/transport/child-transport", true);
loader.lazyGetter(this, "generateUUID", () => {
// eslint-disable-next-line no-shadow
const { generateUUID } = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
return generateUUID;
});
copy from devtools/shared/transport/transport.js
copy to devtools/shared/transport/child-transport.js
--- a/devtools/shared/transport/transport.js
+++ b/devtools/shared/transport/child-transport.js
@@ -1,493 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-/* global uneval */
-
-const { Cc, Cr, CC } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { dumpn, dumpv } = DevToolsUtils;
+const { Cr } = require("chrome");
const flags = require("devtools/shared/flags");
-const StreamUtils = require("devtools/shared/transport/stream-utils");
-const { Packet, JSONPacket, BulkPacket } =
- require("devtools/shared/transport/packets");
-
-loader.lazyGetter(this, "ScriptableInputStream", () => {
- return CC("@mozilla.org/scriptableinputstream;1",
- "nsIScriptableInputStream", "init");
-});
-
-const PACKET_HEADER_MAX = 200;
-
-/**
- * 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 = [];
-
- 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();
- },
-
- /**
- * 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;
- },
-
- /**
- * 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.");
- }
- },
-
- /**
- * 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) {
- return;
- }
-
- // 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;
- },
-
- /**
- * Resume this transport's attempts to write to the output stream.
- */
- resumeOutgoing: function() {
- this._outgoingEnabled = true;
- this._flushOutgoing();
- },
-
- // 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;
- }
-
- try {
- this._currentOutgoing.write(stream);
- } catch (e) {
- if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
- this.close(e.result);
- return;
- }
- 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;
- },
-
- /**
- * 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);
-
- if (!count) {
- dumpv("Nothing to read, skipping");
- return false;
- }
-
- 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._incoming.done) {
- // 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);
-
- // 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;
- }
-
- // 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("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();
- },
-
- /**
- * 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;
/**
* 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.
@@ -598,126 +121,8 @@ ChildDebuggerTransport.prototype = {
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);
-}
--- a/devtools/shared/transport/moz.build
+++ b/devtools/shared/transport/moz.build
@@ -2,14 +2,15 @@
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
DevToolsModules(
+ 'child-transport.js',
'local-transport.js',
'packets.js',
'stream-utils.js',
'transport.js',
'websocket-transport.js',
)
--- a/devtools/shared/transport/transport.js
+++ b/devtools/shared/transport/transport.js
@@ -479,136 +479,16 @@ DebuggerTransport.prototype = {
this._incomingHeader = "";
this._incoming = null;
}
};
exports.DebuggerTransport = DebuggerTransport;
-/**
- * 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";
-}
-
-/*
- * 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;
- }
- // 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