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/. */
7 var { settleAll } = require("devtools/shared/DevToolsUtils");
8 var EventEmitter = require("devtools/shared/event-emitter");
10 var { Pool } = require("./Pool");
13 callFunctionWithAsyncStack,
14 } = require("devtools/shared/platform/stack");
15 // Bug 1454373: devtools/shared/defer still uses Promise.jsm which is slower
16 // than DOM Promises. So implement our own copy of `defer` based on DOM Promises.
19 const promise = new Promise(function() {
20 resolve = arguments[0];
21 reject = arguments[1];
31 * Base class for client-side actor fronts.
33 * @param optional conn
34 * Either a DebuggerServerConnection or a DebuggerClient. Must have
35 * addActorPool, removeActorPool, and poolFor.
36 * conn can be null if the subclass provides a conn property.
39 class Front extends Pool {
40 constructor(conn = null) {
43 // The targetFront attribute represents the debuggable context. Only target-scoped
44 // fronts and their children fronts will have the targetFront attribute set.
45 this.targetFront = null;
48 // Front listener functions registered via `onFront` get notified
49 // of new fronts via this dedicated EventEmitter object.
50 this._frontListeners = new EventEmitter();
52 // List of optional listener for each event, that is processed immediatly on packet
53 // receival, before emitting event via EventEmitter on the Front.
54 // These listeners are register via Front.before function.
55 // Map(Event Name[string] => Event Listener[function])
56 this._beforeListeners = new Map();
60 // Reject all outstanding requests, they won't make sense after
61 // the front is destroyed.
62 while (this._requests && this._requests.length > 0) {
63 const { deferred, to, type, stack } = this._requests.shift();
65 "Connection closed, pending request to " +
70 "\n\nRequest stack:\n" +
72 deferred.reject(new Error(msg));
77 this._frontListeners = null;
78 this._beforeListeners = null;
84 "Can't manage front without an actor ID.\n" +
85 "Ensure server supports " +
92 // Call listeners registered via `onFront` method
93 this._frontListeners.emit(front.typeName, front);
96 // Run callback on every front of this type that currently exists, and on every
97 // instantiation of front type in the future.
98 onFront(typeName, callback) {
99 // First fire the callback on already instantiated fronts
100 for (const front of this.poolChildren()) {
101 if (front.typeName == typeName) {
105 // Then register the callback for fronts instantiated in the future
106 this._frontListeners.on(typeName, callback);
110 * Register an event listener that will be called immediately on packer receival.
111 * The given callback is going to be called before emitting the event via EventEmitter
112 * API on the Front. Event emitting will be delayed if the callback is async.
113 * Only one such listener can be registered per type of event.
116 * Event emitted by the actor to intercept.
117 * @param Function callback
118 * Function that will process the event.
120 before(type, callback) {
121 if (this._beforeListeners.has(type)) {
123 `Can't register multiple before listeners for "${type}".`
126 this._beforeListeners.set(type, callback);
130 return "[Front for " + this.typeName + "/" + this.actorID + "]";
134 * Update the actor from its representation.
135 * Subclasses should override this.
140 * Send a packet on the connection.
144 this.conn._transport.send(packet);
146 packet.to = this.actorID;
147 // The connection might be closed during the promise resolution
148 if (this.conn._transport) {
149 this.conn._transport.send(packet);
155 * Send a two-way request on the connection.
158 const deferred = defer();
159 // Save packet basics for debugging
160 const { to, type } = packet;
161 this._requests.push({
163 to: to || this.actorID,
168 return deferred.promise;
172 * Handler for incoming packets from the client's actor.
175 // Pick off event packets
176 const type = packet.type || undefined;
177 if (this._clientSpec.events && this._clientSpec.events.has(type)) {
178 const event = this._clientSpec.events.get(packet.type);
181 args = event.request.read(packet, this);
183 console.error("Error reading event: " + packet.type);
184 console.exception(ex);
187 // Check for "pre event" callback to be processed before emitting events on fronts
188 // Use event.name instead of packet.type to use specific event name instead of RDP
190 const beforeEvent = this._beforeListeners.get(event.name);
192 const result = beforeEvent.apply(this, args);
193 // Check to see if the beforeEvent returned a promise -- if so,
194 // wait for their resolution before emitting. Otherwise, emit synchronously.
195 if (result && typeof result.then == "function") {
197 super.emit(event.name, ...args);
203 super.emit(event.name, ...args);
207 // Remaining packets must be responses.
208 if (this._requests.length === 0) {
210 "Unexpected packet " + this.actorID + ", " + JSON.stringify(packet);
211 const err = Error(msg);
216 const { deferred, stack } = this._requests.shift();
217 callFunctionWithAsyncStack(
220 // "Protocol error" is here to avoid TBPL heuristics. See also
221 // https://dxr.mozilla.org/webtools-central/source/tbpl/php/inc/GeneralErrorFilter.php
223 if (packet.error && packet.message) {
225 "Protocol error (" + packet.error + "): " + packet.message;
227 message = packet.error;
229 deferred.reject(message);
231 deferred.resolve(packet);
240 return !!this._requests.length;
244 * Wait for all current requests from this front to settle. This is especially useful
245 * for tests and other utility environments that may not have events or mechanisms to
246 * await the completion of requests without this utility.
249 * Resolved when all requests have settled.
251 waitForRequestsToSettle() {
252 return settleAll(this._requests.map(({ deferred }) => deferred.promise));
256 exports.Front = Front;