toolkit/devtools/server/transport.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 // TODO: Get rid of this code once the marionette server loads transport.js as
michael@0 8 // an SDK module (see bug 1000814)
michael@0 9 (function (factory) { // Module boilerplate
michael@0 10 if (this.module && module.id.indexOf("transport") >= 0) { // require
michael@0 11 factory(require, exports);
michael@0 12 } else { // loadSubScript
michael@0 13 if (this.require) {
michael@0 14 factory(require, this);
michael@0 15 } else {
michael@0 16 const Cu = Components.utils;
michael@0 17 const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
michael@0 18 factory(devtools.require, this);
michael@0 19 }
michael@0 20 }
michael@0 21 }).call(this, function (require, exports) {
michael@0 22
michael@0 23 "use strict";
michael@0 24
michael@0 25 const { Cc, Ci, Cr, Cu } = require("chrome");
michael@0 26 const Services = require("Services");
michael@0 27 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
michael@0 28 const { dumpn } = DevToolsUtils;
michael@0 29
michael@0 30 Cu.import("resource://gre/modules/NetUtil.jsm");
michael@0 31
michael@0 32 /**
michael@0 33 * An adapter that handles data transfers between the debugger client and
michael@0 34 * server. It can work with both nsIPipe and nsIServerSocket transports so
michael@0 35 * long as the properly created input and output streams are specified.
michael@0 36 * (However, for intra-process connections, LocalDebuggerTransport, below,
michael@0 37 * is more efficient than using an nsIPipe pair with DebuggerTransport.)
michael@0 38 *
michael@0 39 * @param aInput nsIInputStream
michael@0 40 * The input stream.
michael@0 41 * @param aOutput nsIAsyncOutputStream
michael@0 42 * The output stream.
michael@0 43 *
michael@0 44 * Given a DebuggerTransport instance dt:
michael@0 45 * 1) Set dt.hooks to a packet handler object (described below).
michael@0 46 * 2) Call dt.ready() to begin watching for input packets.
michael@0 47 * 3) Call dt.send() to send packets as you please, and handle incoming
michael@0 48 * packets passed to hook.onPacket.
michael@0 49 * 4) Call dt.close() to close the connection, and disengage from the event
michael@0 50 * loop.
michael@0 51 *
michael@0 52 * A packet handler is an object with two methods:
michael@0 53 *
michael@0 54 * - onPacket(packet) - called when we have received a complete packet.
michael@0 55 * |Packet| is the parsed form of the packet --- a JavaScript value, not
michael@0 56 * a JSON-syntax string.
michael@0 57 *
michael@0 58 * - onClosed(status) - called when the connection is closed. |Status| is
michael@0 59 * an nsresult, of the sort passed to nsIRequestObserver.
michael@0 60 *
michael@0 61 * Data is transferred as a JSON packet serialized into a string, with the
michael@0 62 * string length prepended to the packet, followed by a colon
michael@0 63 * ([length]:[packet]). The contents of the JSON packet are specified in
michael@0 64 * the Remote Debugging Protocol specification.
michael@0 65 */
michael@0 66 function DebuggerTransport(aInput, aOutput)
michael@0 67 {
michael@0 68 this._input = aInput;
michael@0 69 this._output = aOutput;
michael@0 70
michael@0 71 this._converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 72 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 73 this._converter.charset = "UTF-8";
michael@0 74
michael@0 75 this._outgoing = "";
michael@0 76 this._incoming = "";
michael@0 77
michael@0 78 this.hooks = null;
michael@0 79 }
michael@0 80
michael@0 81 DebuggerTransport.prototype = {
michael@0 82 /**
michael@0 83 * Transmit a packet.
michael@0 84 *
michael@0 85 * This method returns immediately, without waiting for the entire
michael@0 86 * packet to be transmitted, registering event handlers as needed to
michael@0 87 * transmit the entire packet. Packets are transmitted in the order
michael@0 88 * they are passed to this method.
michael@0 89 */
michael@0 90 send: function DT_send(aPacket) {
michael@0 91 let data = JSON.stringify(aPacket);
michael@0 92 data = this._converter.ConvertFromUnicode(data);
michael@0 93 data = data.length + ':' + data;
michael@0 94 this._outgoing += data;
michael@0 95 this._flushOutgoing();
michael@0 96 },
michael@0 97
michael@0 98 /**
michael@0 99 * Close the transport.
michael@0 100 */
michael@0 101 close: function DT_close() {
michael@0 102 this._input.close();
michael@0 103 this._output.close();
michael@0 104 },
michael@0 105
michael@0 106 /**
michael@0 107 * Flush the outgoing stream.
michael@0 108 */
michael@0 109 _flushOutgoing: function DT_flushOutgoing() {
michael@0 110 if (this._outgoing.length > 0) {
michael@0 111 var threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
michael@0 112 this._output.asyncWait(this, 0, 0, threadManager.currentThread);
michael@0 113 }
michael@0 114 },
michael@0 115
michael@0 116 onOutputStreamReady:
michael@0 117 DevToolsUtils.makeInfallible(function DT_onOutputStreamReady(aStream) {
michael@0 118 let written = 0;
michael@0 119 try {
michael@0 120 written = aStream.write(this._outgoing, this._outgoing.length);
michael@0 121 } catch(e if e.result == Cr.NS_BASE_STREAM_CLOSED) {
michael@0 122 dumpn("Connection closed.");
michael@0 123 this.close();
michael@0 124 return;
michael@0 125 }
michael@0 126 this._outgoing = this._outgoing.slice(written);
michael@0 127 this._flushOutgoing();
michael@0 128 }, "DebuggerTransport.prototype.onOutputStreamReady"),
michael@0 129
michael@0 130 /**
michael@0 131 * Initialize the input stream for reading. Once this method has been
michael@0 132 * called, we watch for packets on the input stream, and pass them to
michael@0 133 * this.hook.onPacket.
michael@0 134 */
michael@0 135 ready: function DT_ready() {
michael@0 136 let pump = Cc["@mozilla.org/network/input-stream-pump;1"]
michael@0 137 .createInstance(Ci.nsIInputStreamPump);
michael@0 138 pump.init(this._input, -1, -1, 0, 0, false);
michael@0 139 pump.asyncRead(this, null);
michael@0 140 },
michael@0 141
michael@0 142 // nsIStreamListener
michael@0 143 onStartRequest:
michael@0 144 DevToolsUtils.makeInfallible(function DT_onStartRequest(aRequest, aContext) {},
michael@0 145 "DebuggerTransport.prototype.onStartRequest"),
michael@0 146
michael@0 147 onStopRequest:
michael@0 148 DevToolsUtils.makeInfallible(function DT_onStopRequest(aRequest, aContext, aStatus) {
michael@0 149 this.close();
michael@0 150 if (this.hooks) {
michael@0 151 this.hooks.onClosed(aStatus);
michael@0 152 this.hooks = null;
michael@0 153 }
michael@0 154 }, "DebuggerTransport.prototype.onStopRequest"),
michael@0 155
michael@0 156 onDataAvailable:
michael@0 157 DevToolsUtils.makeInfallible(function DT_onDataAvailable(aRequest, aContext,
michael@0 158 aStream, aOffset, aCount) {
michael@0 159 this._incoming += NetUtil.readInputStreamToString(aStream,
michael@0 160 aStream.available());
michael@0 161 while (this._processIncoming()) {};
michael@0 162 }, "DebuggerTransport.prototype.onDataAvailable"),
michael@0 163
michael@0 164 /**
michael@0 165 * Process incoming packets. Returns true if a packet has been received, either
michael@0 166 * if it was properly parsed or not. Returns false if the incoming stream does
michael@0 167 * not contain a full packet yet. After a proper packet is parsed, the dispatch
michael@0 168 * handler DebuggerTransport.hooks.onPacket is called with the packet as a
michael@0 169 * parameter.
michael@0 170 */
michael@0 171 _processIncoming: function DT__processIncoming() {
michael@0 172 // Well this is ugly.
michael@0 173 let sep = this._incoming.indexOf(':');
michael@0 174 if (sep < 0) {
michael@0 175 // Incoming packet length is too big anyway - drop the connection.
michael@0 176 if (this._incoming.length > 20) {
michael@0 177 this.close();
michael@0 178 }
michael@0 179
michael@0 180 return false;
michael@0 181 }
michael@0 182
michael@0 183 let count = this._incoming.substring(0, sep);
michael@0 184 // Check for a positive number with no garbage afterwards.
michael@0 185 if (!/^[0-9]+$/.exec(count)) {
michael@0 186 this.close();
michael@0 187 return false;
michael@0 188 }
michael@0 189
michael@0 190 count = +count;
michael@0 191 if (this._incoming.length - (sep + 1) < count) {
michael@0 192 // Don't have a complete request yet.
michael@0 193 return false;
michael@0 194 }
michael@0 195
michael@0 196 // We have a complete request, pluck it out of the data and parse it.
michael@0 197 this._incoming = this._incoming.substring(sep + 1);
michael@0 198 let packet = this._incoming.substring(0, count);
michael@0 199 this._incoming = this._incoming.substring(count);
michael@0 200
michael@0 201 try {
michael@0 202 packet = this._converter.ConvertToUnicode(packet);
michael@0 203 var parsed = JSON.parse(packet);
michael@0 204 } catch(e) {
michael@0 205 let msg = "Error parsing incoming packet: " + packet + " (" + e + " - " + e.stack + ")";
michael@0 206 if (Cu.reportError) {
michael@0 207 Cu.reportError(msg);
michael@0 208 }
michael@0 209 dump(msg + "\n");
michael@0 210 return true;
michael@0 211 }
michael@0 212
michael@0 213 if (dumpn.wantLogging) {
michael@0 214 dumpn("Got: " + JSON.stringify(parsed, null, 2));
michael@0 215 }
michael@0 216 let self = this;
michael@0 217 Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(function() {
michael@0 218 // Ensure the hooks are still around by the time this runs (they will go
michael@0 219 // away when the transport is closed).
michael@0 220 if (self.hooks) {
michael@0 221 self.hooks.onPacket(parsed);
michael@0 222 }
michael@0 223 }, "DebuggerTransport instance's this.hooks.onPacket"), 0);
michael@0 224
michael@0 225 return true;
michael@0 226 }
michael@0 227 }
michael@0 228
michael@0 229 exports.DebuggerTransport = DebuggerTransport;
michael@0 230
michael@0 231 /**
michael@0 232 * An adapter that handles data transfers between the debugger client and
michael@0 233 * server when they both run in the same process. It presents the same API as
michael@0 234 * DebuggerTransport, but instead of transmitting serialized messages across a
michael@0 235 * connection it merely calls the packet dispatcher of the other side.
michael@0 236 *
michael@0 237 * @param aOther LocalDebuggerTransport
michael@0 238 * The other endpoint for this debugger connection.
michael@0 239 *
michael@0 240 * @see DebuggerTransport
michael@0 241 */
michael@0 242 function LocalDebuggerTransport(aOther)
michael@0 243 {
michael@0 244 this.other = aOther;
michael@0 245 this.hooks = null;
michael@0 246
michael@0 247 /*
michael@0 248 * A packet number, shared between this and this.other. This isn't used
michael@0 249 * by the protocol at all, but it makes the packet traces a lot easier to
michael@0 250 * follow.
michael@0 251 */
michael@0 252 this._serial = this.other ? this.other._serial : { count: 0 };
michael@0 253 }
michael@0 254
michael@0 255 LocalDebuggerTransport.prototype = {
michael@0 256 /**
michael@0 257 * Transmit a message by directly calling the onPacket handler of the other
michael@0 258 * endpoint.
michael@0 259 */
michael@0 260 send: function LDT_send(aPacket) {
michael@0 261 let serial = this._serial.count++;
michael@0 262 if (dumpn.wantLogging) {
michael@0 263 /* Check 'from' first, as 'echo' packets have both. */
michael@0 264 if (aPacket.from) {
michael@0 265 dumpn("Packet " + serial + " sent from " + uneval(aPacket.from));
michael@0 266 } else if (aPacket.to) {
michael@0 267 dumpn("Packet " + serial + " sent to " + uneval(aPacket.to));
michael@0 268 }
michael@0 269 }
michael@0 270 this._deepFreeze(aPacket);
michael@0 271 let other = this.other;
michael@0 272 if (other) {
michael@0 273 Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(function() {
michael@0 274 // Avoid the cost of JSON.stringify() when logging is disabled.
michael@0 275 if (dumpn.wantLogging) {
michael@0 276 dumpn("Received packet " + serial + ": " + JSON.stringify(aPacket, null, 2));
michael@0 277 }
michael@0 278 if (other.hooks) {
michael@0 279 other.hooks.onPacket(aPacket);
michael@0 280 }
michael@0 281 }, "LocalDebuggerTransport instance's this.other.hooks.onPacket"), 0);
michael@0 282 }
michael@0 283 },
michael@0 284
michael@0 285 /**
michael@0 286 * Close the transport.
michael@0 287 */
michael@0 288 close: function LDT_close() {
michael@0 289 if (this.other) {
michael@0 290 // Remove the reference to the other endpoint before calling close(), to
michael@0 291 // avoid infinite recursion.
michael@0 292 let other = this.other;
michael@0 293 this.other = null;
michael@0 294 other.close();
michael@0 295 }
michael@0 296 if (this.hooks) {
michael@0 297 try {
michael@0 298 this.hooks.onClosed();
michael@0 299 } catch(ex) {
michael@0 300 Cu.reportError(ex);
michael@0 301 }
michael@0 302 this.hooks = null;
michael@0 303 }
michael@0 304 },
michael@0 305
michael@0 306 /**
michael@0 307 * An empty method for emulating the DebuggerTransport API.
michael@0 308 */
michael@0 309 ready: function LDT_ready() {},
michael@0 310
michael@0 311 /**
michael@0 312 * Helper function that makes an object fully immutable.
michael@0 313 */
michael@0 314 _deepFreeze: function LDT_deepFreeze(aObject) {
michael@0 315 Object.freeze(aObject);
michael@0 316 for (let prop in aObject) {
michael@0 317 // Freeze the properties that are objects, not on the prototype, and not
michael@0 318 // already frozen. Note that this might leave an unfrozen reference
michael@0 319 // somewhere in the object if there is an already frozen object containing
michael@0 320 // an unfrozen object.
michael@0 321 if (aObject.hasOwnProperty(prop) && typeof aObject === "object" &&
michael@0 322 !Object.isFrozen(aObject)) {
michael@0 323 this._deepFreeze(o[prop]);
michael@0 324 }
michael@0 325 }
michael@0 326 }
michael@0 327 };
michael@0 328
michael@0 329 exports.LocalDebuggerTransport = LocalDebuggerTransport;
michael@0 330
michael@0 331 /**
michael@0 332 * A transport for the debugging protocol that uses nsIMessageSenders to
michael@0 333 * exchange packets with servers running in child processes.
michael@0 334 *
michael@0 335 * In the parent process, |aSender| should be the nsIMessageSender for the
michael@0 336 * child process. In a child process, |aSender| should be the child process
michael@0 337 * message manager, which sends packets to the parent.
michael@0 338 *
michael@0 339 * aPrefix is a string included in the message names, to distinguish
michael@0 340 * multiple servers running in the same child process.
michael@0 341 *
michael@0 342 * This transport exchanges messages named 'debug:<prefix>:packet', where
michael@0 343 * <prefix> is |aPrefix|, whose data is the protocol packet.
michael@0 344 */
michael@0 345 function ChildDebuggerTransport(aSender, aPrefix) {
michael@0 346 this._sender = aSender.QueryInterface(Ci.nsIMessageSender);
michael@0 347 this._messageName = "debug:" + aPrefix + ":packet";
michael@0 348 }
michael@0 349
michael@0 350 /*
michael@0 351 * To avoid confusion, we use 'message' to mean something that
michael@0 352 * nsIMessageSender conveys, and 'packet' to mean a remote debugging
michael@0 353 * protocol packet.
michael@0 354 */
michael@0 355 ChildDebuggerTransport.prototype = {
michael@0 356 constructor: ChildDebuggerTransport,
michael@0 357
michael@0 358 hooks: null,
michael@0 359
michael@0 360 ready: function () {
michael@0 361 this._sender.addMessageListener(this._messageName, this);
michael@0 362 },
michael@0 363
michael@0 364 close: function () {
michael@0 365 this._sender.removeMessageListener(this._messageName, this);
michael@0 366 this.hooks.onClosed();
michael@0 367 },
michael@0 368
michael@0 369 receiveMessage: function ({data}) {
michael@0 370 this.hooks.onPacket(data);
michael@0 371 },
michael@0 372
michael@0 373 send: function (packet) {
michael@0 374 this._sender.sendAsyncMessage(this._messageName, packet);
michael@0 375 }
michael@0 376 };
michael@0 377
michael@0 378 exports.ChildDebuggerTransport = ChildDebuggerTransport;
michael@0 379
michael@0 380 });

mercurial