File size: 4,430 Bytes
7e4b742
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
chrome_load_times = """
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) {
    throw new Error('skipping chrome loadtimes update, running in headfull mode')
}

// Check that the Navigation Timing API v1 + v2 is available, we need that
if (
    window.performance ||
    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) {
        var 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)
}
"""