function getNormalizedRequestTiming(networkRecord) {
  const extractedTimes = extractPartialTiming(networkRecord);

  const possibleStarts = [
    extractedTimes.networkRequestTime,
    extractedTimes.requestTime,
    extractedTimes.rendererStartTime,
    // Note: since redirectResponseTimestamp (aka the redirect's networkEndTime) is
    // used as a last resort, some redirected requests may start before the
    // redirect ends. Up to the caller to override if this matters.
    extractedTimes.redirectResponseTimestamp,
  ];
  const networkRequestTime = possibleStarts.find(timeDefined) ?? defaultStart;

  const rendererStartTime = timeDefined(extractedTimes.rendererStartTime) ?
      extractedTimes.rendererStartTime :
      // Because `rendererStartTime` was added much later, several tests assume
      // requests start at `networkRequestTime`, so default to the same timestamp.
      networkRequestTime;

  // `networkRequestTime` and `requestTime` are the same.
  const requestTime = networkRequestTime;

  // `receiveHeadersEnd` is milliseconds after `requestTime`.
  let {receiveHeadersEnd} = extractedTimes;
  if (!timeDefined(receiveHeadersEnd)) {
    if (timeDefined(extractedTimes.responseHeadersEndTime)) {
      receiveHeadersEnd = extractedTimes.responseHeadersEndTime - requestTime;
    } else if (timeDefined(extractedTimes.networkEndTime)) {
      // Pick a time between last defined `timing` (may just be `requestTime`) and `networkEndTime`.
      receiveHeadersEnd = (extractedTimes.relativeTimingMax +
          (extractedTimes.networkEndTime - requestTime)) / 2;
    } else {
      receiveHeadersEnd = Math.max(extractedTimes.relativeTimingMax, defaultTimingOffset);
    }
  }

  // Equivalent to responseHeadersEndTime if provided due to `receiveHeadersEnd` construction above.
  const responseHeadersEndTime = requestTime + receiveHeadersEnd;

  // networkEndTime is allowed to be -1, e.g. for incomplete requests.
  const networkEndTime = extractedTimes.networkEndTime ??
      (responseHeadersEndTime + defaultTimingOffset);

  const responseTimestamp = extractedTimes.responseTimestamp ?? requestTime + receiveHeadersEnd;

  return {
    rendererStartTime,
    networkRequestTime,
    responseHeadersEndTime,
    networkEndTime,
    responseTimestamp,
    timing: {
      // TODO: other `timing` properties could have default values.
      ...networkRecord.timing,
      requestTime: Math.round(requestTime * 1_000) / 1_000_000, // Convert back to seconds.
      receiveHeadersEnd,
    },
  };
}
Takes partial network timing and fills in the missing values to at least
ensure time is linear (if not realistic). The main timing properties on
`NetworkRequest`, and `requestTime` and `receiveHeadersEnd` on
`NetworkRequest['timing']` will be generated. The other `timing` properties
will not be automatically generated, but will be copied if provided, so it's
up to the caller to make them correctly ordered with each other.
If no absolute times are provided at all:
- request will start at `defaultStart`
- finish receiving headers `defaultTimingOffset` ms later
- then end `defaultTimingOffset` ms after that
Throws an error if conditions appear impossible to satisfy.
@param {Partial<NetworkRequest>} values
@return {NormalizedRequestTime}