tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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;