firefox-data-provider.js (28683B)
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 /* eslint-disable block-scoped-var */ 5 6 "use strict"; 7 8 const { 9 EVENTS, 10 TEST_EVENTS, 11 } = require("resource://devtools/client/netmonitor/src/constants.js"); 12 const { CurlUtils } = require("resource://devtools/client/shared/curl.js"); 13 const { 14 fetchHeaders, 15 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 16 17 const { 18 getLongStringFullText, 19 } = require("resource://devtools/client/shared/string-utils.js"); 20 21 /** 22 * This object is responsible for fetching additional HTTP 23 * data from the backend over RDP protocol. 24 * 25 * The object also keeps track of RDP requests in-progress, 26 * so it's possible to determine whether all has been fetched 27 * or not. 28 */ 29 class FirefoxDataProvider { 30 /** 31 * Constructor for data provider 32 * 33 * @param {object} commands Object defined from devtools/shared/commands to interact with the devtools backend 34 * @param {object} actions set of actions fired during data fetching process. 35 * @param {object} owner all events are fired on this object. 36 */ 37 constructor({ commands, actions, owner }) { 38 // Options 39 this.commands = commands; 40 this.actions = actions || {}; 41 this.actionsEnabled = true; 42 43 // Allow requesting of on-demand network data, this would be `false` when requests 44 // are cleared (as we clear also on the backend), and will be flipped back 45 // to true on the next `onNetworkResourceAvailable` call. 46 this._requestDataEnabled = true; 47 48 // `_requestDataEnabled` can only be used to prevent new calls to 49 // requestData. For pending/already started calls, we need to check if 50 // clear() was called during the call, which is the purpose of this counter. 51 this._lastRequestDataClearId = 0; 52 53 this.owner = owner; 54 55 // This holds stacktraces infomation temporarily. Stacktrace resources 56 // can come before or after (out of order) their related network events. 57 // This will hold stacktrace related info from the NETWORK_EVENT_STACKTRACE resource 58 // for the NETWORK_EVENT resource and vice versa. 59 this.stackTraces = new Map(); 60 // Map of the stacktrace information keyed by the actor id's 61 this.stackTraceRequestInfoByActorID = new Map(); 62 63 // For tracking unfinished requests 64 this.pendingRequests = new Set(); 65 66 // Map[key string => Promise] used by `requestData` to prevent requesting the same 67 // request data twice. 68 this.lazyRequestData = new Map(); 69 70 // Fetching data from the backend 71 this.getLongString = this.getLongString.bind(this); 72 73 // Event handlers 74 this.onNetworkResourceAvailable = 75 this.onNetworkResourceAvailable.bind(this); 76 this.onNetworkResourceUpdated = this.onNetworkResourceUpdated.bind(this); 77 78 this.onWebSocketOpened = this.onWebSocketOpened.bind(this); 79 this.onWebSocketClosed = this.onWebSocketClosed.bind(this); 80 this.onFrameSent = this.onFrameSent.bind(this); 81 this.onFrameReceived = this.onFrameReceived.bind(this); 82 83 this.onEventSourceConnectionClosed = 84 this.onEventSourceConnectionClosed.bind(this); 85 this.onEventReceived = this.onEventReceived.bind(this); 86 this.setEventStreamFlag = this.setEventStreamFlag.bind(this); 87 } 88 89 /* 90 * Cleans up all the internal states, this usually done before navigation 91 * (without the persist flag on). 92 */ 93 clear() { 94 this.stackTraces.clear(); 95 this.pendingRequests.clear(); 96 this.lazyRequestData.clear(); 97 this.stackTraceRequestInfoByActorID.clear(); 98 this._requestDataEnabled = false; 99 this._lastRequestDataClearId++; 100 } 101 102 destroy() { 103 // TODO: clear() is called in the middle of the lifecycle of the 104 // FirefoxDataProvider, for clarity we are exposing it as a separate method. 105 // `destroy` should be updated to nullify relevant instance properties. 106 this.clear(); 107 } 108 109 /** 110 * Enable/disable firing redux actions (enabled by default). 111 * 112 * @param {boolean} enable Set to true to fire actions. 113 */ 114 enableActions(enable) { 115 this.actionsEnabled = enable; 116 } 117 118 /** 119 * Add a new network request to application state. 120 * 121 * @param {string} id request id 122 * @param {object} resource resource payload will be added to application state 123 */ 124 async addRequest(id, resource) { 125 // Add to the pending requests which helps when deciding if the request is complete. 126 this.pendingRequests.add(id); 127 128 if (this.actionsEnabled && this.actions.addRequest) { 129 await this.actions.addRequest(id, resource, true); 130 } 131 132 this.emit(EVENTS.REQUEST_ADDED, id); 133 } 134 135 /** 136 * Update a network request if it already exists in application state. 137 * 138 * @param {string} id request id 139 * @param {object} data data payload will be updated to application state 140 */ 141 async updateRequest(id, data) { 142 const { 143 responseContent, 144 responseCookies, 145 responseHeaders, 146 earlyHintsResponseHeaders, 147 requestCookies, 148 requestHeaders, 149 requestPostData, 150 responseCache, 151 } = data; 152 153 // fetch request detail contents in parallel 154 const [ 155 responseContentObj, 156 requestHeadersObj, 157 responseHeadersObj, 158 earlyHintsResponseHeadersObj, 159 postDataObj, 160 requestCookiesObj, 161 responseCookiesObj, 162 responseCacheObj, 163 ] = await Promise.all([ 164 this.fetchResponseContent(responseContent), 165 this.fetchRequestHeaders(requestHeaders), 166 this.fetchResponseHeaders(responseHeaders), 167 this.fetchEarlyHintResponseHeaders(earlyHintsResponseHeaders), 168 this.fetchPostData(requestPostData), 169 this.fetchRequestCookies(requestCookies), 170 this.fetchResponseCookies(responseCookies), 171 this.fetchResponseCache(responseCache), 172 ]); 173 174 const payload = Object.assign( 175 {}, 176 data, 177 responseContentObj, 178 requestHeadersObj, 179 responseHeadersObj, 180 earlyHintsResponseHeadersObj, 181 postDataObj, 182 requestCookiesObj, 183 responseCookiesObj, 184 responseCacheObj 185 ); 186 187 if (this.actionsEnabled && this.actions.updateRequest) { 188 await this.actions.updateRequest(id, payload, true); 189 } 190 191 return payload; 192 } 193 194 async fetchResponseContent(responseContent) { 195 const payload = {}; 196 if (responseContent?.content) { 197 const { text } = responseContent.content; 198 const response = await this.getLongString(text); 199 responseContent.content.text = response; 200 payload.responseContent = responseContent; 201 } 202 return payload; 203 } 204 205 async fetchRequestHeaders(requestHeaders) { 206 const payload = {}; 207 if (requestHeaders?.headers?.length) { 208 const headers = await fetchHeaders(requestHeaders, this.getLongString); 209 if (headers) { 210 payload.requestHeaders = headers; 211 } 212 } 213 return payload; 214 } 215 216 async fetchResponseHeaders(responseHeaders) { 217 const payload = {}; 218 if (responseHeaders?.headers?.length) { 219 const headers = await fetchHeaders(responseHeaders, this.getLongString); 220 if (headers) { 221 payload.responseHeaders = headers; 222 } 223 } 224 return payload; 225 } 226 227 async fetchEarlyHintResponseHeaders(earlyHintsResponseHeaders) { 228 const payload = {}; 229 if (earlyHintsResponseHeaders?.headers?.length) { 230 const headers = await fetchHeaders( 231 earlyHintsResponseHeaders, 232 this.getLongString 233 ); 234 if (headers) { 235 payload.earlyHintsResponseHeaders = headers; 236 } 237 } 238 return payload; 239 } 240 241 async fetchPostData(requestPostData) { 242 const payload = {}; 243 if (requestPostData?.postData) { 244 const { text } = requestPostData.postData; 245 const postData = await this.getLongString(text); 246 const headers = CurlUtils.getHeadersFromMultipartText(postData); 247 248 // Calculate total header size and don't forget to include 249 // two new-line characters at the end. 250 const headersSize = headers.reduce((acc, { name, value }) => { 251 return acc + name.length + value.length + 2; 252 }, 0); 253 254 requestPostData.postData.text = postData; 255 payload.requestPostData = { 256 ...requestPostData, 257 uploadHeaders: { headers, headersSize }, 258 }; 259 } 260 return payload; 261 } 262 263 async fetchRequestCookies(requestCookies) { 264 const payload = {}; 265 if (requestCookies) { 266 const reqCookies = []; 267 // request store cookies in requestCookies or requestCookies.cookies 268 const cookies = requestCookies.cookies 269 ? requestCookies.cookies 270 : requestCookies; 271 // make sure cookies is iterable 272 if (typeof cookies[Symbol.iterator] === "function") { 273 for (const cookie of cookies) { 274 reqCookies.push( 275 Object.assign({}, cookie, { 276 value: await this.getLongString(cookie.value), 277 }) 278 ); 279 } 280 if (reqCookies.length) { 281 payload.requestCookies = reqCookies; 282 } 283 } 284 } 285 return payload; 286 } 287 288 async fetchResponseCookies(responseCookies) { 289 const payload = {}; 290 if (responseCookies) { 291 const resCookies = []; 292 // response store cookies in responseCookies or responseCookies.cookies 293 const cookies = responseCookies.cookies 294 ? responseCookies.cookies 295 : responseCookies; 296 // make sure cookies is iterable 297 if (typeof cookies[Symbol.iterator] === "function") { 298 for (const cookie of cookies) { 299 resCookies.push( 300 Object.assign({}, cookie, { 301 value: await this.getLongString(cookie.value), 302 }) 303 ); 304 } 305 if (resCookies.length) { 306 payload.responseCookies = resCookies; 307 } 308 } 309 } 310 return payload; 311 } 312 313 async fetchResponseCache(responseCache) { 314 const payload = {}; 315 if (responseCache) { 316 payload.responseCache = await responseCache; 317 payload.responseCacheAvailable = false; 318 } 319 return payload; 320 } 321 322 /** 323 * Public API used by the Toolbox: Tells if there is still any pending request. 324 * 325 * @return {boolean} returns true if pending requests still exist in the queue. 326 */ 327 hasPendingRequests() { 328 return this.pendingRequests.size > 0; 329 } 330 331 /** 332 * Fetches the full text of a LongString. 333 * 334 * @param {object|string} stringGrip 335 * The long string grip containing the corresponding actor. 336 * If you pass in a plain string (by accident or because you're lazy), 337 * then a promise of the same string is simply returned. 338 * @return {object} 339 * A promise that is resolved when the full string contents 340 * are available, or rejected if something goes wrong. 341 */ 342 async getLongString(stringGrip) { 343 const payload = await getLongStringFullText( 344 this.commands.client, 345 stringGrip 346 ); 347 this.emitForTests(TEST_EVENTS.LONGSTRING_RESOLVED, { payload }); 348 return payload; 349 } 350 351 /** 352 * Retrieve the stack-trace information for the given StackTracesActor. 353 * 354 * @param object actor 355 * - {Object} targetFront: the target front. 356 * 357 * - {String} resourceId: the resource id for the network request". 358 * @return {object} 359 */ 360 async _getStackTraceFromWatcher(actor) { 361 // If we request the stack trace for the navigation request, 362 // t was coming from previous page content process, which may no longer be around. 363 // In any case, the previous target is destroyed and we can't fetch the stack anymore. 364 let stacktrace = []; 365 if (!actor.targetFront.isDestroyed()) { 366 const networkContentFront = 367 await actor.targetFront.getFront("networkContent"); 368 stacktrace = await networkContentFront.getStackTrace( 369 actor.stacktraceResourceId 370 ); 371 } 372 return { stacktrace }; 373 } 374 375 /** 376 * The handler for when the network event stacktrace resource is available. 377 * The resource contains basic info, the actual stacktrace is fetched lazily 378 * using requestData. 379 * 380 * @param {object} resource The network event stacktrace resource 381 */ 382 async onStackTraceAvailable(resource) { 383 if (!this.stackTraces.has(resource.resourceId)) { 384 // If no stacktrace info exists, this means the network event 385 // has not fired yet, lets store useful info for the NETWORK_EVENT 386 // resource. 387 this.stackTraces.set(resource.resourceId, resource); 388 } else { 389 // If stacktrace info exists, this means the network event has already 390 // fired, so lets just update the reducer with the necessary stacktrace info. 391 const request = this.stackTraces.get(resource.resourceId); 392 request.cause.stacktraceAvailable = resource.stacktraceAvailable; 393 request.cause.lastFrame = resource.lastFrame; 394 395 this.stackTraces.delete(resource.resourceId); 396 397 this.stackTraceRequestInfoByActorID.set(request.actor, { 398 targetFront: resource.targetFront, 399 stacktraceResourceId: resource.resourceId, 400 }); 401 402 if (this.actionsEnabled && this.actions.updateRequest) { 403 await this.actions.updateRequest(request.actor, request, true); 404 } 405 } 406 } 407 408 /** 409 * The handler for when the network event resource is available. 410 * 411 * @param {object} resource The network event resource 412 */ 413 async onNetworkResourceAvailable(resource) { 414 const { actor, stacktraceResourceId, cause } = resource; 415 416 if (!this._requestDataEnabled) { 417 this._requestDataEnabled = true; 418 } 419 420 // Check if a stacktrace resource already exists for this network resource. 421 if (this.stackTraces.has(stacktraceResourceId)) { 422 const { stacktraceAvailable, lastFrame, targetFront } = 423 this.stackTraces.get(stacktraceResourceId); 424 425 resource.cause.stacktraceAvailable = stacktraceAvailable; 426 resource.cause.lastFrame = lastFrame; 427 428 this.stackTraces.delete(stacktraceResourceId); 429 // We retrieve preliminary information about the stacktrace from the 430 // NETWORK_EVENT_STACKTRACE resource via `this.stackTraces` Map, 431 // The actual stacktrace is fetched lazily based on the actor id, using 432 // the targetFront and the stacktrace resource id therefore we 433 // map these for easy access. 434 this.stackTraceRequestInfoByActorID.set(actor, { 435 targetFront, 436 stacktraceResourceId, 437 }); 438 } else if (cause) { 439 // If the stacktrace for this request is not available, and we 440 // expect that this request should have a stacktrace, lets store 441 // some useful info for when the NETWORK_EVENT_STACKTRACE resource 442 // finally comes. 443 this.stackTraces.set(stacktraceResourceId, { actor, cause }); 444 } 445 await this.addRequest(actor, resource); 446 this.emitForTests(TEST_EVENTS.NETWORK_EVENT, resource); 447 } 448 449 /** 450 * The handler for when the network event resource is updated. 451 * 452 * @param {object} resource The updated network event resource. 453 * @param {object} update The update packet, includes the latest resource updates 454 */ 455 async onNetworkResourceUpdated(resource, update) { 456 // Identify the channel as SSE if mimeType is event-stream. 457 if (resource?.mimeType?.includes("text/event-stream")) { 458 await this.setEventStreamFlag(resource.actor); 459 } 460 461 if (this.actionsEnabled && this.actions.updateRequest) { 462 await this.actions.updateRequest(resource.actor, resource, true); 463 } 464 465 // This event is fired multiple times per request 466 this.emitForTests(TEST_EVENTS.NETWORK_EVENT_UPDATED, resource.actor); 467 if (update.resourceUpdates.responseEndAvailable) { 468 this.pendingRequests.delete(resource.actor); 469 // The EVENTS.PAYLOAD_READY might be consumed by extensions. 470 this.emit(EVENTS.PAYLOAD_READY, resource); 471 } 472 } 473 474 /** 475 * The "webSocketOpened" message type handler. 476 * 477 * @param {number} httpChannelId the channel ID 478 * @param {string} effectiveURI the effective URI of the page 479 * @param {string} protocols webSocket protocols 480 * @param {string} extensions 481 */ 482 async onWebSocketOpened() {} 483 484 /** 485 * The "webSocketClosed" message type handler. 486 * 487 * @param {number} httpChannelId 488 * @param {boolean} wasClean 489 * @param {number} code 490 * @param {string} reason 491 */ 492 async onWebSocketClosed(httpChannelId, wasClean, code, reason) { 493 if (this.actionsEnabled && this.actions.closeConnection) { 494 await this.actions.closeConnection(httpChannelId, wasClean, code, reason); 495 } 496 } 497 498 /** 499 * The "frameSent" message type handler. 500 * 501 * @param {number} httpChannelId the channel ID 502 * @param {object} data websocket frame information 503 */ 504 async onFrameSent(httpChannelId, data) { 505 this.addMessage(httpChannelId, data); 506 } 507 508 /** 509 * The "frameReceived" message type handler. 510 * 511 * @param {number} httpChannelId the channel ID 512 * @param {object} data websocket frame information 513 */ 514 async onFrameReceived(httpChannelId, data) { 515 this.addMessage(httpChannelId, data); 516 } 517 518 /** 519 * Add a new WebSocket frame to application state. 520 * 521 * @param {number} httpChannelId the channel ID 522 * @param {object} data websocket frame information 523 */ 524 async addMessage(httpChannelId, data) { 525 if (this.actionsEnabled && this.actions.addMessage) { 526 await this.actions.addMessage(httpChannelId, data, true); 527 } 528 // TODO: Emit an event for test here 529 } 530 531 /** 532 * Public connector API to lazily request HTTP details from the backend. 533 * 534 * The method focus on: 535 * - calling the right actor method, 536 * - emitting an event to tell we start fetching some request data, 537 * - call data processing method. 538 * 539 * @param {string} actor actor id (used as request id) 540 * @param {string} method identifier of the data we want to fetch 541 * 542 * @return {Promise} return a promise resolved when data is received. 543 */ 544 requestData(actor, method) { 545 // if this is `false`, do not try to request data as requests on the backend 546 // might no longer exist (usually `false` after requests are cleared). 547 if (!this._requestDataEnabled) { 548 return Promise.resolve(); 549 } 550 // Key string used in `lazyRequestData`. We use this Map to prevent requesting 551 // the same data twice at the same time. 552 const key = `${actor}-${method}`; 553 let promise = this.lazyRequestData.get(key); 554 // If a request is pending, reuse it. 555 if (promise) { 556 return promise; 557 } 558 // Fetch the data 559 promise = this._requestData(actor, method).then(async payload => { 560 // Remove the request from the cache, any new call to requestData will fetch the 561 // data again. 562 this.lazyRequestData.delete(key); 563 564 if (this.actionsEnabled && this.actions.updateRequest) { 565 await this.actions.updateRequest( 566 actor, 567 { 568 ...payload, 569 // Lockdown *Available property once we fetch data from back-end. 570 // Using this as a flag to prevent fetching arrived data again. 571 [`${method}Available`]: false, 572 }, 573 true 574 ); 575 } 576 577 return payload; 578 }); 579 580 this.lazyRequestData.set(key, promise); 581 582 return promise; 583 } 584 585 /** 586 * Internal helper used to request HTTP details from the backend. 587 * 588 * This is internal method that focus on: 589 * - calling the right actor method, 590 * - emitting an event to tell we start fetching some request data, 591 * - call data processing method. 592 * 593 * @param {string} actor actor id (used as request id) 594 * @param {string} method identifier of the data we want to fetch 595 * 596 * @return {Promise} return a promise resolved when data is received. 597 */ 598 async _requestData(actor, method) { 599 // Backup the lastRequestDataClearId before doing any async processing. 600 const lastRequestDataClearId = this._lastRequestDataClearId; 601 602 // Calculate real name of the client getter. 603 const clientMethodName = `get${method 604 .charAt(0) 605 .toUpperCase()}${method.slice(1)}`; 606 // The name of the callback that processes request response 607 const callbackMethodName = `on${method 608 .charAt(0) 609 .toUpperCase()}${method.slice(1)}`; 610 // And the event to fire before updating this data 611 const updatingEventName = `UPDATING_${method 612 .replace(/([A-Z])/g, "_$1") 613 .toUpperCase()}`; 614 615 // Emit event that tell we just start fetching some data 616 this.emitForTests(EVENTS[updatingEventName], actor); 617 618 // Make sure we fetch the real actor data instead of cloned actor 619 // e.g. CustomRequestPanel will clone a request with additional '-clone' actor id 620 const actorID = actor.replace("-clone", ""); 621 622 // 'getStackTrace' is the only one to be fetched via the NetworkContent actor in content process 623 // while all other attributes are fetched from the NetworkEvent actors, running in the parent process 624 let response; 625 if ( 626 clientMethodName == "getStackTrace" && 627 this.commands.resourceCommand.hasResourceCommandSupport( 628 this.commands.resourceCommand.TYPES.NETWORK_EVENT_STACKTRACE 629 ) 630 ) { 631 const requestInfo = this.stackTraceRequestInfoByActorID.get(actorID); 632 const { stacktrace } = await this._getStackTraceFromWatcher(requestInfo); 633 this.stackTraceRequestInfoByActorID.delete(actorID); 634 response = { from: actor, stacktrace }; 635 } else { 636 // We don't create fronts for NetworkEvent actors, 637 // so that we have to do the request manually via DevToolsClient.request() 638 try { 639 const packet = { 640 to: actorID, 641 type: clientMethodName, 642 }; 643 response = await this.commands.client.request(packet); 644 } catch (e) { 645 if (this._lastRequestDataClearId !== lastRequestDataClearId) { 646 // If lastRequestDataClearId was updated, FirefoxDataProvider:clear() 647 // was called and all network event actors have been destroyed. 648 // Swallow errors to avoid unhandled promise rejections in tests. 649 console.warn( 650 `Firefox Data Provider destroyed while requesting data: ${e.message}` 651 ); 652 // Return an empty response packet to avoid too many callback errors. 653 response = { from: actor }; 654 } else { 655 throw new Error( 656 `Error while calling method ${clientMethodName}: ${e.message}` 657 ); 658 } 659 } 660 } 661 662 // Restore clone actor id 663 if (actor.includes("-clone")) { 664 // Because response's properties are read-only, we create a new response 665 response = { ...response, from: `${response.from}-clone` }; 666 } 667 668 // Call data processing method. 669 return this[callbackMethodName](response); 670 } 671 672 /** 673 * Handles additional information received for a "requestHeaders" packet. 674 * 675 * @param {object} response the message received from the server. 676 */ 677 async onRequestHeaders(response) { 678 const payload = await this.updateRequest(response.from, { 679 requestHeaders: response, 680 }); 681 this.emitForTests(TEST_EVENTS.RECEIVED_REQUEST_HEADERS, response); 682 return payload.requestHeaders; 683 } 684 685 /** 686 * Handles additional information received for a "responseHeaders" packet. 687 * 688 * @param {object} response the message received from the server. 689 */ 690 async onEarlyHintsResponseHeaders(response) { 691 const payload = await this.updateRequest(response.from, { 692 earlyHintsResponseHeaders: response, 693 }); 694 this.emitForTests( 695 TEST_EVENTS.RECEIVED_EARLY_HINTS_RESPONSE_HEADERS, 696 response 697 ); 698 return payload.earlyHintsResponseHeaders; 699 } 700 701 /** 702 * Handles additional information received for a "responseHeaders" packet. 703 * 704 * @param {object} response the message received from the server. 705 */ 706 async onResponseHeaders(response) { 707 const payload = await this.updateRequest(response.from, { 708 responseHeaders: response, 709 }); 710 this.emitForTests(TEST_EVENTS.RECEIVED_RESPONSE_HEADERS, response); 711 return payload.responseHeaders; 712 } 713 714 /** 715 * Handles additional information received for a "requestCookies" packet. 716 * 717 * @param {object} response the message received from the server. 718 */ 719 async onRequestCookies(response) { 720 const payload = await this.updateRequest(response.from, { 721 requestCookies: response, 722 }); 723 this.emitForTests(TEST_EVENTS.RECEIVED_REQUEST_COOKIES, response); 724 return payload.requestCookies; 725 } 726 727 /** 728 * Handles additional information received for a "requestPostData" packet. 729 * 730 * @param {object} response the message received from the server. 731 */ 732 async onRequestPostData(response) { 733 const payload = await this.updateRequest(response.from, { 734 requestPostData: response, 735 }); 736 this.emitForTests(TEST_EVENTS.RECEIVED_REQUEST_POST_DATA, response); 737 return payload.requestPostData; 738 } 739 740 /** 741 * Handles additional information received for a "securityInfo" packet. 742 * 743 * @param {object} response the message received from the server. 744 */ 745 async onSecurityInfo(response) { 746 const payload = await this.updateRequest(response.from, { 747 securityInfo: response.securityInfo, 748 }); 749 this.emitForTests(TEST_EVENTS.RECEIVED_SECURITY_INFO, response); 750 return payload.securityInfo; 751 } 752 753 /** 754 * Handles additional information received for a "responseCookies" packet. 755 * 756 * @param {object} response the message received from the server. 757 */ 758 async onResponseCookies(response) { 759 const payload = await this.updateRequest(response.from, { 760 responseCookies: response, 761 }); 762 this.emitForTests(TEST_EVENTS.RECEIVED_RESPONSE_COOKIES, response); 763 return payload.responseCookies; 764 } 765 766 /** 767 * Handles additional information received for a "responseCache" packet. 768 * 769 * @param {object} response the message received from the server. 770 */ 771 async onResponseCache(response) { 772 const payload = await this.updateRequest(response.from, { 773 responseCache: response, 774 }); 775 this.emitForTests(TEST_EVENTS.RECEIVED_RESPONSE_CACHE, response); 776 return payload.responseCache; 777 } 778 779 /** 780 * Handles additional information received via "getResponseContent" request. 781 * 782 * @param {object} response the message received from the server. 783 */ 784 async onResponseContent(response) { 785 const payload = await this.updateRequest(response.from, { 786 // We have to ensure passing mimeType as fetchResponseContent needs it from 787 // updateRequest. It will convert the LongString in `response.content.text` to a 788 // string. 789 mimeType: response.content.mimeType, 790 responseContent: response, 791 }); 792 this.emitForTests(TEST_EVENTS.RECEIVED_RESPONSE_CONTENT, response); 793 return payload.responseContent; 794 } 795 796 /** 797 * Handles additional information received for a "eventTimings" packet. 798 * 799 * @param {object} response the message received from the server. 800 */ 801 async onEventTimings(response) { 802 const payload = await this.updateRequest(response.from, { 803 eventTimings: response, 804 }); 805 806 // This event is utilized only in tests but, DAMP is using it too 807 // and running DAMP test doesn't set the `devtools.testing` flag. 808 // So, emit this event even in the production mode. 809 // See also: https://bugzilla.mozilla.org/show_bug.cgi?id=1578215 810 this.emit(EVENTS.RECEIVED_EVENT_TIMINGS, response); 811 return payload.eventTimings; 812 } 813 814 /** 815 * Handles information received for a "stackTrace" packet. 816 * 817 * @param {object} response the message received from the server. 818 */ 819 async onStackTrace(response) { 820 const payload = await this.updateRequest(response.from, { 821 stacktrace: response.stacktrace, 822 }); 823 this.emitForTests(TEST_EVENTS.RECEIVED_EVENT_STACKTRACE, response); 824 return payload.stacktrace; 825 } 826 827 /** 828 * Handle EventSource events. 829 */ 830 831 async onEventSourceConnectionClosed(httpChannelId) { 832 if (this.actionsEnabled && this.actions.closeConnection) { 833 await this.actions.closeConnection(httpChannelId); 834 } 835 } 836 837 async onEventReceived(httpChannelId, data) { 838 // Dispatch the same action used by websocket inspector. 839 this.addMessage(httpChannelId, data); 840 } 841 842 async setEventStreamFlag(actorId) { 843 if (this.actionsEnabled && this.actions.setEventStreamFlag) { 844 await this.actions.setEventStreamFlag(actorId, true); 845 } 846 } 847 848 /** 849 * Fire events for the owner object. 850 */ 851 emit(type, data) { 852 if (this.owner) { 853 this.owner.emit(type, data); 854 } 855 } 856 857 /** 858 * Fire test events for the owner object. These events are 859 * emitted only when tests are running. 860 */ 861 emitForTests(type, data) { 862 if (this.owner) { 863 this.owner.emitForTests(type, data); 864 } 865 } 866 } 867 868 module.exports = FirefoxDataProvider;