tor-browser

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

NetworkTimings.sys.mjs (11960B)


      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 * Helper singleton to compute network timings for a given httpActivity object.
      7 */
      8 export const NetworkTimings = new (class {
      9  /**
     10   * Convert the httpActivity timings in HAR compatible timings. The HTTP
     11   * activity object holds the raw timing information in |timings| - these are
     12   * timings stored for each activity notification. The HAR timing information
     13   * is constructed based on these lower level data.
     14   *
     15   * @param {object} httpActivity
     16   *     The HTTP activity object we are working with.
     17   * @return {object}
     18   *     This object holds three properties:
     19   *     - {Object} offsets: the timings computed as offsets from the initial
     20   *     request start time.
     21   *     - {Object} timings: the HAR timings object
     22   *     - {number} total: the total time for all of the request and response
     23   */
     24  extractHarTimings(httpActivity) {
     25    if (httpActivity.fromCache) {
     26      // If it came from the browser cache, we have no timing
     27      // information and these should all be 0
     28      return this.getEmptyHARTimings();
     29    }
     30 
     31    const timings = httpActivity.timings;
     32    const harTimings = {};
     33    // If the TCP Fast Open option or tls1.3 0RTT is used tls and data can
     34    // be dispatched in SYN packet and not after tcp socket is connected.
     35    // To demostrate this properly we will calculated TLS and send start time
     36    // relative to CONNECTING_TO.
     37    // Similary if 0RTT is used, data can be sent as soon as a TLS handshake
     38    // starts.
     39 
     40    harTimings.blocked = this.#getBlockedTiming(timings);
     41    // DNS timing information is available only in when the DNS record is not
     42    // cached.
     43    harTimings.dns = this.#getDnsTiming(timings);
     44    harTimings.connect = this.#getConnectTiming(timings);
     45    harTimings.ssl = this.#getSslTiming(timings);
     46 
     47    let { secureConnectionStartTime, secureConnectionStartTimeRelative } =
     48      this.#getSecureConnectionStartTimeInfo(timings);
     49 
     50    // sometimes the connection information events are attached to a speculative
     51    // channel instead of this one, but necko might glue them back together in the
     52    // nsITimedChannel interface used by Resource and Navigation Timing
     53    const timedChannel = httpActivity.channel.QueryInterface(
     54      Ci.nsITimedChannel
     55    );
     56 
     57    const {
     58      tcpConnectEndTimeTc,
     59      connectStartTimeTc,
     60      connectEndTimeTc,
     61      secureConnectionStartTimeTc,
     62      domainLookupEndTimeTc,
     63      domainLookupStartTimeTc,
     64    } = this.#getDataFromTimedChannel(timedChannel);
     65 
     66    if (
     67      harTimings.connect <= 0 &&
     68      timedChannel &&
     69      tcpConnectEndTimeTc != 0 &&
     70      connectStartTimeTc != 0
     71    ) {
     72      harTimings.connect = tcpConnectEndTimeTc - connectStartTimeTc;
     73      if (secureConnectionStartTimeTc != 0) {
     74        harTimings.ssl = connectEndTimeTc - secureConnectionStartTimeTc;
     75        secureConnectionStartTime =
     76          secureConnectionStartTimeTc - connectStartTimeTc;
     77        secureConnectionStartTimeRelative = true;
     78      } else {
     79        harTimings.ssl = -1;
     80      }
     81    } else if (
     82      timedChannel &&
     83      timings.STATUS_TLS_STARTING &&
     84      secureConnectionStartTimeTc != 0
     85    ) {
     86      // It can happen that TCP Fast Open actually have not sent any data and
     87      // timings.STATUS_TLS_STARTING.first value will be corrected in
     88      // timedChannel.secureConnectionStartTime
     89      if (secureConnectionStartTimeTc > timings.STATUS_TLS_STARTING.first) {
     90        // TCP Fast Open actually did not sent any data.
     91        harTimings.ssl = connectEndTimeTc - secureConnectionStartTimeTc;
     92        secureConnectionStartTimeRelative = false;
     93      }
     94    }
     95 
     96    if (
     97      harTimings.dns <= 0 &&
     98      timedChannel &&
     99      domainLookupEndTimeTc != 0 &&
    100      domainLookupStartTimeTc != 0
    101    ) {
    102      harTimings.dns = domainLookupEndTimeTc - domainLookupStartTimeTc;
    103    }
    104 
    105    harTimings.send = this.#getSendTiming(timings);
    106    harTimings.wait = this.#getWaitTiming(timings);
    107    harTimings.receive = this.#getReceiveTiming(timings);
    108    let { startSendingTime, startSendingTimeRelative } =
    109      this.#getStartSendingTimeInfo(timings, connectStartTimeTc);
    110 
    111    if (secureConnectionStartTimeRelative) {
    112      const time = Math.max(Math.round(secureConnectionStartTime / 1000), -1);
    113      secureConnectionStartTime = time;
    114    }
    115    if (startSendingTimeRelative) {
    116      const time = Math.max(Math.round(startSendingTime / 1000), -1);
    117      startSendingTime = time;
    118    }
    119 
    120    const ot = this.#calculateOffsetAndTotalTime(
    121      harTimings,
    122      secureConnectionStartTime,
    123      startSendingTimeRelative,
    124      secureConnectionStartTimeRelative,
    125      startSendingTime
    126    );
    127    return {
    128      total: ot.total,
    129      timings: harTimings,
    130      offsets: ot.offsets,
    131    };
    132  }
    133 
    134  extractServerTimings(httpActivity) {
    135    const channel = httpActivity.channel;
    136    if (!channel || !channel.serverTiming) {
    137      return null;
    138    }
    139 
    140    const serverTimings = new Array(channel.serverTiming.length);
    141 
    142    for (let i = 0; i < channel.serverTiming.length; ++i) {
    143      const { name, duration, description } =
    144        channel.serverTiming.queryElementAt(i, Ci.nsIServerTiming);
    145      serverTimings[i] = { name, duration, description };
    146    }
    147 
    148    return serverTimings;
    149  }
    150 
    151  extractServiceWorkerTimings(httpActivity) {
    152    if (!httpActivity.fromServiceWorker) {
    153      return null;
    154    }
    155    const timedChannel = httpActivity.channel.QueryInterface(
    156      Ci.nsITimedChannel
    157    );
    158 
    159    return {
    160      launchServiceWorker:
    161        timedChannel.launchServiceWorkerEndTime -
    162        timedChannel.launchServiceWorkerStartTime,
    163      requestToServiceWorker:
    164        timedChannel.dispatchFetchEventEndTime -
    165        timedChannel.dispatchFetchEventStartTime,
    166      handledByServiceWorker:
    167        timedChannel.handleFetchEventEndTime -
    168        timedChannel.handleFetchEventStartTime,
    169    };
    170  }
    171 
    172  /**
    173   * For some requests such as cached or data: URI requests, we don't have
    174   * access to any timing information so all timings should be 0.
    175   *
    176   * @return {object}
    177   *     A timings object (@see extractHarTimings), with all values set to 0.
    178   */
    179  getEmptyHARTimings() {
    180    return {
    181      total: 0,
    182      timings: {
    183        blocked: 0,
    184        dns: 0,
    185        ssl: 0,
    186        connect: 0,
    187        send: 0,
    188        wait: 0,
    189        receive: 0,
    190      },
    191      offsets: {
    192        blocked: 0,
    193        dns: 0,
    194        ssl: 0,
    195        connect: 0,
    196        send: 0,
    197        wait: 0,
    198        receive: 0,
    199      },
    200    };
    201  }
    202 
    203  #getBlockedTiming(timings) {
    204    if (timings.STATUS_RESOLVING && timings.STATUS_CONNECTING_TO) {
    205      return timings.STATUS_RESOLVING.first - timings.REQUEST_HEADER.first;
    206    } else if (timings.STATUS_SENDING_TO) {
    207      return timings.STATUS_SENDING_TO.first - timings.REQUEST_HEADER.first;
    208    }
    209 
    210    return -1;
    211  }
    212 
    213  #getDnsTiming(timings) {
    214    if (timings.STATUS_RESOLVING && timings.STATUS_RESOLVED) {
    215      return timings.STATUS_RESOLVED.last - timings.STATUS_RESOLVING.first;
    216    }
    217 
    218    return -1;
    219  }
    220 
    221  #getConnectTiming(timings) {
    222    if (timings.STATUS_CONNECTING_TO && timings.STATUS_CONNECTED_TO) {
    223      return (
    224        timings.STATUS_CONNECTED_TO.last - timings.STATUS_CONNECTING_TO.first
    225      );
    226    }
    227 
    228    return -1;
    229  }
    230 
    231  #getReceiveTiming(timings) {
    232    if (timings.RESPONSE_START && timings.RESPONSE_COMPLETE) {
    233      return timings.RESPONSE_COMPLETE.last - timings.RESPONSE_START.first;
    234    }
    235 
    236    return -1;
    237  }
    238 
    239  #getWaitTiming(timings) {
    240    if (timings.RESPONSE_START) {
    241      return (
    242        timings.RESPONSE_START.first -
    243        (timings.REQUEST_BODY_SENT || timings.STATUS_SENDING_TO).last
    244      );
    245    }
    246 
    247    return -1;
    248  }
    249 
    250  #getSslTiming(timings) {
    251    if (timings.STATUS_TLS_STARTING && timings.STATUS_TLS_ENDING) {
    252      return timings.STATUS_TLS_ENDING.last - timings.STATUS_TLS_STARTING.first;
    253    }
    254 
    255    return -1;
    256  }
    257 
    258  #getSendTiming(timings) {
    259    if (timings.STATUS_SENDING_TO) {
    260      return timings.STATUS_SENDING_TO.last - timings.STATUS_SENDING_TO.first;
    261    } else if (timings.REQUEST_HEADER && timings.REQUEST_BODY_SENT) {
    262      return timings.REQUEST_BODY_SENT.last - timings.REQUEST_HEADER.first;
    263    }
    264 
    265    return -1;
    266  }
    267 
    268  #getDataFromTimedChannel(timedChannel) {
    269    const lookUpArr = [
    270      "tcpConnectEndTime",
    271      "connectStartTime",
    272      "connectEndTime",
    273      "secureConnectionStartTime",
    274      "domainLookupEndTime",
    275      "domainLookupStartTime",
    276    ];
    277 
    278    return lookUpArr.reduce((prev, prop) => {
    279      const propName = prop + "Tc";
    280      return {
    281        ...prev,
    282        [propName]: (() => {
    283          if (!timedChannel) {
    284            return 0;
    285          }
    286 
    287          const value = timedChannel[prop];
    288 
    289          if (
    290            value != 0 &&
    291            timedChannel.asyncOpenTime &&
    292            value < timedChannel.asyncOpenTime
    293          ) {
    294            return 0;
    295          }
    296 
    297          return value;
    298        })(),
    299      };
    300    }, {});
    301  }
    302 
    303  #getSecureConnectionStartTimeInfo(timings) {
    304    let secureConnectionStartTime = 0;
    305    let secureConnectionStartTimeRelative = false;
    306 
    307    if (timings.STATUS_TLS_STARTING && timings.STATUS_TLS_ENDING) {
    308      if (timings.STATUS_CONNECTING_TO) {
    309        secureConnectionStartTime =
    310          timings.STATUS_TLS_STARTING.first -
    311          timings.STATUS_CONNECTING_TO.first;
    312      }
    313 
    314      if (secureConnectionStartTime < 0) {
    315        secureConnectionStartTime = 0;
    316      }
    317      secureConnectionStartTimeRelative = true;
    318    }
    319 
    320    return {
    321      secureConnectionStartTime,
    322      secureConnectionStartTimeRelative,
    323    };
    324  }
    325 
    326  #getStartSendingTimeInfo(timings, connectStartTimeTc) {
    327    let startSendingTime = 0;
    328    let startSendingTimeRelative = false;
    329 
    330    if (timings.STATUS_SENDING_TO) {
    331      if (timings.STATUS_CONNECTING_TO) {
    332        startSendingTime =
    333          timings.STATUS_SENDING_TO.first - timings.STATUS_CONNECTING_TO.first;
    334        startSendingTimeRelative = true;
    335      } else if (connectStartTimeTc != 0) {
    336        startSendingTime = timings.STATUS_SENDING_TO.first - connectStartTimeTc;
    337        startSendingTimeRelative = true;
    338      }
    339 
    340      if (startSendingTime < 0) {
    341        startSendingTime = 0;
    342      }
    343    }
    344    return { startSendingTime, startSendingTimeRelative };
    345  }
    346 
    347  #convertTimeToMs(timing) {
    348    return Math.max(Math.round(timing / 1000), -1);
    349  }
    350 
    351  #calculateOffsetAndTotalTime(
    352    harTimings,
    353    secureConnectionStartTime,
    354    startSendingTimeRelative,
    355    secureConnectionStartTimeRelative,
    356    startSendingTime
    357  ) {
    358    let totalTime = 0;
    359    for (const timing in harTimings) {
    360      const time = this.#convertTimeToMs(harTimings[timing]);
    361      harTimings[timing] = time;
    362      if (time > -1 && timing != "connect" && timing != "ssl") {
    363        totalTime += time;
    364      }
    365    }
    366 
    367    // connect, ssl and send times can be overlapped.
    368    if (startSendingTimeRelative) {
    369      totalTime += startSendingTime;
    370    } else if (secureConnectionStartTimeRelative) {
    371      totalTime += secureConnectionStartTime;
    372      totalTime += harTimings.ssl;
    373    }
    374 
    375    const offsets = {};
    376    offsets.blocked = 0;
    377    offsets.dns = harTimings.blocked;
    378    offsets.connect = offsets.dns + harTimings.dns;
    379    if (secureConnectionStartTimeRelative) {
    380      offsets.ssl = offsets.connect + secureConnectionStartTime;
    381    } else {
    382      offsets.ssl = offsets.connect + harTimings.connect;
    383    }
    384    if (startSendingTimeRelative) {
    385      offsets.send = offsets.connect + startSendingTime;
    386      if (!secureConnectionStartTimeRelative) {
    387        offsets.ssl = offsets.send - harTimings.ssl;
    388      }
    389    } else {
    390      offsets.send = offsets.ssl + harTimings.ssl;
    391    }
    392    offsets.wait = offsets.send + harTimings.send;
    393    offsets.receive = offsets.wait + harTimings.wait;
    394 
    395    return {
    396      total: totalTime,
    397      offsets,
    398    };
    399  }
    400 })();