log("loading chrome.load.times.js");

if (!window.chrome) {
  // Use the exact property descriptor found in headful Chrome
  // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')`
  Object.defineProperty(window, "chrome", {
    writable: true,
    enumerable: true,
    configurable: false, // note!
    value: {} // We'll extend that later
  });
}

// That means we're running headful and don't need to mock anything
if ("loadTimes" in window.chrome) {
  warn("skipping chrome loadtimes update, running in headful mode");
}

// Check that the Navigation Timing API v1 + v2 is available, we need that
if (window.performance?.timing || window.PerformancePaintTiming) {
  const { performance } = window;

  // Some stuff is not available on about:blank as it requires a navigation to occur,
  // let's harden the code to not fail then:
  const ntEntryFallback = {
    nextHopProtocol: "h2",
    type: "other"
  };

  // The API exposes some funky info regarding the connection
  const protocolInfo = {
    get connectionInfo() {
      const ntEntry = performance.getEntriesByType("navigation")[0] ||
        ntEntryFallback;
      return ntEntry.nextHopProtocol;
    },
    get npnNegotiatedProtocol() {
      // NPN is deprecated in favor of ALPN, but this implementation returns the
      // HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.
      const ntEntry = performance.getEntriesByType("navigation")[0] ||
        ntEntryFallback;
      return ["h2", "hq"].includes(ntEntry.nextHopProtocol) ?
        ntEntry.nextHopProtocol :
        "unknown";
    },
    get navigationType() {
      const ntEntry = performance.getEntriesByType("navigation")[0] ||
        ntEntryFallback;
      return ntEntry.type;
    },
    get wasAlternateProtocolAvailable() {
      // The Alternate-Protocol header is deprecated in favor of Alt-Svc
      // (https://www.mnot.net/blog/2016/03/09/alt-svc), so technically this
      // should always return false.
      return false;
    },
    get wasFetchedViaSpdy() {
      // SPDY is deprecated in favor of HTTP/2, but this implementation returns
      // true for HTTP/2 or HTTP2+QUIC/39 as well.
      const ntEntry = performance.getEntriesByType("navigation")[0] ||
        ntEntryFallback;
      return ["h2", "hq"].includes(ntEntry.nextHopProtocol);
    },
    get wasNpnNegotiated() {
      // NPN is deprecated in favor of ALPN, but this implementation returns true
      // for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.
      const ntEntry = performance.getEntriesByType("navigation")[0] ||
        ntEntryFallback;
      return ["h2", "hq"].includes(ntEntry.nextHopProtocol);
    }
  };

  const { timing } = window.performance;

  // Truncate number to specific number of decimals, most of the `loadTimes` stuff has 3
  function toFixed(num, fixed) {
    const re = new RegExp("^-?\\d+(?:.\\d{0," + (fixed || -1) + "})?");
    return num.toString().match(re)[0];
  }

  const timingInfo = {
    get firstPaintAfterLoadTime() {
      // This was never actually implemented and always returns 0.
      return 0;
    },
    get requestTime() {
      return timing.navigationStart / 1000;
    },
    get startLoadTime() {
      return timing.navigationStart / 1000;
    },
    get commitLoadTime() {
      return timing.responseStart / 1000;
    },
    get finishDocumentLoadTime() {
      return timing.domContentLoadedEventEnd / 1000;
    },
    get finishLoadTime() {
      return timing.loadEventEnd / 1000;
    },
    get firstPaintTime() {
      const fpEntry = performance.getEntriesByType("paint")[0] || {
        startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occured (`about:blank`)
      };
      return toFixed((fpEntry.startTime + performance.timeOrigin) / 1000, 3);
    }
  };

  window.chrome.loadTimes = function() {
    return {
      ...protocolInfo,
      ...timingInfo
    };
  };
  utils.patchToString(window.chrome.loadTimes);
}