tor-browser

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

network-event-actor.js (23257B)


      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 "use strict";
      6 
      7 const { Actor } = require("resource://devtools/shared/protocol.js");
      8 const {
      9  networkEventSpec,
     10 } = require("resource://devtools/shared/specs/network-event.js");
     11 
     12 const {
     13  TYPES: { NETWORK_EVENT },
     14 } = require("resource://devtools/server/actors/resources/index.js");
     15 const {
     16  LongStringActor,
     17 } = require("resource://devtools/server/actors/string.js");
     18 
     19 const lazy = {};
     20 
     21 ChromeUtils.defineESModuleGetters(
     22  lazy,
     23  {
     24    NetworkUtils:
     25      "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
     26  },
     27  { global: "contextual" }
     28 );
     29 
     30 const CONTENT_TYPE_REGEXP = /^content-type/i;
     31 
     32 const REDIRECT_STATES = [
     33  301, // HTTP Moved Permanently
     34  302, // HTTP Found
     35  303, // HTTP See Other
     36  307, // HTTP Temporary Redirect
     37 ];
     38 
     39 function isDataChannel(channel) {
     40  return channel instanceof Ci.nsIDataChannel;
     41 }
     42 
     43 function isFileChannel(channel) {
     44  return channel instanceof Ci.nsIFileChannel;
     45 }
     46 
     47 /**
     48 * Creates an actor for a network event.
     49 *
     50 * @class
     51 * @param {DevToolsServerConnection} conn
     52 *        The connection into which this Actor will be added.
     53 * @param {object} sessionContext
     54 *        The Session Context to help know what is debugged.
     55 *        See devtools/server/actors/watcher/session-context.js
     56 * @param {object} options
     57 *        Dictionary object with the following attributes:
     58 *        - onNetworkEventUpdate: optional function
     59 *          Callback for updates for the network event
     60 *        - onNetworkEventDestroy: optional function
     61 *          Callback for the destruction of the network event
     62 * @param {object} networkEventOptions
     63 *        Object describing the network event or the configuration of the
     64 *        network observer, and which cannot be easily inferred from the raw
     65 *        channel.
     66 *        - extension: optional object with `blocking` or `blocked` extension IDs
     67 *          id of the blocking webextension if any
     68 *        - blockedReason: optional number or string
     69 *        - discardRequestBody: boolean
     70 *        - discardResponseBody: boolean
     71 *        - fromCache: boolean
     72 *        - fromServiceWorker: boolean
     73 *        - timestamp: number
     74 * @param {nsIChannel} channel
     75 *        The channel related to this network event
     76 */
     77 class NetworkEventActor extends Actor {
     78  constructor(
     79    conn,
     80    sessionContext,
     81    { onNetworkEventUpdate, onNetworkEventDestroy },
     82    networkEventOptions,
     83    channel
     84  ) {
     85    super(conn, networkEventSpec);
     86 
     87    this._sessionContext = sessionContext;
     88    this._onNetworkEventUpdate = onNetworkEventUpdate;
     89    this._onNetworkEventDestroy = onNetworkEventDestroy;
     90 
     91    // Store the channelId which will act as resource id.
     92    this._channelId = channel.channelId;
     93 
     94    this._timings = {};
     95    this._serverTimings = [];
     96 
     97    this._discardRequestBody = !!networkEventOptions.discardRequestBody;
     98    this._discardResponseBody = !!networkEventOptions.discardResponseBody;
     99 
    100    this._response = {
    101      headers: [],
    102      cookies: [],
    103      content: {},
    104    };
    105 
    106    this._earlyHintsResponse = {
    107      headers: [],
    108      rawHeaders: "",
    109    };
    110 
    111    if (isDataChannel(channel) || isFileChannel(channel)) {
    112      this._innerWindowId = lazy.NetworkUtils.getChannelInnerWindowId(channel);
    113      this._isNavigationRequest = false;
    114 
    115      this._request = {
    116        cookies: [],
    117        headers: [],
    118        postData: {},
    119        rawHeaders: "",
    120      };
    121      this._resource = this._createResource(networkEventOptions, channel);
    122      return;
    123    }
    124 
    125    // innerWindowId and isNavigationRequest are used to check if the actor
    126    // should be destroyed when a window is destroyed. See network-events.js.
    127    this._innerWindowId = lazy.NetworkUtils.getChannelInnerWindowId(channel);
    128    this._isNavigationRequest = lazy.NetworkUtils.isNavigationRequest(channel);
    129 
    130    // Retrieve cookies and headers from the channel
    131    const { cookies, headers } =
    132      lazy.NetworkUtils.fetchRequestHeadersAndCookies(channel);
    133 
    134    this._request = {
    135      cookies,
    136      headers,
    137      postData: {},
    138    };
    139 
    140    this._resource = this._createResource(networkEventOptions, channel);
    141  }
    142 
    143  /**
    144   * Return the network event actor as a resource, and add the actorID which is
    145   * not available in the constructor yet.
    146   */
    147  asResource() {
    148    return {
    149      actor: this.actorID,
    150      ...this._resource,
    151    };
    152  }
    153 
    154  /**
    155   * Create the resource corresponding to this actor.
    156   */
    157  _createResource(networkEventOptions, channel) {
    158    let wsChannel;
    159    let method;
    160    if (isDataChannel(channel) || isFileChannel(channel)) {
    161      channel.QueryInterface(Ci.nsIChannel);
    162      wsChannel = null;
    163      method = "GET";
    164    } else {
    165      channel = channel.QueryInterface(Ci.nsIHttpChannel);
    166      wsChannel = lazy.NetworkUtils.getWebSocketChannel(channel);
    167      method = channel.requestMethod;
    168    }
    169 
    170    // Use the WebSocket channel URL for websockets.
    171    const url = wsChannel ? wsChannel.URI.spec : channel.URI.spec;
    172 
    173    let browsingContextID =
    174      lazy.NetworkUtils.getChannelBrowsingContextID(channel);
    175 
    176    // Ensure that we have a browsing context ID for all requests.
    177    // Only privileged requests debugged via the Browser Toolbox (sessionContext.type == "all") can be unrelated to any browsing context.
    178    if (!browsingContextID && this._sessionContext.type != "all") {
    179      throw new Error(`Got a request ${url} without a browsingContextID set`);
    180    }
    181 
    182    // The browsingContextID is used by the ResourceCommand on the client
    183    // to find the related Target Front.
    184    //
    185    // For now in the browser and web extension toolboxes, requests
    186    // do not relate to any specific WindowGlobalTargetActor
    187    // as we are still using a unique target (ParentProcessTargetActor) for everything.
    188    if (
    189      this._sessionContext.type == "all" ||
    190      this._sessionContext.type == "webextension"
    191    ) {
    192      browsingContextID = -1;
    193    }
    194 
    195    const cause = lazy.NetworkUtils.getCauseDetails(channel);
    196    // Both xhr and fetch are flagged as XHR in DevTools.
    197    const isXHR = cause.type == "xhr" || cause.type == "fetch";
    198 
    199    // For websocket requests the serial is used instead of the channel id.
    200    const stacktraceResourceId =
    201      cause.type == "websocket" ? wsChannel.serial : channel.channelId;
    202 
    203    // If a timestamp was provided, it is a high resolution timestamp
    204    // corresponding to ACTIVITY_SUBTYPE_REQUEST_HEADER. Fallback to Date.now().
    205    const timeStamp = networkEventOptions.timestamp
    206      ? networkEventOptions.timestamp / 1000
    207      : Date.now();
    208 
    209    let blockedReason = networkEventOptions.blockedReason;
    210 
    211    // Check if blockedReason was set to a falsy value, meaning the blocked did
    212    // not give an explicit blocked reason.
    213    if (
    214      blockedReason === 0 ||
    215      blockedReason === false ||
    216      blockedReason === null ||
    217      blockedReason === ""
    218    ) {
    219      blockedReason = "unknown";
    220    }
    221 
    222    const resource = {
    223      resourceId: this._channelId,
    224      resourceType: NETWORK_EVENT,
    225      blockedReason,
    226      extension: networkEventOptions.extension,
    227      browsingContextID,
    228      cause,
    229      // This is used specifically in the browser toolbox console to distinguish privileged
    230      // resources from the parent process from those from the content.
    231      chromeContext: lazy.NetworkUtils.isChannelFromSystemPrincipal(channel),
    232      innerWindowId: this._innerWindowId,
    233      isNavigationRequest: this._isNavigationRequest,
    234      isThirdPartyTrackingResource:
    235        lazy.NetworkUtils.isThirdPartyTrackingResource(channel),
    236      isXHR,
    237      method,
    238      priority: lazy.NetworkUtils.getChannelPriority(channel),
    239      private: lazy.NetworkUtils.isChannelPrivate(channel),
    240      referrerPolicy: lazy.NetworkUtils.getReferrerPolicy(channel),
    241      stacktraceResourceId,
    242      startedDateTime: new Date(timeStamp).toISOString(),
    243      securityFlags: channel.loadInfo.securityFlags,
    244      timeStamp,
    245      timings: {},
    246      url,
    247    };
    248 
    249    return resource;
    250  }
    251 
    252  /**
    253   * Releases this actor from the pool.
    254   */
    255  destroy(conn) {
    256    if (!this._channelId) {
    257      return;
    258    }
    259 
    260    if (this._onNetworkEventDestroy) {
    261      this._onNetworkEventDestroy(this._channelId);
    262    }
    263 
    264    this._channelId = null;
    265    super.destroy(conn);
    266  }
    267 
    268  release() {
    269    // Per spec, destroy is automatically going to be called after this request
    270  }
    271 
    272  getInnerWindowId() {
    273    return this._innerWindowId;
    274  }
    275 
    276  isNavigationRequest() {
    277    return this._isNavigationRequest;
    278  }
    279 
    280  /**
    281   * The "getRequestHeaders" packet type handler.
    282   *
    283   * @return object
    284   *         The response packet - network request headers.
    285   */
    286  getRequestHeaders() {
    287    let rawHeaders;
    288    let headersSize = 0;
    289    if (this._request.rawHeaders) {
    290      headersSize = this._request.rawHeaders.length;
    291      rawHeaders = this._createLongStringActor(this._request.rawHeaders);
    292    }
    293 
    294    return {
    295      headers: this._request.headers.map(header => ({
    296        name: header.name,
    297        value: this._createLongStringActor(header.value),
    298      })),
    299      headersSize,
    300      rawHeaders,
    301    };
    302  }
    303 
    304  /**
    305   * The "getRequestCookies" packet type handler.
    306   *
    307   * @return object
    308   *         The response packet - network request cookies.
    309   */
    310  getRequestCookies() {
    311    return {
    312      cookies: this._request.cookies.map(cookie => ({
    313        name: cookie.name,
    314        value: this._createLongStringActor(cookie.value),
    315      })),
    316    };
    317  }
    318 
    319  /**
    320   * The "getRequestPostData" packet type handler.
    321   *
    322   * @return object
    323   *         The response packet - network POST data.
    324   */
    325  getRequestPostData() {
    326    let postDataText;
    327    if (this._request.postData.text) {
    328      // Create a long string actor for the postData text if needed.
    329      postDataText = this._createLongStringActor(this._request.postData.text);
    330    }
    331 
    332    return {
    333      postData: {
    334        size: this._request.postData.size,
    335        text: postDataText,
    336      },
    337      postDataDiscarded: this._discardRequestBody,
    338    };
    339  }
    340 
    341  /**
    342   * The "getSecurityInfo" packet type handler.
    343   *
    344   * @return object
    345   *         The response packet - connection security information.
    346   */
    347  getSecurityInfo() {
    348    return {
    349      securityInfo: this._securityInfo,
    350    };
    351  }
    352 
    353  /**
    354   * The "getEarlyHintsResponseHeaders" packet type handler.
    355   *
    356   * @return object
    357   *         The response packet - network early hint response headers.
    358   */
    359  getEarlyHintsResponseHeaders() {
    360    const { rawHeaders, headers } = this._earlyHintsResponse;
    361    return {
    362      headers: headers.map(header => ({
    363        name: header.name,
    364        value: this._createLongStringActor(header.value),
    365      })),
    366      headersSize: rawHeaders.length,
    367      rawHeaders: this._createLongStringActor(rawHeaders),
    368    };
    369  }
    370 
    371  /**
    372   * The "getResponseHeaders" packet type handler.
    373   *
    374   * @return object
    375   *         The response packet - network response headers.
    376   */
    377  getResponseHeaders() {
    378    let rawHeaders;
    379    let headersSize = 0;
    380    if (this._response.rawHeaders) {
    381      headersSize = this._response.rawHeaders.length;
    382      rawHeaders = this._createLongStringActor(this._response.rawHeaders);
    383    }
    384 
    385    return {
    386      headers: this._response.headers.map(header => ({
    387        name: header.name,
    388        value: this._createLongStringActor(header.value),
    389      })),
    390      headersSize,
    391      rawHeaders,
    392    };
    393  }
    394 
    395  /**
    396   * The "getResponseCache" packet type handler.
    397   *
    398   * @return object
    399   *         The cache packet - network cache information.
    400   */
    401  getResponseCache() {
    402    return {
    403      cache: this._response.responseCache,
    404    };
    405  }
    406 
    407  /**
    408   * The "getResponseCookies" packet type handler.
    409   *
    410   * @return object
    411   *         The response packet - network response cookies.
    412   */
    413  getResponseCookies() {
    414    // As opposed to request cookies, response cookies can come with additional
    415    // properties.
    416    const cookieOptionalProperties = [
    417      "domain",
    418      "expires",
    419      "httpOnly",
    420      "path",
    421      "samesite",
    422      "secure",
    423    ];
    424 
    425    return {
    426      cookies: this._response.cookies.map(cookie => {
    427        const cookieResponse = {
    428          name: cookie.name,
    429          value: this._createLongStringActor(cookie.value),
    430        };
    431 
    432        for (const prop of cookieOptionalProperties) {
    433          if (prop in cookie) {
    434            cookieResponse[prop] = cookie[prop];
    435          }
    436        }
    437        return cookieResponse;
    438      }),
    439    };
    440  }
    441 
    442  /**
    443   * The "getResponseContent" packet type handler.
    444   *
    445   * @return object
    446   *         The response packet - network response content.
    447   */
    448  getResponseContent() {
    449    const content = { ...this._response.content };
    450    if (this._response.contentLongStringActor) {
    451      // Remove the old actor from the pool as new actor will be created
    452      // with updated content.
    453      this.unmanage(this._response.contentLongStringActor);
    454    }
    455    this._response.contentLongStringActor = new LongStringActor(
    456      this.conn,
    457      content.text
    458    );
    459    // bug 1462561 - Use "json" type and manually manage/marshall actors to workaround
    460    // protocol.js performance issue
    461    this.manage(this._response.contentLongStringActor);
    462    content.text = this._response.contentLongStringActor.form();
    463 
    464    return {
    465      content,
    466      contentDiscarded: this._discardResponseBody,
    467    };
    468  }
    469 
    470  /**
    471   * The "getEventTimings" packet type handler.
    472   *
    473   * @return object
    474   *         The response packet - network event timings.
    475   */
    476  getEventTimings() {
    477    return {
    478      timings: this._timings,
    479      totalTime: this._totalTime,
    480      offsets: this._offsets,
    481      serverTimings: this._serverTimings,
    482      serviceWorkerTimings: this._serviceWorkerTimings,
    483    };
    484  }
    485 
    486  /******************************************************************
    487   * Listeners for new network event data coming from NetworkMonitor.
    488   *****************************************************************/
    489 
    490  addCacheDetails({ fromCache, fromServiceWorker }) {
    491    this._resource.fromCache = fromCache;
    492    this._resource.fromServiceWorker = fromServiceWorker;
    493    this._onEventUpdate(lazy.NetworkUtils.NETWORK_EVENT_TYPES.CACHE_DETAILS, {
    494      fromCache,
    495      fromServiceWorker,
    496    });
    497  }
    498 
    499  addRawHeaders({ channel, rawHeaders }) {
    500    this._request.rawHeaders = rawHeaders;
    501 
    502    // For regular requests, some additional headers might only be available
    503    // when rawHeaders are provided, so we update the request headers here.
    504    const { headers } =
    505      lazy.NetworkUtils.fetchRequestHeadersAndCookies(channel);
    506    this._request.headers = headers;
    507  }
    508 
    509  /**
    510   * Add network request POST data.
    511   *
    512   * @param object postData
    513   *        The request POST data.
    514   */
    515  addRequestPostData(postData) {
    516    // Ignore calls when this actor is already destroyed
    517    if (this.isDestroyed()) {
    518      return;
    519    }
    520 
    521    this._request.postData = postData;
    522    this._onEventUpdate(
    523      lazy.NetworkUtils.NETWORK_EVENT_TYPES.REQUEST_POSTDATA,
    524      {}
    525    );
    526  }
    527 
    528  /**
    529   * Add the initial network response information.
    530   *
    531   * @param {object} options
    532   * @param {nsIChannel} options.channel
    533   * @param {boolean} options.fromCache
    534   * @param {string} options.rawHeaders
    535   * @param {string} options.proxyResponseRawHeaders
    536   * @param {string} options.earlyHintsResponseRawHeaders
    537   */
    538  addResponseStart({
    539    channel,
    540    fromCache,
    541    rawHeaders = "",
    542    proxyResponseRawHeaders,
    543    earlyHintsResponseRawHeaders,
    544  }) {
    545    // Ignore calls when this actor is already destroyed
    546    if (this.isDestroyed()) {
    547      return;
    548    }
    549 
    550    // Avoid reading responseStatus and responseStatusText dynamically from
    551    // the channel as much as possible. In some cases, eg 304 Not Modified, the
    552    // channel is dynamically replaced with the original channel and we lose the
    553    // original information about the channel status as the request progresses.
    554    // Reading this synchronously in this method is fine, but we extract them as
    555    // separate variables here to bring some attention to this issue.
    556    const { responseStatus, responseStatusText } = channel;
    557 
    558    fromCache = fromCache || lazy.NetworkUtils.isFromCache(channel);
    559    const isDataOrFile = isDataChannel(channel) || isFileChannel(channel);
    560 
    561    // Read response headers and cookies.
    562    let responseHeaders = [];
    563    let responseCookies = [];
    564    if (!this._blockedReason && !isDataOrFile) {
    565      const { cookies, headers } =
    566        lazy.NetworkUtils.fetchResponseHeadersAndCookies(channel);
    567      responseCookies = cookies;
    568      responseHeaders = headers;
    569    }
    570 
    571    // Handle response headers
    572    this._response.rawHeaders = rawHeaders;
    573    this._response.headers = responseHeaders;
    574    this._response.cookies = responseCookies;
    575 
    576    // Handle the rest of the response start metadata.
    577    this._response.headersSize = rawHeaders ? rawHeaders.length : 0;
    578 
    579    // Early Hint Response Headers
    580    if (earlyHintsResponseRawHeaders) {
    581      this._earlyHintsResponse.headers =
    582        lazy.NetworkUtils.parseEarlyHintsResponseHeaders(
    583          earlyHintsResponseRawHeaders
    584        );
    585      this._earlyHintsResponse.rawHeaders = earlyHintsResponseRawHeaders;
    586    }
    587 
    588    // Discard the response body for known redirect response statuses.
    589    if (REDIRECT_STATES.includes(responseStatus)) {
    590      this._discardResponseBody = true;
    591    }
    592 
    593    // Mime type needs to be sent on response start for identifying an sse channel.
    594    const contentTypeHeader = responseHeaders.find(header =>
    595      CONTENT_TYPE_REGEXP.test(header.name)
    596    );
    597 
    598    let mimeType = "";
    599    if (contentTypeHeader) {
    600      mimeType = contentTypeHeader.value;
    601    }
    602 
    603    let waitingTime = null;
    604    if (!isDataOrFile) {
    605      const timedChannel = channel.QueryInterface(Ci.nsITimedChannel);
    606      waitingTime = Math.round(
    607        (timedChannel.responseStartTime - timedChannel.requestStartTime) / 1000
    608      );
    609    }
    610 
    611    let proxyInfo = [];
    612    if (proxyResponseRawHeaders) {
    613      // The typical format for proxy raw headers is `HTTP/2 200 Connected\r\nConnection: keep-alive`
    614      // The content is parsed and split into http version (HTTP/2), status(200) and status text (Connected)
    615      proxyInfo = proxyResponseRawHeaders.split("\r\n")[0].split(" ");
    616    }
    617 
    618    this._onEventUpdate(lazy.NetworkUtils.NETWORK_EVENT_TYPES.RESPONSE_START, {
    619      httpVersion: isDataOrFile
    620        ? null
    621        : lazy.NetworkUtils.getHttpVersion(channel),
    622      mimeType,
    623      remoteAddress: fromCache ? "" : channel.remoteAddress,
    624      remotePort: fromCache ? "" : channel.remotePort,
    625      status: isDataOrFile ? "200" : responseStatus + "",
    626      statusText: isDataOrFile ? "0K" : responseStatusText,
    627      earlyHintsStatus: earlyHintsResponseRawHeaders ? "103" : "",
    628      waitingTime,
    629      isResolvedByTRR: channel.isResolvedByTRR,
    630      proxyHttpVersion: proxyInfo[0],
    631      proxyStatus: proxyInfo[1],
    632      proxyStatusText: proxyInfo[2],
    633    });
    634  }
    635 
    636  /**
    637   * Add connection security information.
    638   *
    639   * @param object info
    640   *        The object containing security information.
    641   */
    642  addSecurityInfo(info, isRacing) {
    643    // Ignore calls when this actor is already destroyed
    644    if (this.isDestroyed()) {
    645      return;
    646    }
    647 
    648    this._securityInfo = info;
    649 
    650    this._onEventUpdate(lazy.NetworkUtils.NETWORK_EVENT_TYPES.SECURITY_INFO, {
    651      state: info.state,
    652      isRacing,
    653    });
    654  }
    655 
    656  /**
    657   * Add network response content end.
    658   *
    659   * @param object
    660   */
    661  addResponseContentComplete({ blockedReason, extension }) {
    662    // Ignore calls when this actor is already destroyed
    663    if (this.isDestroyed()) {
    664      return;
    665    }
    666 
    667    this._onEventUpdate(
    668      lazy.NetworkUtils.NETWORK_EVENT_TYPES.RESPONSE_CONTENT_COMPLETE,
    669      {
    670        blockedReason,
    671        extension,
    672      }
    673    );
    674  }
    675 
    676  /**
    677   * Add network response content.
    678   *
    679   * @param object content
    680   *        The response content.
    681   */
    682  addResponseContent(content, data) {
    683    const { blockedReason, extension } = data || {};
    684 
    685    // Ignore calls when this actor is already destroyed
    686    if (this.isDestroyed()) {
    687      return;
    688    }
    689 
    690    this._response.content = content;
    691    this._onEventUpdate(
    692      lazy.NetworkUtils.NETWORK_EVENT_TYPES.RESPONSE_CONTENT,
    693      {
    694        mimeType: content.mimeType,
    695        contentSize: content.size,
    696        transferredSize: content.transferredSize,
    697        blockedReason,
    698        extension,
    699      }
    700    );
    701  }
    702 
    703  addResponseCache(content) {
    704    // Ignore calls when this actor is already destroyed
    705    if (this.isDestroyed()) {
    706      return;
    707    }
    708    this._response.responseCache = content.responseCache;
    709    this._onEventUpdate(
    710      lazy.NetworkUtils.NETWORK_EVENT_TYPES.RESPONSE_CACHE,
    711      {}
    712    );
    713  }
    714 
    715  /**
    716   * Add network event timing information.
    717   *
    718   * @param number total
    719   *        The total time of the network event.
    720   * @param object timings
    721   *        Timing details about the network event.
    722   * @param object offsets
    723   */
    724  addEventTimings(total, timings, offsets) {
    725    // Ignore calls when this actor is already destroyed
    726    if (this.isDestroyed()) {
    727      return;
    728    }
    729 
    730    this._totalTime = total;
    731    this._timings = timings;
    732    this._offsets = offsets;
    733 
    734    this._onEventUpdate(lazy.NetworkUtils.NETWORK_EVENT_TYPES.EVENT_TIMINGS, {
    735      totalTime: total,
    736    });
    737  }
    738 
    739  /**
    740   * Store server timing information. They are merged together
    741   * with network event timing data when they are available and
    742   * notification sent to the client.
    743   * See `addEventTimings` above for more information.
    744   *
    745   * @param object serverTimings
    746   *        Timing details extracted from the Server-Timing header.
    747   */
    748  addServerTimings(serverTimings) {
    749    if (!serverTimings || this.isDestroyed()) {
    750      return;
    751    }
    752    this._serverTimings = serverTimings;
    753  }
    754 
    755  /**
    756   * Store service worker timing information. They are merged together
    757   * with network event timing data when they are available and
    758   * notification sent to the client.
    759   * See `addEventTimnings`` above for more information.
    760   *
    761   * @param object serviceWorkerTimings
    762   *        Timing details extracted from the Timed Channel.
    763   */
    764  addServiceWorkerTimings(serviceWorkerTimings) {
    765    if (!serviceWorkerTimings || this.isDestroyed()) {
    766      return;
    767    }
    768    this._serviceWorkerTimings = serviceWorkerTimings;
    769  }
    770 
    771  _createLongStringActor(string) {
    772    if (string?.actorID) {
    773      return string;
    774    }
    775 
    776    const longStringActor = new LongStringActor(this.conn, string);
    777    // bug 1462561 - Use "json" type and manually manage/marshall actors to workaround
    778    // protocol.js performance issue
    779    this.manage(longStringActor);
    780    return longStringActor.form();
    781  }
    782 
    783  /**
    784   * Sends the updated event data to the client
    785   *
    786   * @private
    787   * @param string updateType
    788   * @param object data
    789   *        The properties that have changed for the event
    790   */
    791  _onEventUpdate(updateType, data) {
    792    if (this._onNetworkEventUpdate) {
    793      this._onNetworkEventUpdate({
    794        resourceId: this._channelId,
    795        updateType,
    796        ...data,
    797      });
    798    }
    799  }
    800 }
    801 
    802 exports.NetworkEventActor = NetworkEventActor;