transport.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. /* global Pipe, ScriptableInputStream, uneval */
  6. // TODO: Get rid of this code once the marionette server loads transport.js as
  7. // an SDK module (see bug 1000814)
  8. (function (factory) {
  9. if (this.module && module.id.indexOf("transport") >= 0) {
  10. // require
  11. factory.call(this, require, exports);
  12. } else if (this.require) {
  13. // loadSubScript
  14. factory.call(this, require, this);
  15. } else {
  16. // Cu.import
  17. const Cu = Components.utils;
  18. const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
  19. factory.call(this, require, this);
  20. }
  21. }).call(this, function (require, exports) {
  22. const { Cc, Cr, CC } = require("chrome");
  23. const DevToolsUtils = require("devtools/shared/DevToolsUtils");
  24. const { dumpn, dumpv } = DevToolsUtils;
  25. const flags = require("devtools/shared/flags");
  26. const StreamUtils = require("devtools/shared/transport/stream-utils");
  27. const { Packet, JSONPacket, BulkPacket } =
  28. require("devtools/shared/transport/packets");
  29. const promise = require("promise");
  30. const defer = require("devtools/shared/defer");
  31. const EventEmitter = require("devtools/shared/event-emitter");
  32. DevToolsUtils.defineLazyGetter(this, "Pipe", () => {
  33. return CC("@mozilla.org/pipe;1", "nsIPipe", "init");
  34. });
  35. DevToolsUtils.defineLazyGetter(this, "ScriptableInputStream", () => {
  36. return CC("@mozilla.org/scriptableinputstream;1",
  37. "nsIScriptableInputStream", "init");
  38. });
  39. const PACKET_HEADER_MAX = 200;
  40. /**
  41. * An adapter that handles data transfers between the debugger client and
  42. * server. It can work with both nsIPipe and nsIServerSocket transports so
  43. * long as the properly created input and output streams are specified.
  44. * (However, for intra-process connections, LocalDebuggerTransport, below,
  45. * is more efficient than using an nsIPipe pair with DebuggerTransport.)
  46. *
  47. * @param input nsIAsyncInputStream
  48. * The input stream.
  49. * @param output nsIAsyncOutputStream
  50. * The output stream.
  51. *
  52. * Given a DebuggerTransport instance dt:
  53. * 1) Set dt.hooks to a packet handler object (described below).
  54. * 2) Call dt.ready() to begin watching for input packets.
  55. * 3) Call dt.send() / dt.startBulkSend() to send packets.
  56. * 4) Call dt.close() to close the connection, and disengage from the event
  57. * loop.
  58. *
  59. * A packet handler is an object with the following methods:
  60. *
  61. * - onPacket(packet) - called when we have received a complete packet.
  62. * |packet| is the parsed form of the packet --- a JavaScript value, not
  63. * a JSON-syntax string.
  64. *
  65. * - onBulkPacket(packet) - called when we have switched to bulk packet
  66. * receiving mode. |packet| is an object containing:
  67. * * actor: Name of actor that will receive the packet
  68. * * type: Name of actor's method that should be called on receipt
  69. * * length: Size of the data to be read
  70. * * stream: This input stream should only be used directly if you can ensure
  71. * that you will read exactly |length| bytes and will not close the
  72. * stream when reading is complete
  73. * * done: If you use the stream directly (instead of |copyTo| below), you
  74. * must signal completion by resolving / rejecting this deferred.
  75. * If it's rejected, the transport will be closed. If an Error is
  76. * supplied as a rejection value, it will be logged via |dumpn|.
  77. * If you do use |copyTo|, resolving is taken care of for you when
  78. * copying completes.
  79. * * copyTo: A helper function for getting your data out of the stream that
  80. * meets the stream handling requirements above, and has the
  81. * following signature:
  82. * @param output nsIAsyncOutputStream
  83. * The stream to copy to.
  84. * @return Promise
  85. * The promise is resolved when copying completes or rejected if any
  86. * (unexpected) errors occur.
  87. * This object also emits "progress" events for each chunk that is
  88. * copied. See stream-utils.js.
  89. *
  90. * - onClosed(reason) - called when the connection is closed. |reason| is
  91. * an optional nsresult or object, typically passed when the transport is
  92. * closed due to some error in a underlying stream.
  93. *
  94. * See ./packets.js and the Remote Debugging Protocol specification for more
  95. * details on the format of these packets.
  96. */
  97. function DebuggerTransport(input, output) {
  98. EventEmitter.decorate(this);
  99. this._input = input;
  100. this._scriptableInput = new ScriptableInputStream(input);
  101. this._output = output;
  102. // The current incoming (possibly partial) header, which will determine which
  103. // type of Packet |_incoming| below will become.
  104. this._incomingHeader = "";
  105. // The current incoming Packet object
  106. this._incoming = null;
  107. // A queue of outgoing Packet objects
  108. this._outgoing = [];
  109. this.hooks = null;
  110. this.active = false;
  111. this._incomingEnabled = true;
  112. this._outgoingEnabled = true;
  113. this.close = this.close.bind(this);
  114. }
  115. DebuggerTransport.prototype = {
  116. /**
  117. * Transmit an object as a JSON packet.
  118. *
  119. * This method returns immediately, without waiting for the entire
  120. * packet to be transmitted, registering event handlers as needed to
  121. * transmit the entire packet. Packets are transmitted in the order
  122. * they are passed to this method.
  123. */
  124. send: function (object) {
  125. this.emit("send", object);
  126. let packet = new JSONPacket(this);
  127. packet.object = object;
  128. this._outgoing.push(packet);
  129. this._flushOutgoing();
  130. },
  131. /**
  132. * Transmit streaming data via a bulk packet.
  133. *
  134. * This method initiates the bulk send process by queuing up the header data.
  135. * The caller receives eventual access to a stream for writing.
  136. *
  137. * N.B.: Do *not* attempt to close the stream handed to you, as it will
  138. * continue to be used by this transport afterwards. Most users should
  139. * instead use the provided |copyFrom| function instead.
  140. *
  141. * @param header Object
  142. * This is modeled after the format of JSON packets above, but does not
  143. * actually contain the data, but is instead just a routing header:
  144. * * actor: Name of actor that will receive the packet
  145. * * type: Name of actor's method that should be called on receipt
  146. * * length: Size of the data to be sent
  147. * @return Promise
  148. * The promise will be resolved when you are allowed to write to the
  149. * stream with an object containing:
  150. * * stream: This output stream should only be used directly if
  151. * you can ensure that you will write exactly |length|
  152. * bytes and will not close the stream when writing is
  153. * complete
  154. * * done: If you use the stream directly (instead of |copyFrom|
  155. * below), you must signal completion by resolving /
  156. * rejecting this deferred. If it's rejected, the
  157. * transport will be closed. If an Error is supplied as
  158. * a rejection value, it will be logged via |dumpn|. If
  159. * you do use |copyFrom|, resolving is taken care of for
  160. * you when copying completes.
  161. * * copyFrom: A helper function for getting your data onto the
  162. * stream that meets the stream handling requirements
  163. * above, and has the following signature:
  164. * @param input nsIAsyncInputStream
  165. * The stream to copy from.
  166. * @return Promise
  167. * The promise is resolved when copying completes or
  168. * rejected if any (unexpected) errors occur.
  169. * This object also emits "progress" events for each chunk
  170. * that is copied. See stream-utils.js.
  171. */
  172. startBulkSend: function (header) {
  173. this.emit("startbulksend", header);
  174. let packet = new BulkPacket(this);
  175. packet.header = header;
  176. this._outgoing.push(packet);
  177. this._flushOutgoing();
  178. return packet.streamReadyForWriting;
  179. },
  180. /**
  181. * Close the transport.
  182. * @param reason nsresult / object (optional)
  183. * The status code or error message that corresponds to the reason for
  184. * closing the transport (likely because a stream closed or failed).
  185. */
  186. close: function (reason) {
  187. this.emit("close", reason);
  188. this.active = false;
  189. this._input.close();
  190. this._scriptableInput.close();
  191. this._output.close();
  192. this._destroyIncoming();
  193. this._destroyAllOutgoing();
  194. if (this.hooks) {
  195. this.hooks.onClosed(reason);
  196. this.hooks = null;
  197. }
  198. if (reason) {
  199. dumpn("Transport closed: " + DevToolsUtils.safeErrorString(reason));
  200. } else {
  201. dumpn("Transport closed.");
  202. }
  203. },
  204. /**
  205. * The currently outgoing packet (at the top of the queue).
  206. */
  207. get _currentOutgoing() {
  208. return this._outgoing[0];
  209. },
  210. /**
  211. * Flush data to the outgoing stream. Waits until the output stream notifies
  212. * us that it is ready to be written to (via onOutputStreamReady).
  213. */
  214. _flushOutgoing: function () {
  215. if (!this._outgoingEnabled || this._outgoing.length === 0) {
  216. return;
  217. }
  218. // If the top of the packet queue has nothing more to send, remove it.
  219. if (this._currentOutgoing.done) {
  220. this._finishCurrentOutgoing();
  221. }
  222. if (this._outgoing.length > 0) {
  223. let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
  224. this._output.asyncWait(this, 0, 0, threadManager.currentThread);
  225. }
  226. },
  227. /**
  228. * Pause this transport's attempts to write to the output stream. This is
  229. * used when we've temporarily handed off our output stream for writing bulk
  230. * data.
  231. */
  232. pauseOutgoing: function () {
  233. this._outgoingEnabled = false;
  234. },
  235. /**
  236. * Resume this transport's attempts to write to the output stream.
  237. */
  238. resumeOutgoing: function () {
  239. this._outgoingEnabled = true;
  240. this._flushOutgoing();
  241. },
  242. // nsIOutputStreamCallback
  243. /**
  244. * This is called when the output stream is ready for more data to be written.
  245. * The current outgoing packet will attempt to write some amount of data, but
  246. * may not complete.
  247. */
  248. onOutputStreamReady: DevToolsUtils.makeInfallible(function (stream) {
  249. if (!this._outgoingEnabled || this._outgoing.length === 0) {
  250. return;
  251. }
  252. try {
  253. this._currentOutgoing.write(stream);
  254. } catch (e) {
  255. if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
  256. this.close(e.result);
  257. return;
  258. }
  259. throw e;
  260. }
  261. this._flushOutgoing();
  262. }, "DebuggerTransport.prototype.onOutputStreamReady"),
  263. /**
  264. * Remove the current outgoing packet from the queue upon completion.
  265. */
  266. _finishCurrentOutgoing: function () {
  267. if (this._currentOutgoing) {
  268. this._currentOutgoing.destroy();
  269. this._outgoing.shift();
  270. }
  271. },
  272. /**
  273. * Clear the entire outgoing queue.
  274. */
  275. _destroyAllOutgoing: function () {
  276. for (let packet of this._outgoing) {
  277. packet.destroy();
  278. }
  279. this._outgoing = [];
  280. },
  281. /**
  282. * Initialize the input stream for reading. Once this method has been called,
  283. * we watch for packets on the input stream, and pass them to the appropriate
  284. * handlers via this.hooks.
  285. */
  286. ready: function () {
  287. this.active = true;
  288. this._waitForIncoming();
  289. },
  290. /**
  291. * Asks the input stream to notify us (via onInputStreamReady) when it is
  292. * ready for reading.
  293. */
  294. _waitForIncoming: function () {
  295. if (this._incomingEnabled) {
  296. let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
  297. this._input.asyncWait(this, 0, 0, threadManager.currentThread);
  298. }
  299. },
  300. /**
  301. * Pause this transport's attempts to read from the input stream. This is
  302. * used when we've temporarily handed off our input stream for reading bulk
  303. * data.
  304. */
  305. pauseIncoming: function () {
  306. this._incomingEnabled = false;
  307. },
  308. /**
  309. * Resume this transport's attempts to read from the input stream.
  310. */
  311. resumeIncoming: function () {
  312. this._incomingEnabled = true;
  313. this._flushIncoming();
  314. this._waitForIncoming();
  315. },
  316. // nsIInputStreamCallback
  317. /**
  318. * Called when the stream is either readable or closed.
  319. */
  320. onInputStreamReady: DevToolsUtils.makeInfallible(function (stream) {
  321. try {
  322. while (stream.available() && this._incomingEnabled &&
  323. this._processIncoming(stream, stream.available())) {
  324. // Loop until there is nothing more to process
  325. }
  326. this._waitForIncoming();
  327. } catch (e) {
  328. if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
  329. this.close(e.result);
  330. } else {
  331. throw e;
  332. }
  333. }
  334. }, "DebuggerTransport.prototype.onInputStreamReady"),
  335. /**
  336. * Process the incoming data. Will create a new currently incoming Packet if
  337. * needed. Tells the incoming Packet to read as much data as it can, but
  338. * reading may not complete. The Packet signals that its data is ready for
  339. * delivery by calling one of this transport's _on*Ready methods (see
  340. * ./packets.js and the _on*Ready methods below).
  341. * @return boolean
  342. * Whether incoming stream processing should continue for any
  343. * remaining data.
  344. */
  345. _processIncoming: function (stream, count) {
  346. dumpv("Data available: " + count);
  347. if (!count) {
  348. dumpv("Nothing to read, skipping");
  349. return false;
  350. }
  351. try {
  352. if (!this._incoming) {
  353. dumpv("Creating a new packet from incoming");
  354. if (!this._readHeader(stream)) {
  355. // Not enough data to read packet type
  356. return false;
  357. }
  358. // Attempt to create a new Packet by trying to parse each possible
  359. // header pattern.
  360. this._incoming = Packet.fromHeader(this._incomingHeader, this);
  361. if (!this._incoming) {
  362. throw new Error("No packet types for header: " +
  363. this._incomingHeader);
  364. }
  365. }
  366. if (!this._incoming.done) {
  367. // We have an incomplete packet, keep reading it.
  368. dumpv("Existing packet incomplete, keep reading");
  369. this._incoming.read(stream, this._scriptableInput);
  370. }
  371. } catch (e) {
  372. let msg = "Error reading incoming packet: (" + e + " - " + e.stack + ")";
  373. dumpn(msg);
  374. // Now in an invalid state, shut down the transport.
  375. this.close();
  376. return false;
  377. }
  378. if (!this._incoming.done) {
  379. // Still not complete, we'll wait for more data.
  380. dumpv("Packet not done, wait for more");
  381. return true;
  382. }
  383. // Ready for next packet
  384. this._flushIncoming();
  385. return true;
  386. },
  387. /**
  388. * Read as far as we can into the incoming data, attempting to build up a
  389. * complete packet header (which terminates with ":"). We'll only read up to
  390. * PACKET_HEADER_MAX characters.
  391. * @return boolean
  392. * True if we now have a complete header.
  393. */
  394. _readHeader: function () {
  395. let amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
  396. this._incomingHeader +=
  397. StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead);
  398. if (flags.wantVerbose) {
  399. dumpv("Header read: " + this._incomingHeader);
  400. }
  401. if (this._incomingHeader.endsWith(":")) {
  402. if (flags.wantVerbose) {
  403. dumpv("Found packet header successfully: " + this._incomingHeader);
  404. }
  405. return true;
  406. }
  407. if (this._incomingHeader.length >= PACKET_HEADER_MAX) {
  408. throw new Error("Failed to parse packet header!");
  409. }
  410. // Not enough data yet.
  411. return false;
  412. },
  413. /**
  414. * If the incoming packet is done, log it as needed and clear the buffer.
  415. */
  416. _flushIncoming: function () {
  417. if (!this._incoming.done) {
  418. return;
  419. }
  420. if (flags.wantLogging) {
  421. dumpn("Got: " + this._incoming);
  422. }
  423. this._destroyIncoming();
  424. },
  425. /**
  426. * Handler triggered by an incoming JSONPacket completing it's |read| method.
  427. * Delivers the packet to this.hooks.onPacket.
  428. */
  429. _onJSONObjectReady: function (object) {
  430. DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
  431. // Ensure the transport is still alive by the time this runs.
  432. if (this.active) {
  433. this.emit("packet", object);
  434. this.hooks.onPacket(object);
  435. }
  436. }, "DebuggerTransport instance's this.hooks.onPacket"));
  437. },
  438. /**
  439. * Handler triggered by an incoming BulkPacket entering the |read| phase for
  440. * the stream portion of the packet. Delivers info about the incoming
  441. * streaming data to this.hooks.onBulkPacket. See the main comment on the
  442. * transport at the top of this file for more details.
  443. */
  444. _onBulkReadReady: function (...args) {
  445. DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
  446. // Ensure the transport is still alive by the time this runs.
  447. if (this.active) {
  448. this.emit("bulkpacket", ...args);
  449. this.hooks.onBulkPacket(...args);
  450. }
  451. }, "DebuggerTransport instance's this.hooks.onBulkPacket"));
  452. },
  453. /**
  454. * Remove all handlers and references related to the current incoming packet,
  455. * either because it is now complete or because the transport is closing.
  456. */
  457. _destroyIncoming: function () {
  458. if (this._incoming) {
  459. this._incoming.destroy();
  460. }
  461. this._incomingHeader = "";
  462. this._incoming = null;
  463. }
  464. };
  465. exports.DebuggerTransport = DebuggerTransport;
  466. /**
  467. * An adapter that handles data transfers between the debugger client and
  468. * server when they both run in the same process. It presents the same API as
  469. * DebuggerTransport, but instead of transmitting serialized messages across a
  470. * connection it merely calls the packet dispatcher of the other side.
  471. *
  472. * @param other LocalDebuggerTransport
  473. * The other endpoint for this debugger connection.
  474. *
  475. * @see DebuggerTransport
  476. */
  477. function LocalDebuggerTransport(other) {
  478. EventEmitter.decorate(this);
  479. this.other = other;
  480. this.hooks = null;
  481. // A packet number, shared between this and this.other. This isn't used by the
  482. // protocol at all, but it makes the packet traces a lot easier to follow.
  483. this._serial = this.other ? this.other._serial : { count: 0 };
  484. this.close = this.close.bind(this);
  485. }
  486. LocalDebuggerTransport.prototype = {
  487. /**
  488. * Transmit a message by directly calling the onPacket handler of the other
  489. * endpoint.
  490. */
  491. send: function (packet) {
  492. this.emit("send", packet);
  493. let serial = this._serial.count++;
  494. if (flags.wantLogging) {
  495. // Check 'from' first, as 'echo' packets have both.
  496. if (packet.from) {
  497. dumpn("Packet " + serial + " sent from " + uneval(packet.from));
  498. } else if (packet.to) {
  499. dumpn("Packet " + serial + " sent to " + uneval(packet.to));
  500. }
  501. }
  502. this._deepFreeze(packet);
  503. let other = this.other;
  504. if (other) {
  505. DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
  506. // Avoid the cost of JSON.stringify() when logging is disabled.
  507. if (flags.wantLogging) {
  508. dumpn("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
  509. }
  510. if (other.hooks) {
  511. other.emit("packet", packet);
  512. other.hooks.onPacket(packet);
  513. }
  514. }, "LocalDebuggerTransport instance's this.other.hooks.onPacket"));
  515. }
  516. },
  517. /**
  518. * Send a streaming bulk packet directly to the onBulkPacket handler of the
  519. * other endpoint.
  520. *
  521. * This case is much simpler than the full DebuggerTransport, since there is
  522. * no primary stream we have to worry about managing while we hand it off to
  523. * others temporarily. Instead, we can just make a single use pipe and be
  524. * done with it.
  525. */
  526. startBulkSend: function ({actor, type, length}) {
  527. this.emit("startbulksend", {actor, type, length});
  528. let serial = this._serial.count++;
  529. dumpn("Sent bulk packet " + serial + " for actor " + actor);
  530. if (!this.other) {
  531. let error = new Error("startBulkSend: other side of transport missing");
  532. return promise.reject(error);
  533. }
  534. let pipe = new Pipe(true, true, 0, 0, null);
  535. DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
  536. dumpn("Received bulk packet " + serial);
  537. if (!this.other.hooks) {
  538. return;
  539. }
  540. // Receiver
  541. let deferred = defer();
  542. let packet = {
  543. actor: actor,
  544. type: type,
  545. length: length,
  546. copyTo: (output) => {
  547. let copying =
  548. StreamUtils.copyStream(pipe.inputStream, output, length);
  549. deferred.resolve(copying);
  550. return copying;
  551. },
  552. stream: pipe.inputStream,
  553. done: deferred
  554. };
  555. this.other.emit("bulkpacket", packet);
  556. this.other.hooks.onBulkPacket(packet);
  557. // Await the result of reading from the stream
  558. deferred.promise.then(() => pipe.inputStream.close(), this.close);
  559. }, "LocalDebuggerTransport instance's this.other.hooks.onBulkPacket"));
  560. // Sender
  561. let sendDeferred = defer();
  562. // The remote transport is not capable of resolving immediately here, so we
  563. // shouldn't be able to either.
  564. DevToolsUtils.executeSoon(() => {
  565. let copyDeferred = defer();
  566. sendDeferred.resolve({
  567. copyFrom: (input) => {
  568. let copying =
  569. StreamUtils.copyStream(input, pipe.outputStream, length);
  570. copyDeferred.resolve(copying);
  571. return copying;
  572. },
  573. stream: pipe.outputStream,
  574. done: copyDeferred
  575. });
  576. // Await the result of writing to the stream
  577. copyDeferred.promise.then(() => pipe.outputStream.close(), this.close);
  578. });
  579. return sendDeferred.promise;
  580. },
  581. /**
  582. * Close the transport.
  583. */
  584. close: function () {
  585. this.emit("close");
  586. if (this.other) {
  587. // Remove the reference to the other endpoint before calling close(), to
  588. // avoid infinite recursion.
  589. let other = this.other;
  590. this.other = null;
  591. other.close();
  592. }
  593. if (this.hooks) {
  594. try {
  595. this.hooks.onClosed();
  596. } catch (ex) {
  597. console.error(ex);
  598. }
  599. this.hooks = null;
  600. }
  601. },
  602. /**
  603. * An empty method for emulating the DebuggerTransport API.
  604. */
  605. ready: function () {},
  606. /**
  607. * Helper function that makes an object fully immutable.
  608. */
  609. _deepFreeze: function (object) {
  610. Object.freeze(object);
  611. for (let prop in object) {
  612. // Freeze the properties that are objects, not on the prototype, and not
  613. // already frozen. Note that this might leave an unfrozen reference
  614. // somewhere in the object if there is an already frozen object containing
  615. // an unfrozen object.
  616. if (object.hasOwnProperty(prop) && typeof object === "object" &&
  617. !Object.isFrozen(object)) {
  618. this._deepFreeze(object[prop]);
  619. }
  620. }
  621. }
  622. };
  623. exports.LocalDebuggerTransport = LocalDebuggerTransport;
  624. /**
  625. * A transport for the debugging protocol that uses nsIMessageManagers to
  626. * exchange packets with servers running in child processes.
  627. *
  628. * In the parent process, |mm| should be the nsIMessageSender for the
  629. * child process. In a child process, |mm| should be the child process
  630. * message manager, which sends packets to the parent.
  631. *
  632. * |prefix| is a string included in the message names, to distinguish
  633. * multiple servers running in the same child process.
  634. *
  635. * This transport exchanges messages named 'debug:<prefix>:packet', where
  636. * <prefix> is |prefix|, whose data is the protocol packet.
  637. */
  638. function ChildDebuggerTransport(mm, prefix) {
  639. EventEmitter.decorate(this);
  640. this._mm = mm;
  641. this._messageName = "debug:" + prefix + ":packet";
  642. }
  643. /*
  644. * To avoid confusion, we use 'message' to mean something that
  645. * nsIMessageSender conveys, and 'packet' to mean a remote debugging
  646. * protocol packet.
  647. */
  648. ChildDebuggerTransport.prototype = {
  649. constructor: ChildDebuggerTransport,
  650. hooks: null,
  651. _addListener() {
  652. this._mm.addMessageListener(this._messageName, this);
  653. },
  654. _removeListener() {
  655. try {
  656. this._mm.removeMessageListener(this._messageName, this);
  657. } catch (e) {
  658. if (e.result != Cr.NS_ERROR_NULL_POINTER) {
  659. throw e;
  660. }
  661. // In some cases, especially when using messageManagers in non-e10s mode, we reach
  662. // this point with a dead messageManager which only throws errors but does not
  663. // seem to indicate in any other way that it is dead.
  664. }
  665. },
  666. ready: function () {
  667. this._addListener();
  668. },
  669. close: function () {
  670. this._removeListener();
  671. this.emit("close");
  672. this.hooks.onClosed();
  673. },
  674. receiveMessage: function ({data}) {
  675. this.emit("packet", data);
  676. this.hooks.onPacket(data);
  677. },
  678. send: function (packet) {
  679. this.emit("send", packet);
  680. try {
  681. this._mm.sendAsyncMessage(this._messageName, packet);
  682. } catch (e) {
  683. if (e.result != Cr.NS_ERROR_NULL_POINTER) {
  684. throw e;
  685. }
  686. // In some cases, especially when using messageManagers in non-e10s mode, we reach
  687. // this point with a dead messageManager which only throws errors but does not
  688. // seem to indicate in any other way that it is dead.
  689. }
  690. },
  691. startBulkSend: function () {
  692. throw new Error("Can't send bulk data to child processes.");
  693. },
  694. swapBrowser(mm) {
  695. this._removeListener();
  696. this._mm = mm;
  697. this._addListener();
  698. },
  699. };
  700. exports.ChildDebuggerTransport = ChildDebuggerTransport;
  701. // WorkerDebuggerTransport is defined differently depending on whether we are
  702. // on the main thread or a worker thread. In the former case, we are required
  703. // by the devtools loader, and isWorker will be false. Otherwise, we are
  704. // required by the worker loader, and isWorker will be true.
  705. //
  706. // Each worker debugger supports only a single connection to the main thread.
  707. // However, its theoretically possible for multiple servers to connect to the
  708. // same worker. Consequently, each transport has a connection id, to allow
  709. // messages from multiple connections to be multiplexed on a single channel.
  710. if (!this.isWorker) {
  711. // Main thread
  712. (function () {
  713. /**
  714. * A transport that uses a WorkerDebugger to send packets from the main
  715. * thread to a worker thread.
  716. */
  717. function WorkerDebuggerTransport(dbg, id) {
  718. this._dbg = dbg;
  719. this._id = id;
  720. this.onMessage = this._onMessage.bind(this);
  721. }
  722. WorkerDebuggerTransport.prototype = {
  723. constructor: WorkerDebuggerTransport,
  724. ready: function () {
  725. this._dbg.addListener(this);
  726. },
  727. close: function () {
  728. this._dbg.removeListener(this);
  729. if (this.hooks) {
  730. this.hooks.onClosed();
  731. }
  732. },
  733. send: function (packet) {
  734. this._dbg.postMessage(JSON.stringify({
  735. type: "message",
  736. id: this._id,
  737. message: packet
  738. }));
  739. },
  740. startBulkSend: function () {
  741. throw new Error("Can't send bulk data from worker threads!");
  742. },
  743. _onMessage: function (message) {
  744. let packet = JSON.parse(message);
  745. if (packet.type !== "message" || packet.id !== this._id) {
  746. return;
  747. }
  748. if (this.hooks) {
  749. this.hooks.onPacket(packet.message);
  750. }
  751. }
  752. };
  753. exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
  754. }).call(this);
  755. } else {
  756. // Worker thread
  757. (function () {
  758. /**
  759. * A transport that uses a WorkerDebuggerGlobalScope to send packets from a
  760. * worker thread to the main thread.
  761. */
  762. function WorkerDebuggerTransport(scope, id) {
  763. this._scope = scope;
  764. this._id = id;
  765. this._onMessage = this._onMessage.bind(this);
  766. }
  767. WorkerDebuggerTransport.prototype = {
  768. constructor: WorkerDebuggerTransport,
  769. ready: function () {
  770. this._scope.addEventListener("message", this._onMessage);
  771. },
  772. close: function () {
  773. this._scope.removeEventListener("message", this._onMessage);
  774. if (this.hooks) {
  775. this.hooks.onClosed();
  776. }
  777. },
  778. send: function (packet) {
  779. this._scope.postMessage(JSON.stringify({
  780. type: "message",
  781. id: this._id,
  782. message: packet
  783. }));
  784. },
  785. startBulkSend: function () {
  786. throw new Error("Can't send bulk data from worker threads!");
  787. },
  788. _onMessage: function (event) {
  789. let packet = JSON.parse(event.data);
  790. if (packet.type !== "message" || packet.id !== this._id) {
  791. return;
  792. }
  793. if (this.hooks) {
  794. this.hooks.onPacket(packet.message);
  795. }
  796. }
  797. };
  798. exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
  799. }).call(this);
  800. }
  801. });