Bug 1622408 [wpt PR 22244] - Restore the event delegate for a CSSTransition after...
[gecko.git] / devtools / shared / fronts / webconsole.js
blobe8b2f1a4d1795fa3ec73b62701754e9f383daa99
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("devtools/shared/DevToolsUtils");
8 const { LongStringFront } = require("devtools/shared/fronts/string");
9 const {
10   FrontClassWithSpec,
11   registerFront,
12 } = require("devtools/shared/protocol");
13 const { webconsoleSpec } = require("devtools/shared/specs/webconsole");
14 const {
15   getAdHocFrontOrPrimitiveGrip,
16 } = require("devtools/shared/fronts/object");
18 /**
19  * A WebConsoleFront is used as a front end for the WebConsoleActor that is
20  * created on the server, hiding implementation details.
21  *
22  * @param object client
23  *        The DevToolsClient instance we live for.
24  */
25 class WebConsoleFront extends FrontClassWithSpec(webconsoleSpec) {
26   constructor(client, targetFront, parentFront) {
27     super(client, targetFront, parentFront);
28     this._client = client;
29     this.traits = {};
30     this._longStrings = {};
31     this.events = [];
33     // Attribute name from which to retrieve the actorID out of the target actor's form
34     this.formAttributeName = "consoleActor";
35     /**
36      * Holds the network requests currently displayed by the Web Console. Each key
37      * represents the connection ID and the value is network request information.
38      * @private
39      * @type object
40      */
41     this._networkRequests = new Map();
43     this.pendingEvaluationResults = new Map();
44     this.onEvaluationResult = this.onEvaluationResult.bind(this);
45     this.onNetworkEvent = this._onNetworkEvent.bind(this);
46     this.onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
48     this.on("evaluationResult", this.onEvaluationResult);
49     this.on("serverNetworkEvent", this.onNetworkEvent);
50     this.before("consoleAPICall", this.beforeConsoleAPICall);
51     this.before("pageError", this.beforePageError);
53     this._client.on("networkEventUpdate", this.onNetworkEventUpdate);
54   }
56   getNetworkRequest(actorId) {
57     return this._networkRequests.get(actorId);
58   }
60   getNetworkEvents() {
61     return this._networkRequests.values();
62   }
64   get actor() {
65     return this.actorID;
66   }
68   /**
69    * The "networkEvent" message type handler. We redirect any message to
70    * the UI for displaying.
71    *
72    * @private
73    * @param string type
74    *        Message type.
75    * @param object packet
76    *        The message received from the server.
77    */
78   _onNetworkEvent(packet) {
79     const actor = packet.eventActor;
80     const networkInfo = {
81       _type: "NetworkEvent",
82       timeStamp: actor.timeStamp,
83       node: null,
84       actor: actor.actor,
85       discardRequestBody: true,
86       discardResponseBody: true,
87       startedDateTime: actor.startedDateTime,
88       request: {
89         url: actor.url,
90         method: actor.method,
91       },
92       isXHR: actor.isXHR,
93       cause: actor.cause,
94       response: {},
95       timings: {},
96       // track the list of network event updates
97       updates: [],
98       private: actor.private,
99       fromCache: actor.fromCache,
100       fromServiceWorker: actor.fromServiceWorker,
101       isThirdPartyTrackingResource: actor.isThirdPartyTrackingResource,
102       referrerPolicy: actor.referrerPolicy,
103       blockedReason: actor.blockedReason,
104       channelId: actor.channelId,
105     };
106     this._networkRequests.set(actor.actor, networkInfo);
108     this.emit("networkEvent", networkInfo);
109   }
111   /**
112    * The "networkEventUpdate" message type handler. We redirect any message to
113    * the UI for displaying.
114    *
115    * @private
116    * @param string type
117    *        Message type.
118    * @param object packet
119    *        The message received from the server.
120    */
121   _onNetworkEventUpdate(packet) {
122     const networkInfo = this.getNetworkRequest(packet.from);
123     if (!networkInfo) {
124       return;
125     }
127     networkInfo.updates.push(packet.updateType);
129     switch (packet.updateType) {
130       case "requestHeaders":
131         networkInfo.request.headersSize = packet.headersSize;
132         break;
133       case "requestPostData":
134         networkInfo.discardRequestBody = packet.discardRequestBody;
135         networkInfo.request.bodySize = packet.dataSize;
136         break;
137       case "responseStart":
138         networkInfo.response.httpVersion = packet.response.httpVersion;
139         networkInfo.response.status = packet.response.status;
140         networkInfo.response.statusText = packet.response.statusText;
141         networkInfo.response.headersSize = packet.response.headersSize;
142         networkInfo.response.remoteAddress = packet.response.remoteAddress;
143         networkInfo.response.remotePort = packet.response.remotePort;
144         networkInfo.discardResponseBody = packet.response.discardResponseBody;
145         break;
146       case "responseContent":
147         networkInfo.response.content = {
148           mimeType: packet.mimeType,
149         };
150         networkInfo.response.bodySize = packet.contentSize;
151         networkInfo.response.transferredSize = packet.transferredSize;
152         networkInfo.discardResponseBody = packet.discardResponseBody;
153         break;
154       case "eventTimings":
155         networkInfo.totalTime = packet.totalTime;
156         break;
157       case "securityInfo":
158         networkInfo.securityState = packet.state;
159         break;
160       case "responseCache":
161         networkInfo.response.responseCache = packet.responseCache;
162         break;
163     }
165     this.emit("networkEventUpdate", {
166       packet: packet,
167       networkInfo,
168     });
169   }
171   /**
172    * Evaluate a JavaScript expression asynchronously.
173    *
174    * @param {String} string: The code you want to evaluate.
175    * @param {Object} opts: Options for evaluation:
176    *
177    *        - {String} frameActor: a FrameActor ID. The FA holds a reference to
178    *        a Debugger.Frame. This option allows you to evaluate the string in
179    *        the frame of the given FA.
180    *
181    *        - {String} url: the url to evaluate the script as. Defaults to
182    *        "debugger eval code".
183    *
184    *        - {String} selectedNodeActor: the NodeActor ID of the current
185    *        selection in the Inspector, if such a selection
186    *        exists. This is used by helper functions that can
187    *        reference the currently selected node in the Inspector, like $0.
188    *
189    *        - {String} selectedObjectActor: the actorID of a given objectActor.
190    *        This is used by context menu entries to get a reference to an object, in order
191    *        to perform some operation on it (copy it, store it as a global variable, …).
192    *
193    * @return {Promise}: A promise that resolves with the response.
194    */
195   async evaluateJSAsync(string, opts = {}) {
196     const options = {
197       text: string,
198       frameActor: opts.frameActor,
199       url: opts.url,
200       selectedNodeActor: opts.selectedNodeActor,
201       selectedObjectActor: opts.selectedObjectActor,
202       mapped: opts.mapped,
203       eager: opts.eager,
204     };
206     this._pendingAsyncEvaluation = super.evaluateJSAsync(options);
207     const { resultID } = await this._pendingAsyncEvaluation;
208     this._pendingAsyncEvaluation = null;
210     return new Promise((resolve, reject) => {
211       // Null check this in case the client has been detached while sending
212       // the one way request
213       if (this.pendingEvaluationResults) {
214         this.pendingEvaluationResults.set(resultID, resp => {
215           if (resp.error) {
216             reject(resp);
217           } else {
218             if (resp.result) {
219               resp.result = getAdHocFrontOrPrimitiveGrip(resp.result, this);
220             }
222             if (resp.helperResult && resp.helperResult.object) {
223               resp.helperResult.object = getAdHocFrontOrPrimitiveGrip(
224                 resp.helperResult.object,
225                 this
226               );
227             }
229             if (resp.exception) {
230               resp.exception = getAdHocFrontOrPrimitiveGrip(
231                 resp.exception,
232                 this
233               );
234             }
236             if (resp.exceptionMessage) {
237               resp.exceptionMessage = getAdHocFrontOrPrimitiveGrip(
238                 resp.exceptionMessage,
239                 this
240               );
241             }
243             resolve(resp);
244           }
245         });
246       }
247     });
248   }
250   /**
251    * Handler for the actors's unsolicited evaluationResult packet.
252    */
253   async onEvaluationResult(packet) {
254     // In some cases, the evaluationResult event can be received before the initial call
255     // to evaluationJSAsync completes. So make sure to wait for the corresponding promise
256     // before handling the event.
257     await this._pendingAsyncEvaluation;
259     // Find the associated callback based on this ID, and fire it.
260     // In a sync evaluation, this would have already been called in
261     // direct response to the client.request function.
262     const onResponse = this.pendingEvaluationResults.get(packet.resultID);
263     if (onResponse) {
264       onResponse(packet);
265       this.pendingEvaluationResults.delete(packet.resultID);
266     } else {
267       DevToolsUtils.reportException(
268         "onEvaluationResult",
269         "No response handler for an evaluateJSAsync result (resultID: " +
270           packet.resultID +
271           ")"
272       );
273     }
274   }
276   beforeConsoleAPICall(packet) {
277     if (packet.message && Array.isArray(packet.message.arguments)) {
278       // We might need to create fronts for each of the message arguments.
279       packet.message.arguments = packet.message.arguments.map(arg =>
280         getAdHocFrontOrPrimitiveGrip(arg, this)
281       );
282     }
283     return packet;
284   }
286   beforePageError(packet) {
287     if (packet && packet.pageError && packet.pageError.errorMessage) {
288       packet.pageError.errorMessage = getAdHocFrontOrPrimitiveGrip(
289         packet.pageError.errorMessage,
290         this
291       );
292     }
293     return packet;
294   }
296   async getCachedMessages(messageTypes) {
297     const response = await super.getCachedMessages(messageTypes);
298     if (Array.isArray(response.messages)) {
299       response.messages = response.messages.map(message => {
300         if (!message || !Array.isArray(message.arguments)) {
301           return message;
302         }
304         // We might need to create fronts for each of the message arguments.
305         message.arguments = message.arguments.map(arg =>
306           getAdHocFrontOrPrimitiveGrip(arg, this)
307         );
308         return message;
309       });
310     }
311     return response;
312   }
314   /**
315    * Retrieve the request headers from the given NetworkEventActor.
316    *
317    * @param string actor
318    *        The NetworkEventActor ID.
319    * @param function onResponse
320    *        The function invoked when the response is received.
321    * @return request
322    *         Request object that implements both Promise and EventEmitter interfaces
323    */
324   getRequestHeaders(actor, onResponse) {
325     const packet = {
326       to: actor,
327       type: "getRequestHeaders",
328     };
329     return this._client.request(packet, onResponse);
330   }
332   /**
333    * Retrieve the request cookies from the given NetworkEventActor.
334    *
335    * @param string actor
336    *        The NetworkEventActor ID.
337    * @param function onResponse
338    *        The function invoked when the response is received.
339    * @return request
340    *         Request object that implements both Promise and EventEmitter interfaces
341    */
342   getRequestCookies(actor, onResponse) {
343     const packet = {
344       to: actor,
345       type: "getRequestCookies",
346     };
347     return this._client.request(packet, onResponse);
348   }
350   /**
351    * Retrieve the request post data from the given NetworkEventActor.
352    *
353    * @param string actor
354    *        The NetworkEventActor ID.
355    * @param function onResponse
356    *        The function invoked when the response is received.
357    * @return request
358    *         Request object that implements both Promise and EventEmitter interfaces
359    */
360   getRequestPostData(actor, onResponse) {
361     const packet = {
362       to: actor,
363       type: "getRequestPostData",
364     };
365     return this._client.request(packet, onResponse);
366   }
368   /**
369    * Retrieve the response headers from the given NetworkEventActor.
370    *
371    * @param string actor
372    *        The NetworkEventActor ID.
373    * @param function onResponse
374    *        The function invoked when the response is received.
375    * @return request
376    *         Request object that implements both Promise and EventEmitter interfaces
377    */
378   getResponseHeaders(actor, onResponse) {
379     const packet = {
380       to: actor,
381       type: "getResponseHeaders",
382     };
383     return this._client.request(packet, onResponse);
384   }
386   /**
387    * Retrieve the response cookies from the given NetworkEventActor.
388    *
389    * @param string actor
390    *        The NetworkEventActor ID.
391    * @param function onResponse
392    *        The function invoked when the response is received.
393    * @return request
394    *         Request object that implements both Promise and EventEmitter interfaces
395    */
396   getResponseCookies(actor, onResponse) {
397     const packet = {
398       to: actor,
399       type: "getResponseCookies",
400     };
401     return this._client.request(packet, onResponse);
402   }
404   /**
405    * Retrieve the response content from the given NetworkEventActor.
406    *
407    * @param string actor
408    *        The NetworkEventActor ID.
409    * @param function onResponse
410    *        The function invoked when the response is received.
411    * @return request
412    *         Request object that implements both Promise and EventEmitter interfaces
413    */
414   getResponseContent(actor, onResponse) {
415     const packet = {
416       to: actor,
417       type: "getResponseContent",
418     };
419     return this._client.request(packet, onResponse);
420   }
422   /**
423    * Retrieve the response cache from the given NetworkEventActor
424    *
425    * @param string actor
426    *        The NetworkEventActor ID.
427    * @param function onResponse
428    *        The function invoked when the response is received.
429    * @return request
430    *         Request object that implements both Promise and EventEmitter interfaces.
431    */
432   getResponseCache(actor, onResponse) {
433     const packet = {
434       to: actor,
435       type: "getResponseCache",
436     };
437     return this._client.request(packet, onResponse);
438   }
440   /**
441    * Retrieve the timing information for the given NetworkEventActor.
442    *
443    * @param string actor
444    *        The NetworkEventActor ID.
445    * @param function onResponse
446    *        The function invoked when the response is received.
447    * @return request
448    *         Request object that implements both Promise and EventEmitter interfaces
449    */
450   getEventTimings(actor, onResponse) {
451     const packet = {
452       to: actor,
453       type: "getEventTimings",
454     };
455     return this._client.request(packet, onResponse);
456   }
458   /**
459    * Retrieve the security information for the given NetworkEventActor.
460    *
461    * @param string actor
462    *        The NetworkEventActor ID.
463    * @param function onResponse
464    *        The function invoked when the response is received.
465    * @return request
466    *         Request object that implements both Promise and EventEmitter interfaces
467    */
468   getSecurityInfo(actor, onResponse) {
469     const packet = {
470       to: actor,
471       type: "getSecurityInfo",
472     };
473     return this._client.request(packet, onResponse);
474   }
476   /**
477    * Retrieve the stack-trace information for the given NetworkEventActor.
478    *
479    * @param string actor
480    *        The NetworkEventActor ID.
481    * @param function onResponse
482    *        The function invoked when the stack-trace is received.
483    * @return request
484    *         Request object that implements both Promise and EventEmitter interfaces
485    */
486   getStackTrace(actor, onResponse) {
487     const packet = {
488       to: actor,
489       type: "getStackTrace",
490     };
491     return this._client.request(packet, onResponse);
492   }
494   /**
495    * Start the given Web Console listeners.
496    * TODO: remove once the front is retrieved via getFront, and we use form()
497    *
498    * @see this.LISTENERS
499    * @param array listeners
500    *        Array of listeners you want to start. See this.LISTENERS for
501    *        known listeners.
502    * @return request
503    *         Request object that implements both Promise and EventEmitter interfaces
504    */
505   async startListeners(listeners) {
506     const response = await super.startListeners(listeners);
507     this.hasNativeConsoleAPI = response.nativeConsoleAPI;
508     this.traits = response.traits;
509     return response;
510   }
512   /**
513    * Return an instance of LongStringFront for the given long string grip.
514    *
515    * @param object grip
516    *        The long string grip returned by the protocol.
517    * @return {LongStringFront} the front for the given long string grip.
518    */
519   longString(grip) {
520     if (grip.actor in this._longStrings) {
521       return this._longStrings[grip.actor];
522     }
524     const front = new LongStringFront(this._client, this.targetFront, this);
525     front.form(grip);
526     this.manage(front);
527     this._longStrings[grip.actor] = front;
528     return front;
529   }
531   /**
532    * Fetches the full text of a LongString.
533    *
534    * @param object | string stringGrip
535    *        The long string grip containing the corresponding actor.
536    *        If you pass in a plain string (by accident or because you're lazy),
537    *        then a promise of the same string is simply returned.
538    * @return object Promise
539    *         A promise that is resolved when the full string contents
540    *         are available, or rejected if something goes wrong.
541    */
542   async getString(stringGrip) {
543     // Make sure this is a long string.
544     if (typeof stringGrip !== "object" || stringGrip.type !== "longString") {
545       // Go home string, you're drunk.
546       return stringGrip;
547     }
549     // Fetch the long string only once.
550     if (stringGrip._fullText) {
551       return stringGrip._fullText;
552     }
554     const { initial, length } = stringGrip;
555     const longStringFront = this.longString(stringGrip);
557     try {
558       const response = await longStringFront.substring(initial.length, length);
559       return initial + response;
560     } catch (e) {
561       DevToolsUtils.reportException("getString", e.message);
562       throw e;
563     }
564   }
566   clearNetworkRequests() {
567     // Prevent exception if the front has already been destroyed.
568     if (this._networkRequests) {
569       this._networkRequests.clear();
570     }
571   }
573   /**
574    * Close the WebConsoleFront.
575    *
576    */
577   destroy() {
578     if (!this._client) {
579       return null;
580     }
582     this._client.off("networkEventUpdate", this.onNetworkEventUpdate);
583     // This will make future calls to this function harmless because of the early return
584     // at the top of the function.
585     this._client = null;
587     this.off("evaluationResult", this.onEvaluationResult);
588     this.off("serverNetworkEvent", this.onNetworkEvent);
589     this._longStrings = null;
590     this.pendingEvaluationResults.clear();
591     this.pendingEvaluationResults = null;
592     this.clearNetworkRequests();
593     this._networkRequests = null;
594     return super.destroy();
595   }
598 exports.WebConsoleFront = WebConsoleFront;
599 registerFront(WebConsoleFront);