|
12 | 12 |
|
13 | 13 | const obs = window.obs || Object.create(null); |
14 | 14 |
|
15 | | - const navigation = performance.getEntriesByType('navigation')[0]; |
16 | | - |
17 | | - const { connection } = navigator; |
18 | | - |
19 | | - if (connection && 'rtt' in connection) { |
20 | | - lux.addData('rtt', connection.rtt); |
21 | | - } |
22 | | - |
23 | 15 | // Keys we intend to send. Keep in sync with obs.js |
24 | 16 | const keys = [ |
25 | 17 | 'canShowRichMedia', |
|
45 | 37 | } |
46 | 38 | } |
47 | 39 |
|
| 40 | + // Surface the browser-reported static CPU performance tier. |
| 41 | + if ('cpuPerformance' in navigator) { |
| 42 | + const { cpuPerformance } = navigator; |
| 43 | + if ( |
| 44 | + typeof cpuPerformance === 'number' && |
| 45 | + Number.isFinite(cpuPerformance) && |
| 46 | + Number.isInteger(cpuPerformance) && |
| 47 | + cpuPerformance >= 0 |
| 48 | + ) { |
| 49 | + lux.addData('cpuTier', cpuPerformance); |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + const navigation = performance.getEntriesByType('navigation')[0]; |
| 54 | + |
48 | 55 | if (!navigation) return; |
49 | 56 |
|
50 | | - // Was the response from HTTP cache or the network? |
| 57 | + // Separate locally served documents from those that required network |
| 58 | + // transfer. |
51 | 59 | const { transferSize } = navigation; |
52 | 60 |
|
53 | 61 | if (transferSize === 0) { |
|
56 | 64 | lux.addData('fromCache', false); |
57 | 65 | } |
58 | 66 |
|
59 | | - // Was the response from the back–forward cache? |
| 67 | + // Record the final HTTP response status exposed for the navigation. |
| 68 | + if ('responseStatus' in navigation) { |
| 69 | + const { responseStatus } = navigation; |
| 70 | + if ( |
| 71 | + typeof responseStatus === 'number' && |
| 72 | + Number.isFinite(responseStatus) && |
| 73 | + Number.isInteger(responseStatus) && |
| 74 | + responseStatus > 0 |
| 75 | + ) { |
| 76 | + lux.addData('responseStatus', responseStatus); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + // Identify navigations intercepted by a service worker. |
| 81 | + if ('workerStart' in navigation) { |
| 82 | + const { workerStart } = navigation; |
| 83 | + if (typeof workerStart === 'number' && Number.isFinite(workerStart)) { |
| 84 | + if (workerStart > 0) { |
| 85 | + lux.addData('viaSW', true); |
| 86 | + } else if (workerStart === 0) { |
| 87 | + lux.addData('viaSW', false); |
| 88 | + } |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + // Approximate the time spent starting or activating the service worker. |
| 93 | + if ('workerStart' in navigation) { |
| 94 | + const { fetchStart, workerStart } = navigation; |
| 95 | + if ( |
| 96 | + typeof fetchStart === 'number' && |
| 97 | + Number.isFinite(fetchStart) && |
| 98 | + typeof workerStart === 'number' && |
| 99 | + Number.isFinite(workerStart) && |
| 100 | + workerStart > 0 && |
| 101 | + fetchStart >= workerStart |
| 102 | + ) { |
| 103 | + const swStartupTime = Math.round(fetchStart - workerStart); |
| 104 | + if (Number.isFinite(swStartupTime) && swStartupTime >= 0) { |
| 105 | + lux.addData('swStartupTime', swStartupTime); |
| 106 | + } |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + // Keep restored views distinct from conventional navigations in RUM analysis. |
60 | 111 | window.addEventListener('pageshow', (event) => { |
61 | 112 | lux.addData('frombfCache', event.persisted); |
62 | 113 | }); |
63 | 114 |
|
| 115 | + // Preserve prerender history so pre-activation timings remain interpretable. |
64 | 116 | lux.addData( |
65 | 117 | 'fromPrerender', |
66 | 118 | document.prerendering || navigation.activationStart > 0 |
67 | 119 | ); |
68 | 120 |
|
| 121 | + // Measure the proportion of HTML body bytes saved by content encoding. |
| 122 | + const { decodedBodySize, encodedBodySize } = navigation; |
| 123 | + if ( |
| 124 | + typeof decodedBodySize === 'number' && |
| 125 | + Number.isFinite(decodedBodySize) && |
| 126 | + decodedBodySize > 0 && |
| 127 | + typeof encodedBodySize === 'number' && |
| 128 | + Number.isFinite(encodedBodySize) && |
| 129 | + encodedBodySize > 0 |
| 130 | + ) { |
| 131 | + const compressionDelta = 1 - encodedBodySize / decodedBodySize; |
| 132 | + if (Number.isFinite(compressionDelta)) { |
| 133 | + lux.addData('compressionDelta', compressionDelta); |
| 134 | + } |
| 135 | + } |
| 136 | + |
69 | 137 | // Unattributed Navigation Overhead (UNO): TTFB not covered by named phases. |
70 | 138 | // https://calendar.perfplanet.com/2024/uno/ |
71 | 139 | const span = (end, start) => Math.max(0, end - start); |
|
81 | 149 | lux.addData('uno', uno); |
82 | 150 | } |
83 | 151 |
|
84 | | - // Time to Last Byte (TTLB) |
| 152 | + // Capture the full document response time, beyond the first byte as captured |
| 153 | + // by TTFB. |
85 | 154 | if (navigation.responseEnd && navigation.startTime >= 0) { |
86 | 155 | const ttlb = Math.round(navigation.responseEnd - navigation.startTime); |
87 | 156 | if (Number.isFinite(ttlb) && ttlb >= 0) { |
|
92 | 161 | // First Potential Paint (FPP) |
93 | 162 | const headEnd = performance.getEntriesByName('HEAD_End')[0]; |
94 | 163 |
|
95 | | - if (headEnd && navigation.startTime >= 0) { |
| 164 | + if (navigation && headEnd && navigation.startTime >= 0) { |
96 | 165 | const fpp = Math.round(headEnd.startTime - navigation.startTime); |
97 | 166 | if (Number.isFinite(fpp) && fpp >= 0) { |
98 | 167 | lux.addData('fpp', fpp); |
|
0 commit comments