tor-browser

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

NetworkObserver.sys.mjs (42759B)


      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 
      5 /**
      6 * NetworkObserver is the main class in DevTools to observe network requests
      7 * out of many events fired by the platform code.
      8 */
      9 
     10 // Enable logging all platform events this module listen to
     11 const DEBUG_PLATFORM_EVENTS = false;
     12 // Enables defining criteria to filter the logs
     13 const DEBUG_PLATFORM_EVENTS_FILTER = () => {
     14  // e.g return eventName == "HTTP_TRANSACTION:REQUEST_HEADER" && channel.URI.spec == "http://foo.com";
     15  return true;
     16 };
     17 
     18 const lazy = {};
     19 
     20 import { DevToolsInfaillibleUtils } from "resource://devtools/shared/DevToolsInfaillibleUtils.sys.mjs";
     21 
     22 ChromeUtils.defineESModuleGetters(
     23  lazy,
     24  {
     25    ChannelMap:
     26      "resource://devtools/shared/network-observer/ChannelMap.sys.mjs",
     27    NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
     28    NetworkAuthListener:
     29      "resource://devtools/shared/network-observer/NetworkAuthListener.sys.mjs",
     30    NetworkHelper:
     31      "resource://devtools/shared/network-observer/NetworkHelper.sys.mjs",
     32    NetworkOverride:
     33      "resource://devtools/shared/network-observer/NetworkOverride.sys.mjs",
     34    NetworkResponseListener:
     35      "resource://devtools/shared/network-observer/NetworkResponseListener.sys.mjs",
     36    NetworkTimings:
     37      "resource://devtools/shared/network-observer/NetworkTimings.sys.mjs",
     38    NetworkThrottleManager:
     39      "resource://devtools/shared/network-observer/NetworkThrottleManager.sys.mjs",
     40    NetworkUtils:
     41      "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
     42    wildcardToRegExp:
     43      "resource://devtools/shared/network-observer/WildcardToRegexp.sys.mjs",
     44  },
     45  { global: "contextual" }
     46 );
     47 
     48 const gActivityDistributor = Cc[
     49  "@mozilla.org/network/http-activity-distributor;1"
     50 ].getService(Ci.nsIHttpActivityDistributor);
     51 
     52 function logPlatformEvent(eventName, channel, message = "") {
     53  if (!DEBUG_PLATFORM_EVENTS) {
     54    return;
     55  }
     56  if (DEBUG_PLATFORM_EVENTS_FILTER(eventName, channel)) {
     57    dump(
     58      `[netmonitor] ${channel.channelId} - ${eventName} ${message} - ${channel.URI.spec}\n`
     59    );
     60  }
     61 }
     62 
     63 // The maximum uint32 value.
     64 const PR_UINT32_MAX = 4294967295;
     65 
     66 const HTTP_TRANSACTION_CODES = {
     67  0x5001: "REQUEST_HEADER",
     68  0x5002: "REQUEST_BODY_SENT",
     69  0x5003: "RESPONSE_START",
     70  0x5004: "RESPONSE_HEADER",
     71  0x5005: "RESPONSE_COMPLETE",
     72  0x5006: "TRANSACTION_CLOSE",
     73  0x500c: "EARLYHINT_RESPONSE_HEADER",
     74 
     75  0x4b0003: "STATUS_RESOLVING",
     76  0x4b000b: "STATUS_RESOLVED",
     77  0x4b0007: "STATUS_CONNECTING_TO",
     78  0x4b0004: "STATUS_CONNECTED_TO",
     79  0x4b0005: "STATUS_SENDING_TO",
     80  0x4b000a: "STATUS_WAITING_FOR",
     81  0x4b0006: "STATUS_RECEIVING_FROM",
     82  0x4b000c: "STATUS_TLS_STARTING",
     83  0x4b000d: "STATUS_TLS_ENDING",
     84 };
     85 
     86 const HTTP_DOWNLOAD_ACTIVITIES = [
     87  gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_START,
     88  gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER,
     89  gActivityDistributor.ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER,
     90  gActivityDistributor.ACTIVITY_SUBTYPE_EARLYHINT_RESPONSE_HEADER,
     91  gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
     92  gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE,
     93 ];
     94 
     95 /**
     96 * The network monitor uses the nsIHttpActivityDistributor to monitor network
     97 * requests. The nsIObserverService is also used for monitoring
     98 * http-on-examine-response notifications. All network request information is
     99 * routed to the remote Web Console.
    100 *
    101 * @class
    102 * @param {object} options
    103 * @param {Function(nsIChannel): boolean} options.ignoreChannelFunction
    104 *        This function will be called for every detected channel to decide if it
    105 *        should be monitored or not.
    106 * @param {Function(NetworkEvent): owner} options.onNetworkEvent
    107 *        This method is invoked once for every new network request with two
    108 *        arguments:
    109 *        - {Object} networkEvent: object created by NetworkUtils:createNetworkEvent,
    110 *          containing initial network request information as an argument.
    111 *        - {nsIChannel} channel: the channel for which the request was detected
    112 *
    113 *        `onNetworkEvent()` must return an "owner" object which holds several add*()
    114 *        methods which are used to add further network request/response information.
    115 */
    116 export class NetworkObserver {
    117  /**
    118   * Map of URL patterns to RegExp
    119   *
    120   * @type {Map}
    121   */
    122  #blockedURLs = new Map();
    123 
    124  /**
    125   * Map of URL to local file path in order to redirect URL
    126   * to local file overrides.
    127   *
    128   * This will replace the content of some request with the content of local files.
    129   */
    130  #overrides = new Map();
    131 
    132  /**
    133   * Used by NetworkHelper.parseSecurityInfo to skip decoding known certificates.
    134   *
    135   * @type {Map}
    136   */
    137  #decodedCertificateCache = new Map();
    138  /**
    139   * Whether the consumer supports listening and handling auth prompts.
    140   *
    141   * @type {boolean}
    142   */
    143  #authPromptListenerEnabled = false;
    144  /**
    145   * See constructor argument of the same name.
    146   *
    147   * @type {Function}
    148   */
    149  #ignoreChannelFunction;
    150  /**
    151   * Used to store channels intercepted for service-worker requests.
    152   *
    153   * @type {WeakSet}
    154   */
    155  #interceptedChannels = new WeakSet();
    156  /**
    157   * Explicit flag to check if this observer was already destroyed.
    158   *
    159   * @type {boolean}
    160   */
    161  #isDestroyed = false;
    162  /**
    163   * See constructor argument of the same name.
    164   *
    165   * @type {Function}
    166   */
    167  #onNetworkEvent;
    168  /**
    169   * Object that holds the activity objects for ongoing requests.
    170   *
    171   * @type {ChannelMap}
    172   */
    173  #openRequests = new lazy.ChannelMap();
    174  /**
    175   * The maximum size (in bytes) of the individual response bodies to be stored.
    176   *
    177   * @type {number}
    178   */
    179  #responseBodyLimit = 0;
    180  /**
    181   * Network response bodies are piped through a buffer of the given size
    182   * (in bytes).
    183   *
    184   * @type {number}
    185   */
    186  #responsePipeSegmentSize = Services.prefs.getIntPref(
    187    "network.buffer.cache.size"
    188  );
    189  /**
    190   * Whether to save the bodies of network requests and responses.
    191   *
    192   * @type {boolean}
    193   */
    194  #saveRequestAndResponseBodies = true;
    195  /**
    196   * Whether response bodies should be decoded or not.
    197   *
    198   * @type {boolean}
    199   */
    200  #decodeResponseBodies = true;
    201  /**
    202   * Throttling configuration, see constructor of NetworkThrottleManager
    203   *
    204   * @type {object}
    205   */
    206  #throttleData = null;
    207  /**
    208   * NetworkThrottleManager instance, created when a valid throttleData is set.
    209   *
    210   * @type {NetworkThrottleManager}
    211   */
    212  #throttler = null;
    213 
    214  constructor(options = {}) {
    215    const {
    216      decodeResponseBodies,
    217      ignoreChannelFunction,
    218      onNetworkEvent,
    219      responseBodyLimit,
    220    } = options;
    221 
    222    if (typeof ignoreChannelFunction !== "function") {
    223      throw new Error(
    224        `Expected "ignoreChannelFunction" to be a function, got ${ignoreChannelFunction} (${typeof ignoreChannelFunction})`
    225      );
    226    }
    227 
    228    if (typeof onNetworkEvent !== "function") {
    229      throw new Error(
    230        `Expected "onNetworkEvent" to be a function, got ${onNetworkEvent} (${typeof onNetworkEvent})`
    231      );
    232    }
    233 
    234    this.#ignoreChannelFunction = ignoreChannelFunction;
    235    this.#onNetworkEvent = onNetworkEvent;
    236 
    237    // Set decodeResponseBodies if provided, otherwise default to "true".
    238    if (typeof decodeResponseBodies === "boolean") {
    239      this.#decodeResponseBodies = decodeResponseBodies;
    240    }
    241 
    242    // Set the provided responseBodyLimit if any, otherwise use the default "0".
    243    if (typeof responseBodyLimit === "number") {
    244      this.#responseBodyLimit = responseBodyLimit;
    245    }
    246 
    247    // Start all platform observers.
    248    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
    249      gActivityDistributor.addObserver(this);
    250      gActivityDistributor.observeProxyResponse = true;
    251 
    252      Services.obs.addObserver(
    253        this.#httpResponseExaminer,
    254        "http-on-examine-response"
    255      );
    256      Services.obs.addObserver(
    257        this.#httpResponseExaminer,
    258        "http-on-examine-cached-response"
    259      );
    260      Services.obs.addObserver(
    261        this.#httpModifyExaminer,
    262        "http-on-modify-request"
    263      );
    264      Services.obs.addObserver(
    265        this.#fileChannelExaminer,
    266        "file-channel-opened"
    267      );
    268      Services.obs.addObserver(
    269        this.#dataChannelExaminer,
    270        "data-channel-opened"
    271      );
    272      Services.obs.addObserver(
    273        this.#httpBeforeConnect,
    274        "http-on-before-connect"
    275      );
    276 
    277      Services.obs.addObserver(this.#httpStopRequest, "http-on-stop-request");
    278    } else {
    279      Services.obs.addObserver(
    280        this.#httpFailedOpening,
    281        "http-on-failed-opening-request"
    282      );
    283    }
    284    // In child processes, only watch for service worker requests
    285    // everything else only happens in the parent process
    286    Services.obs.addObserver(
    287      this.#serviceWorkerRequest,
    288      "service-worker-synthesized-response"
    289    );
    290  }
    291 
    292  setAuthPromptListenerEnabled(enabled) {
    293    this.#authPromptListenerEnabled = enabled;
    294  }
    295 
    296  /**
    297   * Update the maximum size in bytes that can be collected for network response
    298   * bodies. Responses for which the NetworkResponseListener has already been
    299   * created will not be using the new limit, only later responses will be
    300   * affected.
    301   *
    302   * @param {number} responseBodyLimit
    303   *        The new responseBodyLimit to use.
    304   */
    305  setResponseBodyLimit(responseBodyLimit) {
    306    this.#responseBodyLimit = responseBodyLimit;
    307  }
    308 
    309  setSaveRequestAndResponseBodies(save) {
    310    this.#saveRequestAndResponseBodies = save;
    311  }
    312 
    313  getThrottleData() {
    314    return this.#throttleData;
    315  }
    316 
    317  /**
    318   * Update the network throttling configuration.
    319   *
    320   * @param {object|null} value
    321   *        The network throttling configuration object, or null if throttling
    322   *        should be disabled.
    323   */
    324  setThrottleData(value) {
    325    this.#throttleData = value;
    326 
    327    // If value is null, the user is disabling throttling, destroy the previous
    328    // throttler.
    329    if (this.#throttler && value === null) {
    330      this.#throttler.destroy();
    331    }
    332    this.#throttler = null;
    333  }
    334 
    335  #getThrottler() {
    336    if (this.#throttleData !== null && this.#throttler === null) {
    337      this.#throttler = new lazy.NetworkThrottleManager(this.#throttleData);
    338    }
    339    return this.#throttler;
    340  }
    341 
    342  #serviceWorkerRequest = DevToolsInfaillibleUtils.makeInfallible(
    343    (subject, topic) => {
    344      const channel = subject.QueryInterface(Ci.nsIHttpChannel);
    345 
    346      if (this.#ignoreChannelFunction(channel)) {
    347        return;
    348      }
    349 
    350      logPlatformEvent(topic, channel);
    351 
    352      this.#interceptedChannels.add(subject);
    353 
    354      // Service workers never fire http-on-examine-cached-response, so fake one.
    355      this.#httpResponseExaminer(channel, "http-on-examine-cached-response");
    356    }
    357  );
    358 
    359  /**
    360   * Observes for http-on-failed-opening-request notification to catch any
    361   * channels for which asyncOpen has synchronously failed.  This is the only
    362   * place to catch early security check failures.
    363   */
    364  #httpFailedOpening = DevToolsInfaillibleUtils.makeInfallible(
    365    (subject, topic) => {
    366      if (
    367        this.#isDestroyed ||
    368        topic != "http-on-failed-opening-request" ||
    369        !(subject instanceof Ci.nsIHttpChannel)
    370      ) {
    371        return;
    372      }
    373 
    374      const channel = subject.QueryInterface(Ci.nsIHttpChannel);
    375      if (this.#ignoreChannelFunction(channel)) {
    376        return;
    377      }
    378 
    379      logPlatformEvent(topic, channel);
    380 
    381      // Ignore preload requests to avoid duplicity request entries in
    382      // the Network panel. If a preload fails (for whatever reason)
    383      // then the platform kicks off another 'real' request.
    384      if (lazy.NetworkUtils.isPreloadRequest(channel)) {
    385        return;
    386      }
    387 
    388      this.#httpResponseExaminer(subject, topic);
    389    }
    390  );
    391 
    392  #httpBeforeConnect = DevToolsInfaillibleUtils.makeInfallible(
    393    (subject, topic) => {
    394      if (
    395        this.#isDestroyed ||
    396        topic != "http-on-before-connect" ||
    397        !(subject instanceof Ci.nsIHttpChannel)
    398      ) {
    399        return;
    400      }
    401 
    402      const channel = subject.QueryInterface(Ci.nsIHttpChannel);
    403      if (this.#ignoreChannelFunction(channel)) {
    404        return;
    405      }
    406 
    407      // Here we create the network event from an early platform notification.
    408      // Additional details about the event will be provided using the various
    409      // callbacks on the network event owner.
    410      const httpActivity = this.#createOrGetActivityObject(channel);
    411      this.#createNetworkEvent(httpActivity);
    412 
    413      // Handle overrides in http-on-before-connect because we need to redirect
    414      // the request to the override before reaching the server.
    415      this.#checkForContentOverride(httpActivity);
    416    }
    417  );
    418 
    419  #httpStopRequest = DevToolsInfaillibleUtils.makeInfallible(
    420    (subject, topic) => {
    421      if (
    422        this.#isDestroyed ||
    423        topic != "http-on-stop-request" ||
    424        !(subject instanceof Ci.nsIHttpChannel)
    425      ) {
    426        return;
    427      }
    428 
    429      const channel = subject.QueryInterface(Ci.nsIHttpChannel);
    430      if (this.#ignoreChannelFunction(channel)) {
    431        return;
    432      }
    433 
    434      logPlatformEvent(topic, channel);
    435 
    436      const httpActivity = this.#createOrGetActivityObject(channel);
    437      if (httpActivity.owner) {
    438        // Try extracting server timings. Note that they will be sent to the client
    439        // in the `_onTransactionClose` method together with network event timings.
    440        const serverTimings =
    441          lazy.NetworkTimings.extractServerTimings(httpActivity);
    442        httpActivity.owner.addServerTimings(serverTimings);
    443 
    444        // If the owner isn't set we need to create the network event and send
    445        // it to the client. This happens in case where:
    446        // - the request has been blocked (e.g. CORS) and "http-on-stop-request" is the first notification.
    447        // - the NetworkObserver is start *after* the request started and we only receive the http-stop notification,
    448        //   but that doesn't mean the request is blocked, so check for its status.
    449      } else if (Components.isSuccessCode(channel.status)) {
    450        // Do not pass any blocked reason, as this request is just fine.
    451        // Bug 1489217 - Prevent watching for this request response content,
    452        // as this request is already running, this is too late to watch for it.
    453        this.#createNetworkEvent(httpActivity, {
    454          inProgressRequest: true,
    455        });
    456      } else {
    457        // Handles any early blockings e.g by Web Extensions or by CORS
    458        const { extension, blockedReason } = lazy.NetworkUtils.getBlockedReason(
    459          channel,
    460          httpActivity.fromCache
    461        );
    462        this.#createNetworkEvent(httpActivity, {
    463          blockedReason,
    464          extension,
    465        });
    466      }
    467    }
    468  );
    469 
    470  /**
    471   * Check if the current channel has its content being overriden
    472   * by the content of some local file.
    473   */
    474  #checkForContentOverride(httpActivity) {
    475    const channel = httpActivity.channel;
    476    const overridePath = this.#overrides.get(channel.URI.spec);
    477    if (!overridePath) {
    478      return false;
    479    }
    480 
    481    dump(" Override " + channel.URI.spec + " to " + overridePath + "\n");
    482    try {
    483      lazy.NetworkOverride.overrideChannelWithFilePath(channel, overridePath);
    484      // Handle the activity as being from the cache to avoid looking up
    485      // typical information from the http channel, which would error for
    486      // overridden channels.
    487      httpActivity.fromCache = true;
    488      httpActivity.isOverridden = true;
    489    } catch (e) {
    490      dump("Exception while trying to override request content: " + e + "\n");
    491    }
    492 
    493    return true;
    494  }
    495 
    496  /**
    497   * Observe notifications for the http-on-examine-response topic, coming from
    498   * the nsIObserverService.
    499   *
    500   * @private
    501   * @param nsIHttpChannel subject
    502   * @param string topic
    503   * @returns void
    504   */
    505  #httpResponseExaminer = DevToolsInfaillibleUtils.makeInfallible(
    506    (subject, topic) => {
    507      // The httpResponseExaminer is used to retrieve the uncached response
    508      // headers.
    509      if (
    510        this.#isDestroyed ||
    511        (topic != "http-on-examine-response" &&
    512          topic != "http-on-examine-cached-response" &&
    513          topic != "http-on-failed-opening-request") ||
    514        !(subject instanceof Ci.nsIHttpChannel) ||
    515        !(subject instanceof Ci.nsIClassifiedChannel)
    516      ) {
    517        return;
    518      }
    519 
    520      const blockedOrFailed = topic === "http-on-failed-opening-request";
    521 
    522      subject.QueryInterface(Ci.nsIClassifiedChannel);
    523      const channel = subject.QueryInterface(Ci.nsIHttpChannel);
    524 
    525      if (this.#ignoreChannelFunction(channel)) {
    526        return;
    527      }
    528 
    529      logPlatformEvent(
    530        topic,
    531        subject,
    532        blockedOrFailed
    533          ? "blockedOrFailed:" + channel.loadInfo.requestBlockingReason
    534          : channel.responseStatus
    535      );
    536 
    537      channel.QueryInterface(Ci.nsIHttpChannelInternal);
    538 
    539      // Retrieve or create the http activity.
    540      const httpActivity = this.#createOrGetActivityObject(channel);
    541 
    542      // Preserve the initial responseStatus which can be modified over the
    543      // course of the network request, especially for 304 Not Modified channels
    544      // which will be replaced by the original 200 channel from the cache.
    545      // (this is the correct behavior according to the fetch spec).
    546      httpActivity.responseStatus = httpActivity.channel.responseStatus;
    547 
    548      if (topic === "http-on-examine-cached-response") {
    549        this.#handleExamineCachedResponse(httpActivity);
    550      } else if (topic === "http-on-failed-opening-request") {
    551        this.#handleFailedOpeningRequest(httpActivity);
    552      }
    553 
    554      if (httpActivity.owner) {
    555        httpActivity.owner.addResponseStart({
    556          channel: httpActivity.channel,
    557          fromCache: httpActivity.fromCache || httpActivity.fromServiceWorker,
    558          fromServiceWorker: httpActivity.fromServiceWorker,
    559          rawHeaders: httpActivity.responseRawHeaders,
    560          proxyResponseRawHeaders: httpActivity.proxyResponseRawHeaders,
    561          earlyHintsResponseRawHeaders:
    562            httpActivity.earlyHintsResponseRawHeaders,
    563        });
    564      }
    565    }
    566  );
    567 
    568  #handleExamineCachedResponse(httpActivity) {
    569    const channel = httpActivity.channel;
    570 
    571    const fromServiceWorker = this.#interceptedChannels.has(channel);
    572    const fromCache = !fromServiceWorker;
    573 
    574    // Set the cache flags on the httpActivity object, they will be used later
    575    // on during the lifecycle of the channel.
    576    httpActivity.fromCache = fromCache;
    577    httpActivity.fromServiceWorker = fromServiceWorker;
    578 
    579    // Service worker requests emits cached-response notification on non-e10s,
    580    // and we fake one on e10s.
    581    this.#interceptedChannels.delete(channel);
    582 
    583    if (!httpActivity.owner) {
    584      // If this is a cached response (which are also emitted by service worker requests),
    585      // there never was a request event so we need to construct one here
    586      // so the frontend gets all the expected events.
    587      this.#createNetworkEvent(httpActivity);
    588    }
    589 
    590    httpActivity.owner.addCacheDetails({
    591      fromCache: httpActivity.fromCache,
    592      fromServiceWorker: httpActivity.fromServiceWorker,
    593    });
    594 
    595    // We need to send the request body to the frontend for
    596    // the faked (cached/service worker request) event.
    597    this.#prepareRequestBody(httpActivity);
    598    this.#sendRequestBody(httpActivity);
    599 
    600    // There also is never any timing events, so we can fire this
    601    // event with zeroed out values.
    602    const timings = lazy.NetworkTimings.extractHarTimings(httpActivity);
    603    const serverTimings =
    604      lazy.NetworkTimings.extractServerTimings(httpActivity);
    605    const serviceWorkerTimings =
    606      lazy.NetworkTimings.extractServiceWorkerTimings(httpActivity);
    607 
    608    httpActivity.owner.addServerTimings(serverTimings);
    609    httpActivity.owner.addServiceWorkerTimings(serviceWorkerTimings);
    610    httpActivity.owner.addEventTimings(
    611      timings.total,
    612      timings.timings,
    613      timings.offsets
    614    );
    615  }
    616 
    617  #handleFailedOpeningRequest(httpActivity) {
    618    const channel = httpActivity.channel;
    619    const { blockedReason } = lazy.NetworkUtils.getBlockedReason(
    620      channel,
    621      httpActivity.fromCache
    622    );
    623 
    624    this.#createNetworkEvent(httpActivity, {
    625      blockedReason,
    626    });
    627  }
    628 
    629  /**
    630   * Observe notifications for the http-on-modify-request topic, coming from
    631   * the nsIObserverService.
    632   *
    633   * @private
    634   * @param nsIHttpChannel aSubject
    635   * @returns void
    636   */
    637  #httpModifyExaminer = DevToolsInfaillibleUtils.makeInfallible(subject => {
    638    const throttler = this.#getThrottler();
    639    if (throttler) {
    640      const channel = subject.QueryInterface(Ci.nsIHttpChannel);
    641      if (this.#ignoreChannelFunction(channel)) {
    642        return;
    643      }
    644      logPlatformEvent("http-on-modify-request", channel);
    645 
    646      // Read any request body here, before it is throttled.
    647      const httpActivity = this.#createOrGetActivityObject(channel);
    648      this.#prepareRequestBody(httpActivity);
    649      throttler.manageUpload(channel);
    650    }
    651  });
    652 
    653  #dataChannelExaminer = DevToolsInfaillibleUtils.makeInfallible(
    654    (subject, topic) => {
    655      if (
    656        topic != "data-channel-opened" ||
    657        !(subject instanceof Ci.nsIDataChannel)
    658      ) {
    659        return;
    660      }
    661      const channel = subject.QueryInterface(Ci.nsIDataChannel);
    662      channel.QueryInterface(Ci.nsIIdentChannel);
    663      channel.QueryInterface(Ci.nsIChannel);
    664 
    665      if (this.#ignoreChannelFunction(channel)) {
    666        return;
    667      }
    668 
    669      logPlatformEvent(topic, channel);
    670 
    671      const networkEventActor = this.#onNetworkEvent({}, channel, true);
    672      lazy.NetworkUtils.handleDataChannel(channel, networkEventActor);
    673    }
    674  );
    675 
    676  /**
    677   * Observe notifications for the file-channel-opened topic
    678   *
    679   * @private
    680   * @param nsIFileChannel subject
    681   * @param string topic
    682   * @returns void
    683   */
    684  #fileChannelExaminer = DevToolsInfaillibleUtils.makeInfallible(
    685    (subject, topic) => {
    686      if (
    687        this.#isDestroyed ||
    688        topic != "file-channel-opened" ||
    689        !(subject instanceof Ci.nsIFileChannel)
    690      ) {
    691        return;
    692      }
    693      const channel = subject.QueryInterface(Ci.nsIFileChannel);
    694      channel.QueryInterface(Ci.nsIIdentChannel);
    695      channel.QueryInterface(Ci.nsIChannel);
    696 
    697      if (this.#ignoreChannelFunction(channel)) {
    698        return;
    699      }
    700 
    701      logPlatformEvent(topic, channel);
    702      const owner = this.#onNetworkEvent({}, channel, true);
    703 
    704      owner.addResponseStart({
    705        channel,
    706        fromCache: false,
    707        rawHeaders: "",
    708      });
    709 
    710      // For file URLs we can not set up a stream listener as for http,
    711      // so we have to create a response manually and complete it.
    712      const response = {
    713        contentCharset: channel.contentCharset,
    714        contentLength: channel.contentLength,
    715        contentType: channel.contentType,
    716        mimeType: lazy.NetworkHelper.addCharsetToMimeType(
    717          channel.contentType,
    718          channel.contentCharset
    719        ),
    720        // Same as for cached responses, the transferredSize for file URLs
    721        // should be 0 regardless of the actual size of the response.
    722        transferredSize: 0,
    723      };
    724 
    725      // For file URIs all timings can be set to zero.
    726      const result = lazy.NetworkTimings.getEmptyHARTimings();
    727      owner.addEventTimings(result.total, result.timings, result.offsets);
    728 
    729      const fstream = Cc[
    730        "@mozilla.org/network/file-input-stream;1"
    731      ].createInstance(Ci.nsIFileInputStream);
    732      fstream.init(channel.file, -1, 0, 0);
    733      response.text = lazy.NetUtil.readInputStreamToString(
    734        fstream,
    735        fstream.available()
    736      );
    737      fstream.close();
    738 
    739      // Set the bodySize to the current response.text.length
    740      response.bodySize = response.text.length;
    741 
    742      if (
    743        !response.mimeType ||
    744        !lazy.NetworkHelper.isTextMimeType(response.mimeType)
    745      ) {
    746        response.encoding = "base64";
    747        try {
    748          response.text = btoa(response.text);
    749        } catch (err) {
    750          // Ignore.
    751        }
    752      }
    753 
    754      // Set the size/decodedBodySize to the updated response.text.length, after
    755      // potentially decoding the data.
    756      // NB: `size` is used by DevTools, while WebDriverBiDi relies on
    757      // decodedBodySize, because the name is more explicit.
    758      response.decodedBodySize = response.text.length;
    759      response.size = response.decodedBodySize;
    760 
    761      // Security information is not relevant for file channel, but it should
    762      // not be considered as insecure either. Set empty string as security
    763      // state.
    764      owner.addSecurityInfo({ state: "" });
    765      owner.addResponseContent(response);
    766      owner.addResponseContentComplete({});
    767    }
    768  );
    769 
    770  /**
    771   * A helper function for observeActivity.  This does whatever work
    772   * is required by a particular http activity event.  Arguments are
    773   * the same as for observeActivity.
    774   */
    775  #dispatchActivity(
    776    httpActivity,
    777    channel,
    778    activityType,
    779    activitySubtype,
    780    timestamp,
    781    extraSizeData,
    782    extraStringData
    783  ) {
    784    // Store the time information for this activity subtype.
    785    if (activitySubtype in HTTP_TRANSACTION_CODES) {
    786      const stage = HTTP_TRANSACTION_CODES[activitySubtype];
    787      if (stage in httpActivity.timings) {
    788        httpActivity.timings[stage].last = timestamp;
    789      } else {
    790        httpActivity.timings[stage] = {
    791          first: timestamp,
    792          last: timestamp,
    793        };
    794      }
    795    }
    796    switch (activitySubtype) {
    797      case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
    798        this.#prepareRequestBody(httpActivity);
    799        this.#sendRequestBody(httpActivity);
    800        break;
    801      case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
    802        httpActivity.responseRawHeaders = extraStringData;
    803        httpActivity.headersSize = extraStringData.length;
    804        break;
    805      case gActivityDistributor.ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER:
    806        httpActivity.proxyResponseRawHeaders = extraStringData;
    807        break;
    808      case gActivityDistributor.ACTIVITY_SUBTYPE_EARLYHINT_RESPONSE_HEADER:
    809        httpActivity.earlyHintsResponseRawHeaders = extraStringData;
    810        httpActivity.headersSize = extraStringData.length;
    811        break;
    812      case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
    813        this.#onTransactionClose(httpActivity);
    814        break;
    815      default:
    816        break;
    817    }
    818  }
    819 
    820  getActivityTypeString(activityType, activitySubtype) {
    821    if (
    822      activityType === Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_SOCKET_TRANSPORT
    823    ) {
    824      for (const name in Ci.nsISocketTransport) {
    825        if (Ci.nsISocketTransport[name] === activitySubtype) {
    826          return "SOCKET_TRANSPORT:" + name;
    827        }
    828      }
    829    } else if (
    830      activityType === Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION
    831    ) {
    832      for (const name in Ci.nsIHttpActivityObserver) {
    833        if (Ci.nsIHttpActivityObserver[name] === activitySubtype) {
    834          return "HTTP_TRANSACTION:" + name.replace("ACTIVITY_SUBTYPE_", "");
    835        }
    836      }
    837    }
    838    return "unexpected-activity-types:" + activityType + ":" + activitySubtype;
    839  }
    840 
    841  /**
    842   * Begin observing HTTP traffic that originates inside the current tab.
    843   *
    844   * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver
    845   *
    846   * @param nsIHttpChannel channel
    847   * @param number activityType
    848   * @param number activitySubtype
    849   * @param number timestamp
    850   * @param number extraSizeData
    851   * @param string extraStringData
    852   */
    853  observeActivity = DevToolsInfaillibleUtils.makeInfallible(
    854    function (
    855      channel,
    856      activityType,
    857      activitySubtype,
    858      timestamp,
    859      extraSizeData,
    860      extraStringData
    861    ) {
    862      if (
    863        this.#isDestroyed ||
    864        (activityType != gActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
    865          activityType != gActivityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT)
    866      ) {
    867        return;
    868      }
    869 
    870      if (
    871        !(channel instanceof Ci.nsIHttpChannel) ||
    872        !(channel instanceof Ci.nsIClassifiedChannel)
    873      ) {
    874        return;
    875      }
    876 
    877      channel = channel.QueryInterface(Ci.nsIHttpChannel);
    878      channel = channel.QueryInterface(Ci.nsIClassifiedChannel);
    879 
    880      if (DEBUG_PLATFORM_EVENTS) {
    881        logPlatformEvent(
    882          this.getActivityTypeString(activityType, activitySubtype),
    883          channel
    884        );
    885      }
    886 
    887      if (
    888        activitySubtype == gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER
    889      ) {
    890        this.#onRequestHeader(channel, timestamp, extraStringData);
    891        return;
    892      }
    893 
    894      // Iterate over all currently ongoing requests. If channel can't
    895      // be found within them, then exit this function.
    896      const httpActivity = this.#findActivityObject(channel);
    897      if (!httpActivity) {
    898        return;
    899      }
    900 
    901      // If we're throttling, we must not report events as they arrive
    902      // from platform, but instead let the throttler emit the events
    903      // after some time has elapsed.
    904      if (
    905        httpActivity.downloadThrottle &&
    906        HTTP_DOWNLOAD_ACTIVITIES.includes(activitySubtype)
    907      ) {
    908        const callback = this.#dispatchActivity.bind(this);
    909        httpActivity.downloadThrottle.addActivityCallback(
    910          callback,
    911          httpActivity,
    912          channel,
    913          activityType,
    914          activitySubtype,
    915          timestamp,
    916          extraSizeData,
    917          extraStringData
    918        );
    919      } else {
    920        this.#dispatchActivity(
    921          httpActivity,
    922          channel,
    923          activityType,
    924          activitySubtype,
    925          timestamp,
    926          extraSizeData,
    927          extraStringData
    928        );
    929      }
    930    }
    931  );
    932 
    933  /**
    934   * Craft the "event" object passed to the Watcher class in order
    935   * to instantiate the NetworkEventActor.
    936   *
    937   * /!\ This method does many other important things:
    938   * - Cancel requests blocked by DevTools
    939   * - Fetch request headers/cookies
    940   * - Set a few attributes on http activity object
    941   * - Set a few attributes on file activity object
    942   * - Register listener to record response content
    943   */
    944  #createNetworkEvent(
    945    httpActivity,
    946    { timestamp, blockedReason, extension, inProgressRequest } = {}
    947  ) {
    948    if (
    949      blockedReason === undefined &&
    950      this.#shouldBlockChannel(httpActivity.channel)
    951    ) {
    952      // Check the request URL with ones manually blocked by the user in DevTools.
    953      // If it's meant to be blocked, we cancel the request and annotate the event.
    954      httpActivity.channel.cancel(Cr.NS_BINDING_ABORTED);
    955      blockedReason = "devtools";
    956    }
    957 
    958    httpActivity.owner = this.#onNetworkEvent(
    959      {
    960        timestamp,
    961        blockedReason,
    962        extension,
    963        discardRequestBody: !this.#saveRequestAndResponseBodies,
    964        discardResponseBody: !this.#saveRequestAndResponseBodies,
    965      },
    966      httpActivity.channel
    967    );
    968 
    969    // Bug 1489217 - Avoid watching for response content for blocked or in-progress requests
    970    // as it can't be observed and would throw if we try.
    971    if (blockedReason === undefined && !inProgressRequest) {
    972      this.#setupResponseListener(httpActivity);
    973    }
    974 
    975    const wrapper = ChannelWrapper.get(httpActivity.channel);
    976    if (this.#authPromptListenerEnabled && !wrapper.hasNetworkAuthListener) {
    977      new lazy.NetworkAuthListener(httpActivity.channel, httpActivity.owner);
    978      wrapper.hasNetworkAuthListener = true;
    979    }
    980  }
    981 
    982  /**
    983   * Handler for ACTIVITY_SUBTYPE_REQUEST_HEADER. When a request starts the
    984   * headers are sent to the server. This method creates the |httpActivity|
    985   * object where we store the request and response information that is
    986   * collected through its lifetime.
    987   *
    988   * @private
    989   * @param nsIHttpChannel channel
    990   * @param number timestamp
    991   * @param string rawHeaders
    992   * @return void
    993   */
    994  #onRequestHeader(channel, timestamp, rawHeaders) {
    995    if (this.#ignoreChannelFunction(channel)) {
    996      return;
    997    }
    998 
    999    const httpActivity = this.#createOrGetActivityObject(channel);
   1000    if (timestamp) {
   1001      httpActivity.timings.REQUEST_HEADER = {
   1002        first: timestamp,
   1003        last: timestamp,
   1004      };
   1005    }
   1006 
   1007    // TODO: In theory httpActivity.owner should not be missing here because
   1008    // the network event should have been created in http-on-before-connect.
   1009    // However, there is a scenario in DevTools where this can still happen:
   1010    // if NetworkObserver clear() is called after the event was detected, the
   1011    // activity will be deleted again have an ownerless notification here.
   1012    if (!httpActivity.owner) {
   1013      // If we are not creating events using the early platform notification
   1014      // this should be the first time we are notified about this channel.
   1015      this.#createNetworkEvent(httpActivity, {
   1016        timestamp,
   1017      });
   1018    }
   1019 
   1020    httpActivity.owner.addRawHeaders({
   1021      channel,
   1022      rawHeaders,
   1023    });
   1024  }
   1025 
   1026  /**
   1027   * Check if the provided channel should be blocked given the current
   1028   * blocked URLs configured for this network observer.
   1029   */
   1030  #shouldBlockChannel(channel) {
   1031    for (const regexp of this.#blockedURLs.values()) {
   1032      if (regexp.test(channel.URI.spec)) {
   1033        return true;
   1034      }
   1035    }
   1036    return false;
   1037  }
   1038 
   1039  /**
   1040   * Find an HTTP activity object for the channel.
   1041   *
   1042   * @param nsIHttpChannel channel
   1043   *        The HTTP channel whose activity object we want to find.
   1044   * @return object
   1045   *        The HTTP activity object, or null if it is not found.
   1046   */
   1047  #findActivityObject(channel) {
   1048    return this.#openRequests.get(channel);
   1049  }
   1050 
   1051  /**
   1052   * Find an existing activity object, or create a new one. This
   1053   * object is used for storing all the request and response
   1054   * information.
   1055   *
   1056   * This is a HAR-like object. Conformance to the spec is not guaranteed at
   1057   * this point.
   1058   *
   1059   * @see http://www.softwareishard.com/blog/har-12-spec
   1060   * @param {nsIChannel} channel
   1061   *        The channel for which the activity object is created.
   1062   * @return object
   1063   *         The new HTTP activity object.
   1064   */
   1065  #createOrGetActivityObject(channel) {
   1066    let activity = this.#findActivityObject(channel);
   1067    if (!activity) {
   1068      const isHttpChannel = channel instanceof Ci.nsIHttpChannel;
   1069 
   1070      if (isHttpChannel) {
   1071        // Most of the data needed from the channel is only available via the
   1072        // nsIHttpChannelInternal interface.
   1073        channel.QueryInterface(Ci.nsIHttpChannelInternal);
   1074      } else {
   1075        channel.QueryInterface(Ci.nsIChannel);
   1076      }
   1077 
   1078      activity = {
   1079        // The nsIChannel for which this activity object was created.
   1080        channel,
   1081        // See #prepareRequestBody()
   1082        charset: isHttpChannel ? lazy.NetworkUtils.getCharset(channel) : null,
   1083        // The postData sent by this request.
   1084        sentBody: null,
   1085        // The URL for the current channel.
   1086        url: channel.URI.spec,
   1087        // The encoded response body size.
   1088        bodySize: 0,
   1089        // The response headers size.
   1090        headersSize: 0,
   1091        // needed for host specific security info but file urls do not have hostname
   1092        hostname: isHttpChannel ? channel.URI.host : null,
   1093        discardRequestBody: isHttpChannel
   1094          ? !this.#saveRequestAndResponseBodies
   1095          : false,
   1096        discardResponseBody: isHttpChannel
   1097          ? !this.#saveRequestAndResponseBodies
   1098          : false,
   1099        // internal timing information, see observeActivity()
   1100        timings: {},
   1101        // the activity owner which is notified when changes happen
   1102        owner: null,
   1103      };
   1104 
   1105      this.#openRequests.set(channel, activity);
   1106    }
   1107 
   1108    return activity;
   1109  }
   1110 
   1111  /**
   1112   * Block a request based on certain filtering options.
   1113   *
   1114   * Currently, exact URL match or URL patterns are supported.
   1115   */
   1116  blockRequest(filter) {
   1117    if (!filter || !filter.url) {
   1118      // In the future, there may be other types of filters, such as domain.
   1119      // For now, ignore anything other than URL.
   1120      return;
   1121    }
   1122 
   1123    this.#addBlockedUrl(filter.url);
   1124  }
   1125 
   1126  /**
   1127   * Unblock a request based on certain filtering options.
   1128   *
   1129   * Currently, exact URL match or URL patterns are supported.
   1130   */
   1131  unblockRequest(filter) {
   1132    if (!filter || !filter.url) {
   1133      // In the future, there may be other types of filters, such as domain.
   1134      // For now, ignore anything other than URL.
   1135      return;
   1136    }
   1137 
   1138    this.#blockedURLs.delete(filter.url);
   1139  }
   1140 
   1141  /**
   1142   * Updates the list of blocked request strings
   1143   *
   1144   * This match will be a (String).includes match, not an exact URL match
   1145   */
   1146  setBlockedUrls(urls) {
   1147    urls = urls || [];
   1148    this.#blockedURLs = new Map();
   1149    urls.forEach(url => this.#addBlockedUrl(url));
   1150  }
   1151 
   1152  #addBlockedUrl(url) {
   1153    this.#blockedURLs.set(url, lazy.wildcardToRegExp(url));
   1154  }
   1155 
   1156  /**
   1157   * Returns a list of blocked requests
   1158   * Useful as blockedURLs is mutated by both console & netmonitor
   1159   */
   1160  getBlockedUrls() {
   1161    return this.#blockedURLs.keys();
   1162  }
   1163 
   1164  override(url, path) {
   1165    this.#overrides.set(url, path);
   1166 
   1167    // Clear in-memory cache, so that the subsequent request reaches the
   1168    // http handling and the override works.
   1169    ChromeUtils.clearResourceCache({ url });
   1170  }
   1171 
   1172  removeOverride(url) {
   1173    this.#overrides.delete(url);
   1174 
   1175    ChromeUtils.clearResourceCache({ url });
   1176  }
   1177 
   1178  /**
   1179   * Setup the network response listener for the given HTTP activity. The
   1180   * NetworkResponseListener is responsible for storing the response body.
   1181   *
   1182   * @private
   1183   * @param object httpActivity
   1184   *        The HTTP activity object we are tracking.
   1185   */
   1186  #setupResponseListener(httpActivity) {
   1187    const channel = httpActivity.channel;
   1188    channel.QueryInterface(Ci.nsITraceableChannel);
   1189 
   1190    if (!httpActivity.fromCache) {
   1191      const throttler = this.#getThrottler();
   1192      if (throttler) {
   1193        httpActivity.downloadThrottle = throttler.manage(channel);
   1194      }
   1195    }
   1196 
   1197    // The response will be written into the outputStream of this pipe.
   1198    // This allows us to buffer the data we are receiving and read it
   1199    // asynchronously.
   1200    // Both ends of the pipe must be blocking.
   1201    const sink = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
   1202 
   1203    // The streams need to be blocking because this is required by the
   1204    // stream tee.
   1205    sink.init(false, false, this.#responsePipeSegmentSize, PR_UINT32_MAX, null);
   1206 
   1207    // Add listener for the response body.
   1208    const newListener = new lazy.NetworkResponseListener(httpActivity, {
   1209      decodedCertificateCache: this.#decodedCertificateCache,
   1210      decodeResponseBody: this.#decodeResponseBodies,
   1211      fromServiceWorker: httpActivity.fromServiceWorker,
   1212      responseBodyLimit: this.#responseBodyLimit,
   1213    });
   1214 
   1215    // Remember the input stream, so it isn't released by GC.
   1216    newListener.inputStream = sink.inputStream;
   1217    newListener.sink = sink;
   1218 
   1219    const tee = Cc["@mozilla.org/network/stream-listener-tee;1"].createInstance(
   1220      Ci.nsIStreamListenerTee
   1221    );
   1222 
   1223    const originalListener = channel.setNewListener(tee);
   1224 
   1225    tee.init(originalListener, sink.outputStream, newListener);
   1226  }
   1227 
   1228  /**
   1229   * Handler for ACTIVITY_SUBTYPE_REQUEST_BODY_SENT. Read and record the request
   1230   * body here. It will be available in addResponseStart.
   1231   *
   1232   * @private
   1233   * @param object httpActivity
   1234   *        The HTTP activity object we are working with.
   1235   */
   1236  #prepareRequestBody(httpActivity) {
   1237    // Return early if we don't need the request body, or if we've
   1238    // already found it.
   1239    if (httpActivity.discardRequestBody || httpActivity.sentBody !== null) {
   1240      return;
   1241    }
   1242 
   1243    const sentBody = lazy.NetworkHelper.readPostDataFromRequest(
   1244      httpActivity.channel,
   1245      httpActivity.charset
   1246    );
   1247 
   1248    if (sentBody !== null) {
   1249      httpActivity.sentBody = sentBody.data;
   1250    }
   1251  }
   1252 
   1253  /**
   1254   * Handler for ACTIVITY_SUBTYPE_TRANSACTION_CLOSE. This method updates the HAR
   1255   * timing information on the HTTP activity object and clears the request
   1256   * from the list of known open requests.
   1257   *
   1258   * @private
   1259   * @param object httpActivity
   1260   *        The HTTP activity object we work with.
   1261   */
   1262  #onTransactionClose(httpActivity) {
   1263    if (httpActivity.owner) {
   1264      const result = lazy.NetworkTimings.extractHarTimings(httpActivity);
   1265      const serverTimings =
   1266        lazy.NetworkTimings.extractServerTimings(httpActivity);
   1267 
   1268      httpActivity.owner.addServerTimings(serverTimings);
   1269      httpActivity.owner.addEventTimings(
   1270        result.total,
   1271        result.timings,
   1272        result.offsets
   1273      );
   1274    }
   1275  }
   1276 
   1277  #sendRequestBody(httpActivity) {
   1278    if (httpActivity.sentBody !== null) {
   1279      const limit = Services.prefs.getIntPref(
   1280        "devtools.netmonitor.requestBodyLimit"
   1281      );
   1282      const size = httpActivity.sentBody.length;
   1283      if (size > limit && limit > 0) {
   1284        httpActivity.sentBody = httpActivity.sentBody.substr(0, limit);
   1285      }
   1286      httpActivity.owner.addRequestPostData({
   1287        text: httpActivity.sentBody,
   1288        size,
   1289      });
   1290      httpActivity.sentBody = null;
   1291    }
   1292  }
   1293 
   1294  /*
   1295   * Clears the open requests channel map.
   1296   */
   1297  clear() {
   1298    this.#openRequests.clear();
   1299  }
   1300 
   1301  /**
   1302   * Suspend observer activity. This is called when the Network monitor actor stops
   1303   * listening.
   1304   */
   1305  destroy() {
   1306    if (this.#isDestroyed) {
   1307      return;
   1308    }
   1309 
   1310    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
   1311      gActivityDistributor.removeObserver(this);
   1312      Services.obs.removeObserver(
   1313        this.#httpResponseExaminer,
   1314        "http-on-examine-response"
   1315      );
   1316      Services.obs.removeObserver(
   1317        this.#httpResponseExaminer,
   1318        "http-on-examine-cached-response"
   1319      );
   1320      Services.obs.removeObserver(
   1321        this.#httpModifyExaminer,
   1322        "http-on-modify-request"
   1323      );
   1324      Services.obs.removeObserver(
   1325        this.#fileChannelExaminer,
   1326        "file-channel-opened"
   1327      );
   1328      Services.obs.removeObserver(
   1329        this.#dataChannelExaminer,
   1330        "data-channel-opened"
   1331      );
   1332 
   1333      Services.obs.removeObserver(
   1334        this.#httpStopRequest,
   1335        "http-on-stop-request"
   1336      );
   1337      Services.obs.removeObserver(
   1338        this.#httpBeforeConnect,
   1339        "http-on-before-connect"
   1340      );
   1341    } else {
   1342      Services.obs.removeObserver(
   1343        this.#httpFailedOpening,
   1344        "http-on-failed-opening-request"
   1345      );
   1346    }
   1347 
   1348    Services.obs.removeObserver(
   1349      this.#serviceWorkerRequest,
   1350      "service-worker-synthesized-response"
   1351    );
   1352 
   1353    this.#ignoreChannelFunction = null;
   1354    this.#onNetworkEvent = null;
   1355    this.#throttler = null;
   1356    this.#decodedCertificateCache.clear();
   1357    this.clear();
   1358 
   1359    this.#isDestroyed = true;
   1360  }
   1361 }