Backed out 10 changesets (bug 1803810) for xpcshell failures on test_import_global...
[gecko.git] / devtools / client / devtools-client.js
blobfb4a5a99891bb264359dd339a33b0e9109a6d89c
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/. */
5 "use strict";
7 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
8 const {
9   getStack,
10   callFunctionWithAsyncStack,
11 } = require("resource://devtools/shared/platform/stack.js");
12 const EventEmitter = require("resource://devtools/shared/event-emitter.js");
13 const {
14   UnsolicitedNotifications,
15 } = require("resource://devtools/client/constants.js");
17 loader.lazyRequireGetter(
18   this,
19   "Authentication",
20   "resource://devtools/shared/security/auth.js"
22 loader.lazyRequireGetter(
23   this,
24   "DebuggerSocket",
25   "resource://devtools/shared/security/socket.js",
26   true
28 loader.lazyRequireGetter(
29   this,
30   "EventEmitter",
31   "resource://devtools/shared/event-emitter.js"
34 loader.lazyRequireGetter(
35   this,
36   ["createRootFront", "Front"],
37   "resource://devtools/shared/protocol.js",
38   true
41 loader.lazyRequireGetter(
42   this,
43   "ObjectFront",
44   "resource://devtools/client/fronts/object.js",
45   true
48 /**
49  * Creates a client for the remote debugging protocol server. This client
50  * provides the means to communicate with the server and exchange the messages
51  * required by the protocol in a traditional JavaScript API.
52  */
53 function DevToolsClient(transport) {
54   this._transport = transport;
55   this._transport.hooks = this;
57   this._pendingRequests = new Map();
58   this._activeRequests = new Map();
59   this._eventsEnabled = true;
61   this.traits = {};
63   this.request = this.request.bind(this);
65   /*
66    * As the first thing on the connection, expect a greeting packet from
67    * the connection's root actor.
68    */
69   this.mainRoot = null;
70   this.expectReply("root", packet => {
71     if (packet.error) {
72       console.error("Error when waiting for root actor", packet);
73       return;
74     }
76     this.mainRoot = createRootFront(this, packet);
78     this.emit("connected", packet.applicationType, packet.traits);
79   });
82 // Expose these to save callers the trouble of importing DebuggerSocket
83 DevToolsClient.socketConnect = function (options) {
84   // Defined here instead of just copying the function to allow lazy-load
85   return DebuggerSocket.connect(options);
87 DevToolsUtils.defineLazyGetter(DevToolsClient, "Authenticators", () => {
88   return Authentication.Authenticators;
89 });
90 DevToolsUtils.defineLazyGetter(DevToolsClient, "AuthenticationResult", () => {
91   return Authentication.AuthenticationResult;
92 });
94 DevToolsClient.prototype = {
95   /**
96    * Connect to the server and start exchanging protocol messages.
97    *
98    * @return Promise
99    *         Resolves once connected with an array whose first element
100    *         is the application type, by default "browser", and the second
101    *         element is the traits object (help figure out the features
102    *         and behaviors of the server we connect to. See RootActor).
103    */
104   connect() {
105     return new Promise(resolve => {
106       this.once("connected", (applicationType, traits) => {
107         this.traits = traits;
108         resolve([applicationType, traits]);
109       });
111       this._transport.ready();
112     });
113   },
115   /**
116    * Shut down communication with the debugging server.
117    *
118    * @return Promise
119    *         Resolves after the underlying transport is closed.
120    */
121   close() {
122     if (this._transportClosed) {
123       return Promise.resolve();
124     }
125     if (this._closePromise) {
126       return this._closePromise;
127     }
128     // Immediately set the destroy promise,
129     // as the following code is fully synchronous and can be reentrant.
130     this._closePromise = this.once("closed");
132     // Disable detach event notifications, because event handlers will be in a
133     // cleared scope by the time they run.
134     this._eventsEnabled = false;
136     if (this._transport) {
137       this._transport.close();
138       this._transport = null;
139     }
141     return this._closePromise;
142   },
144   /**
145    * Send a request to the debugging server.
146    *
147    * @param packet object
148    *        A JSON packet to send to the debugging server.
149    * @return Request
150    *         This object emits a number of events to allow you to respond to
151    *         different parts of the request lifecycle.
152    *         It is also a Promise object, with a `then` method, that is resolved
153    *         whenever a JSON or a Bulk response is received; and is rejected
154    *         if the response is an error.
155    *
156    *         Events emitted:
157    *         * json-reply: The server replied with a JSON packet, which is
158    *           passed as event data.
159    *         * bulk-reply: The server replied with bulk data, which you can read
160    *           using the event data object containing:
161    *           * actor:  Name of actor that received the packet
162    *           * type:   Name of actor's method that was called on receipt
163    *           * length: Size of the data to be read
164    *           * stream: This input stream should only be used directly if you
165    *                     can ensure that you will read exactly |length| bytes
166    *                     and will not close the stream when reading is complete
167    *           * done:   If you use the stream directly (instead of |copyTo|
168    *                     below), you must signal completion by resolving /
169    *                     rejecting this promise.  If it's rejected, the
170    *                     transport will be closed.  If an Error is supplied as a
171    *                     rejection value, it will be logged via |dumpn|.  If you
172    *                     do use |copyTo|, resolving is taken care of for you
173    *                     when copying completes.
174    *           * copyTo: A helper function for getting your data out of the
175    *                     stream that meets the stream handling requirements
176    *                     above, and has the following signature:
177    *             @param  output nsIAsyncOutputStream
178    *                     The stream to copy to.
179    *             @return Promise
180    *                     The promise is resolved when copying completes or
181    *                     rejected if any (unexpected) errors occur.
182    *                     This object also emits "progress" events for each chunk
183    *                     that is copied.  See stream-utils.js.
184    */
185   request(packet) {
186     if (!this.mainRoot) {
187       throw Error("Have not yet received a hello packet from the server.");
188     }
189     const type = packet.type || "";
190     if (!packet.to) {
191       throw Error("'" + type + "' request packet has no destination.");
192     }
194     if (this._transportClosed) {
195       const msg =
196         "'" +
197         type +
198         "' request packet to " +
199         "'" +
200         packet.to +
201         "' " +
202         "can't be sent as the connection is closed.";
203       return Promise.reject({ error: "connectionClosed", message: msg });
204     }
206     const request = new Request(packet);
207     request.format = "json";
208     request.stack = getStack();
210     // Implement a Promise like API on the returned object
211     // that resolves/rejects on request response
212     const promise = new Promise((resolve, reject) => {
213       function listenerJson(resp) {
214         removeRequestListeners();
215         if (resp.error) {
216           reject(resp);
217         } else {
218           resolve(resp);
219         }
220       }
221       function listenerBulk(resp) {
222         removeRequestListeners();
223         resolve(resp);
224       }
226       const removeRequestListeners = () => {
227         request.off("json-reply", listenerJson);
228         request.off("bulk-reply", listenerBulk);
229       };
231       request.on("json-reply", listenerJson);
232       request.on("bulk-reply", listenerBulk);
233     });
235     this._sendOrQueueRequest(request);
236     request.then = promise.then.bind(promise);
237     request.catch = promise.catch.bind(promise);
239     return request;
240   },
242   /**
243    * Transmit streaming data via a bulk request.
244    *
245    * This method initiates the bulk send process by queuing up the header data.
246    * The caller receives eventual access to a stream for writing.
247    *
248    * Since this opens up more options for how the server might respond (it could
249    * send back either JSON or bulk data), and the returned Request object emits
250    * events for different stages of the request process that you may want to
251    * react to.
252    *
253    * @param request Object
254    *        This is modeled after the format of JSON packets above, but does not
255    *        actually contain the data, but is instead just a routing header:
256    *          * actor:  Name of actor that will receive the packet
257    *          * type:   Name of actor's method that should be called on receipt
258    *          * length: Size of the data to be sent
259    * @return Request
260    *         This object emits a number of events to allow you to respond to
261    *         different parts of the request lifecycle.
262    *
263    *         Events emitted:
264    *         * bulk-send-ready: Ready to send bulk data to the server, using the
265    *           event data object containing:
266    *           * stream:   This output stream should only be used directly if
267    *                       you can ensure that you will write exactly |length|
268    *                       bytes and will not close the stream when writing is
269    *                       complete
270    *           * done:     If you use the stream directly (instead of |copyFrom|
271    *                       below), you must signal completion by resolving /
272    *                       rejecting this promise.  If it's rejected, the
273    *                       transport will be closed.  If an Error is supplied as
274    *                       a rejection value, it will be logged via |dumpn|.  If
275    *                       you do use |copyFrom|, resolving is taken care of for
276    *                       you when copying completes.
277    *           * copyFrom: A helper function for getting your data onto the
278    *                       stream that meets the stream handling requirements
279    *                       above, and has the following signature:
280    *             @param  input nsIAsyncInputStream
281    *                     The stream to copy from.
282    *             @return Promise
283    *                     The promise is resolved when copying completes or
284    *                     rejected if any (unexpected) errors occur.
285    *                     This object also emits "progress" events for each chunk
286    *                     that is copied.  See stream-utils.js.
287    *         * json-reply: The server replied with a JSON packet, which is
288    *           passed as event data.
289    *         * bulk-reply: The server replied with bulk data, which you can read
290    *           using the event data object containing:
291    *           * actor:  Name of actor that received the packet
292    *           * type:   Name of actor's method that was called on receipt
293    *           * length: Size of the data to be read
294    *           * stream: This input stream should only be used directly if you
295    *                     can ensure that you will read exactly |length| bytes
296    *                     and will not close the stream when reading is complete
297    *           * done:   If you use the stream directly (instead of |copyTo|
298    *                     below), you must signal completion by resolving /
299    *                     rejecting this promise.  If it's rejected, the
300    *                     transport will be closed.  If an Error is supplied as a
301    *                     rejection value, it will be logged via |dumpn|.  If you
302    *                     do use |copyTo|, resolving is taken care of for you
303    *                     when copying completes.
304    *           * copyTo: A helper function for getting your data out of the
305    *                     stream that meets the stream handling requirements
306    *                     above, and has the following signature:
307    *             @param  output nsIAsyncOutputStream
308    *                     The stream to copy to.
309    *             @return Promise
310    *                     The promise is resolved when copying completes or
311    *                     rejected if any (unexpected) errors occur.
312    *                     This object also emits "progress" events for each chunk
313    *                     that is copied.  See stream-utils.js.
314    */
315   startBulkRequest(request) {
316     if (!this.mainRoot) {
317       throw Error("Have not yet received a hello packet from the server.");
318     }
319     if (!request.type) {
320       throw Error("Bulk packet is missing the required 'type' field.");
321     }
322     if (!request.actor) {
323       throw Error("'" + request.type + "' bulk packet has no destination.");
324     }
325     if (!request.length) {
326       throw Error("'" + request.type + "' bulk packet has no length.");
327     }
329     request = new Request(request);
330     request.format = "bulk";
332     this._sendOrQueueRequest(request);
334     return request;
335   },
337   /**
338    * If a new request can be sent immediately, do so.  Otherwise, queue it.
339    */
340   _sendOrQueueRequest(request) {
341     const actor = request.actor;
342     if (!this._activeRequests.has(actor)) {
343       this._sendRequest(request);
344     } else {
345       this._queueRequest(request);
346     }
347   },
349   /**
350    * Send a request.
351    * @throws Error if there is already an active request in flight for the same
352    *         actor.
353    */
354   _sendRequest(request) {
355     const actor = request.actor;
356     this.expectReply(actor, request);
358     if (request.format === "json") {
359       this._transport.send(request.request);
360       return;
361     }
363     this._transport.startBulkSend(request.request).then((...args) => {
364       request.emit("bulk-send-ready", ...args);
365     });
366   },
368   /**
369    * Queue a request to be sent later.  Queues are only drained when an in
370    * flight request to a given actor completes.
371    */
372   _queueRequest(request) {
373     const actor = request.actor;
374     const queue = this._pendingRequests.get(actor) || [];
375     queue.push(request);
376     this._pendingRequests.set(actor, queue);
377   },
379   /**
380    * Attempt the next request to a given actor (if any).
381    */
382   _attemptNextRequest(actor) {
383     if (this._activeRequests.has(actor)) {
384       return;
385     }
386     const queue = this._pendingRequests.get(actor);
387     if (!queue) {
388       return;
389     }
390     const request = queue.shift();
391     if (queue.length === 0) {
392       this._pendingRequests.delete(actor);
393     }
394     this._sendRequest(request);
395   },
397   /**
398    * Arrange to hand the next reply from |actor| to the handler bound to
399    * |request|.
400    *
401    * DevToolsClient.prototype.request / startBulkRequest usually takes care of
402    * establishing the handler for a given request, but in rare cases (well,
403    * greetings from new root actors, is the only case at the moment) we must be
404    * prepared for a "reply" that doesn't correspond to any request we sent.
405    */
406   expectReply(actor, request) {
407     if (this._activeRequests.has(actor)) {
408       throw Error("clashing handlers for next reply from " + actor);
409     }
411     // If a handler is passed directly (as it is with the handler for the root
412     // actor greeting), create a dummy request to bind this to.
413     if (typeof request === "function") {
414       const handler = request;
415       request = new Request();
416       request.on("json-reply", handler);
417     }
419     this._activeRequests.set(actor, request);
420   },
422   // Transport hooks.
424   /**
425    * Called by DebuggerTransport to dispatch incoming packets as appropriate.
426    *
427    * @param packet object
428    *        The incoming packet.
429    */
430   onPacket(packet) {
431     if (!packet.from) {
432       DevToolsUtils.reportException(
433         "onPacket",
434         new Error(
435           "Server did not specify an actor, dropping packet: " +
436             JSON.stringify(packet)
437         )
438       );
439       return;
440     }
442     // Check for "forwardingCancelled" here instead of using a front to handle it.
443     // This is necessary because we might receive this event while the client is closing,
444     // and the fronts have already been removed by that point.
445     if (
446       this.mainRoot &&
447       packet.from == this.mainRoot.actorID &&
448       packet.type == "forwardingCancelled"
449     ) {
450       this.purgeRequests(packet.prefix);
451       return;
452     }
454     // If we have a registered Front for this actor, let it handle the packet
455     // and skip all the rest of this unpleasantness.
456     const front = this.getFrontByID(packet.from);
457     if (front) {
458       front.onPacket(packet);
459       return;
460     }
462     let activeRequest;
463     // See if we have a handler function waiting for a reply from this
464     // actor. (Don't count unsolicited notifications or pauses as
465     // replies.)
466     if (
467       this._activeRequests.has(packet.from) &&
468       !(packet.type in UnsolicitedNotifications)
469     ) {
470       activeRequest = this._activeRequests.get(packet.from);
471       this._activeRequests.delete(packet.from);
472     }
474     // If there is a subsequent request for the same actor, hand it off to the
475     // transport.  Delivery of packets on the other end is always async, even
476     // in the local transport case.
477     this._attemptNextRequest(packet.from);
479     // Only try to notify listeners on events, not responses to requests
480     // that lack a packet type.
481     if (packet.type) {
482       this.emit(packet.type, packet);
483     }
485     if (activeRequest) {
486       const emitReply = () => activeRequest.emit("json-reply", packet);
487       if (activeRequest.stack) {
488         callFunctionWithAsyncStack(
489           emitReply,
490           activeRequest.stack,
491           "DevTools RDP"
492         );
493       } else {
494         emitReply();
495       }
496     }
497   },
499   /**
500    * Called by the DebuggerTransport to dispatch incoming bulk packets as
501    * appropriate.
502    *
503    * @param packet object
504    *        The incoming packet, which contains:
505    *        * actor:  Name of actor that will receive the packet
506    *        * type:   Name of actor's method that should be called on receipt
507    *        * length: Size of the data to be read
508    *        * stream: This input stream should only be used directly if you can
509    *                  ensure that you will read exactly |length| bytes and will
510    *                  not close the stream when reading is complete
511    *        * done:   If you use the stream directly (instead of |copyTo|
512    *                  below), you must signal completion by resolving /
513    *                  rejecting this promise.  If it's rejected, the transport
514    *                  will be closed.  If an Error is supplied as a rejection
515    *                  value, it will be logged via |dumpn|.  If you do use
516    *                  |copyTo|, resolving is taken care of for you when copying
517    *                  completes.
518    *        * copyTo: A helper function for getting your data out of the stream
519    *                  that meets the stream handling requirements above, and has
520    *                  the following signature:
521    *          @param  output nsIAsyncOutputStream
522    *                  The stream to copy to.
523    *          @return Promise
524    *                  The promise is resolved when copying completes or rejected
525    *                  if any (unexpected) errors occur.
526    *                  This object also emits "progress" events for each chunk
527    *                  that is copied.  See stream-utils.js.
528    */
529   onBulkPacket(packet) {
530     const { actor } = packet;
532     if (!actor) {
533       DevToolsUtils.reportException(
534         "onBulkPacket",
535         new Error(
536           "Server did not specify an actor, dropping bulk packet: " +
537             JSON.stringify(packet)
538         )
539       );
540       return;
541     }
543     // See if we have a handler function waiting for a reply from this
544     // actor.
545     if (!this._activeRequests.has(actor)) {
546       return;
547     }
549     const activeRequest = this._activeRequests.get(actor);
550     this._activeRequests.delete(actor);
552     // If there is a subsequent request for the same actor, hand it off to the
553     // transport.  Delivery of packets on the other end is always async, even
554     // in the local transport case.
555     this._attemptNextRequest(actor);
557     activeRequest.emit("bulk-reply", packet);
558   },
560   /**
561    * Called by DebuggerTransport when the underlying stream is closed.
562    *
563    * @param status nsresult
564    *        The status code that corresponds to the reason for closing
565    *        the stream.
566    */
567   onTransportClosed() {
568     if (this._transportClosed) {
569       return;
570     }
571     this._transportClosed = true;
572     this.emit("closed");
574     this.purgeRequests();
576     // The |_pools| array on the client-side currently is used only by
577     // protocol.js to store active fronts, mirroring the actor pools found in
578     // the server.  So, read all usages of "pool" as "protocol.js front".
579     //
580     // In the normal case where we shutdown cleanly, the toolbox tells each tool
581     // to close, and they each call |destroy| on any fronts they were using.
582     // When |destroy| is called on a protocol.js front, it also
583     // removes itself from the |_pools| array.  Once the toolbox has shutdown,
584     // the connection is closed, and we reach here.  All fronts (should have
585     // been) |destroy|ed, so |_pools| should empty.
586     //
587     // If the connection instead aborts unexpectedly, we may end up here with
588     // all fronts used during the life of the connection.  So, we call |destroy|
589     // on them clear their state, reject pending requests, and remove themselves
590     // from |_pools|.  This saves the toolbox from hanging indefinitely, in case
591     // it waits for some server response before shutdown that will now never
592     // arrive.
593     for (const pool of this._pools) {
594       pool.destroy();
595     }
596   },
598   /**
599    * Purge pending and active requests in this client.
600    *
601    * @param prefix string (optional)
602    *        If a prefix is given, only requests for actor IDs that start with the prefix
603    *        will be cleaned up.  This is useful when forwarding of a portion of requests
604    *        is cancelled on the server.
605    */
606   purgeRequests(prefix = "") {
607     const reject = function (type, request) {
608       // Server can send packets on its own and client only pass a callback
609       // to expectReply, so that there is no request object.
610       let msg;
611       if (request.request) {
612         msg =
613           "'" +
614           request.request.type +
615           "' " +
616           type +
617           " request packet" +
618           " to '" +
619           request.actor +
620           "' " +
621           "can't be sent as the connection just closed.";
622       } else {
623         msg =
624           "server side packet can't be received as the connection just closed.";
625       }
626       const packet = { error: "connectionClosed", message: msg };
627       request.emit("json-reply", packet);
628     };
630     let pendingRequestsToReject = [];
631     this._pendingRequests.forEach((requests, actor) => {
632       if (!actor.startsWith(prefix)) {
633         return;
634       }
635       this._pendingRequests.delete(actor);
636       pendingRequestsToReject = pendingRequestsToReject.concat(requests);
637     });
638     pendingRequestsToReject.forEach(request => reject("pending", request));
640     let activeRequestsToReject = [];
641     this._activeRequests.forEach((request, actor) => {
642       if (!actor.startsWith(prefix)) {
643         return;
644       }
645       this._activeRequests.delete(actor);
646       activeRequestsToReject = activeRequestsToReject.concat(request);
647     });
648     activeRequestsToReject.forEach(request => reject("active", request));
650     // Also purge protocol.js requests
651     const fronts = this.getAllFronts();
653     for (const front of fronts) {
654       if (!front.isDestroyed() && front.actorID.startsWith(prefix)) {
655         // Call Front.baseFrontClassDestroy nstead of Front.destroy in order to flush requests
656         // and nullify front.actorID immediately, even if Front.destroy is overloaded
657         // by an async function which would otherwise be able to try emitting new request
658         // after the purge.
659         front.baseFrontClassDestroy();
660       }
661     }
662   },
664   /**
665    * Search for all requests in process for this client, including those made via
666    * protocol.js and wait all of them to complete.  Since the requests seen when this is
667    * first called may in turn trigger more requests, we keep recursing through this
668    * function until there is no more activity.
669    *
670    * This is a fairly heavy weight process, so it's only meant to be used in tests.
671    *
672    * @return Promise
673    *         Resolved when all requests have settled.
674    */
675   waitForRequestsToSettle() {
676     let requests = [];
678     // Gather all pending and active requests in this client
679     // The request object supports a Promise API for completion (it has .then())
680     this._pendingRequests.forEach(requestsForActor => {
681       // Each value is an array of pending requests
682       requests = requests.concat(requestsForActor);
683     });
684     this._activeRequests.forEach(requestForActor => {
685       // Each value is a single active request
686       requests = requests.concat(requestForActor);
687     });
689     // protocol.js
690     const fronts = this.getAllFronts();
692     // For each front, wait for its requests to settle
693     for (const front of fronts) {
694       if (front.hasRequests()) {
695         requests.push(front.waitForRequestsToSettle());
696       }
697     }
699     // Abort early if there are no requests
700     if (!requests.length) {
701       return Promise.resolve();
702     }
704     return DevToolsUtils.settleAll(requests)
705       .catch(() => {
706         // One of the requests might have failed, but ignore that situation here and pipe
707         // both success and failure through the same path.  The important part is just that
708         // we waited.
709       })
710       .then(() => {
711         // Repeat, more requests may have started in response to those we just waited for
712         return this.waitForRequestsToSettle();
713       });
714   },
716   getAllFronts() {
717     // Use a Set because some fronts (like domwalker) seem to have multiple parents.
718     const fronts = new Set();
719     const poolsToVisit = [...this._pools];
721     // With protocol.js, each front can potentially have its own pools containing child
722     // fronts, forming a tree.  Descend through all the pools to locate all child fronts.
723     while (poolsToVisit.length) {
724       const pool = poolsToVisit.shift();
725       // `_pools` contains either Fronts or Pools, we only want to collect Fronts here.
726       // Front inherits from Pool which exposes `poolChildren`.
727       if (pool instanceof Front) {
728         fronts.add(pool);
729       }
730       for (const child of pool.poolChildren()) {
731         poolsToVisit.push(child);
732       }
733     }
734     return fronts;
735   },
737   /**
738    * Actor lifetime management, echos the server's actor pools.
739    */
740   __pools: null,
741   get _pools() {
742     if (this.__pools) {
743       return this.__pools;
744     }
745     this.__pools = new Set();
746     return this.__pools;
747   },
749   addActorPool(pool) {
750     this._pools.add(pool);
751   },
752   removeActorPool(pool) {
753     this._pools.delete(pool);
754   },
756   /**
757    * Return the Front for the Actor whose ID is the one passed in argument.
758    *
759    * @param {String} actorID: The actor ID to look for.
760    */
761   getFrontByID(actorID) {
762     const pool = this.poolFor(actorID);
763     return pool ? pool.getActorByID(actorID) : null;
764   },
766   poolFor(actorID) {
767     for (const pool of this._pools) {
768       if (pool.has(actorID)) {
769         return pool;
770       }
771     }
772     return null;
773   },
775   /**
776    * Creates an object front for this DevToolsClient and the grip in parameter,
777    * @param {Object} grip: The grip to create the ObjectFront for.
778    * @param {ThreadFront} threadFront
779    * @param {Front} parentFront: Optional front that will manage the object front.
780    *                             Defaults to threadFront.
781    * @returns {ObjectFront}
782    */
783   createObjectFront(grip, threadFront, parentFront) {
784     if (!parentFront) {
785       parentFront = threadFront;
786     }
788     return new ObjectFront(this, threadFront.targetFront, parentFront, grip);
789   },
791   get transport() {
792     return this._transport;
793   },
795   dumpPools() {
796     for (const pool of this._pools) {
797       console.log(`%c${pool.actorID}`, "font-weight: bold;", [
798         ...pool.__poolMap.keys(),
799       ]);
800     }
801   },
804 EventEmitter.decorate(DevToolsClient.prototype);
806 class Request extends EventEmitter {
807   constructor(request) {
808     super();
809     this.request = request;
810   }
812   get actor() {
813     return this.request.to || this.request.actor;
814   }
817 module.exports = {
818   DevToolsClient,