webperf-snippets 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -5
- package/package.json +1 -1
- package/snippets/CoreWebVitals/CLS.js +29 -15
- package/snippets/CoreWebVitals/INP.js +14 -7
- package/snippets/CoreWebVitals/LCP-Image-Entropy.js +29 -11
- package/snippets/CoreWebVitals/LCP-Subparts.js +4 -0
- package/snippets/CoreWebVitals/LCP-Trail.js +14 -3
- package/snippets/CoreWebVitals/LCP-Video-Candidate.js +20 -2
- package/snippets/CoreWebVitals/LCP.js +4 -2
- package/snippets/DevTools-Overrides/Fetch-XHR-Timeline-read.js +9 -2
- package/snippets/Interaction/DOM-Size-and-Depth.js +134 -0
- package/snippets/Interaction/Forced-Synchronous-Layout.js +33 -7
- package/snippets/Interaction/Input-Latency-Breakdown.js +45 -1
- package/snippets/Interaction/Interactions.js +16 -5
- package/snippets/Interaction/Layout-Shift-Loading-and-Interaction.js +44 -13
- package/snippets/Interaction/Long-Animation-Frames-Helpers.js +28 -1
- package/snippets/Interaction/Long-Animation-Frames-Script-Attribution.js +8 -0
- package/snippets/Interaction/Long-Animation-Frames.js +31 -2
- package/snippets/Interaction/LongTask.js +37 -5
- package/snippets/Interaction/Scroll-Performance.js +40 -0
- package/snippets/Interaction/WORKFLOWS.md +9 -0
- package/snippets/Loading/Back-Forward-Cache.js +174 -381
- package/snippets/Loading/CSS-Media-Queries-Analysis.js +76 -44
- package/snippets/Loading/Cache-Strategy-Analysis.js +52 -30
- package/snippets/Loading/Compression-Audit.js +172 -0
- package/snippets/Loading/Content-Visibility.js +8 -3
- package/snippets/Loading/Critical-CSS-Detection.js +8 -6
- package/snippets/Loading/Event-Processing-Time.js +15 -2
- package/snippets/Loading/FCP.js +39 -5
- package/snippets/Loading/Find-Above-The-Fold-Lazy-Loaded-Images.js +1 -1
- package/snippets/Loading/Find-non-Lazy-Loaded-Images-outside-of-the-viewport.js +7 -5
- package/snippets/Loading/First-And-Third-Party-Script-Info.js +40 -20
- package/snippets/Loading/First-And-Third-Party-Script-Timings.js +21 -13
- package/snippets/Loading/Fonts-Preloaded-Loaded-and-used-above-the-fold.js +35 -4
- package/snippets/Loading/Inline-CSS-Info-and-Size.js +8 -6
- package/snippets/Loading/Inline-Script-Info-and-Size.js +14 -7
- package/snippets/Loading/JS-Execution-Time-Breakdown.js +23 -10
- package/snippets/Loading/Priority-Hints-Audit.js +4 -1
- package/snippets/Loading/Resource-Hints-Validation.js +15 -0
- package/snippets/Loading/Resource-Hints.js +29 -8
- package/snippets/Loading/SSR-Hydration-Data-Analysis.js +77 -8
- package/snippets/Loading/Script-Loading.js +55 -25
- package/snippets/Loading/Server-Timing-Early-Hints.js +126 -0
- package/snippets/Loading/Service-Worker-Analysis.js +229 -53
- package/snippets/Loading/Speculation-Rules-Inspector.js +134 -0
- package/snippets/Loading/TTFB-Resources.js +59 -18
- package/snippets/Loading/TTFB-Sub-Parts.js +9 -2
- package/snippets/Loading/TTFB.js +3 -2
- package/snippets/Loading/Third-Party-Impact-by-Domain.js +216 -0
- package/snippets/Loading/Validate-Preload-Async-Defer-Scripts.js +21 -3
- package/snippets/Loading/WORKFLOWS.md +47 -1
- package/snippets/Loading/Webfont-Usage-Analyzer.js +332 -0
- package/snippets/Media/Image-Element-Audit.js +22 -3
- package/snippets/Media/Oversized-Images.js +217 -0
- package/snippets/Media/SVG-Embedded-Bitmap-Analysis.js +18 -3
- package/snippets/Media/WORKFLOWS.md +22 -0
- package/snippets/Resources/Network-Bandwidth-Connection-Quality.js +7 -0
- package/snippets/SCHEMA.md +1176 -0
- package/snippets/_shared/helpers.js +44 -0
- package/src/bin.js +55 -3
- package/src/crux.js +84 -0
- package/src/report-to.js +46 -0
- package/src/reporters/human.js +20 -1
- package/src/reporters/markdown.js +22 -1
- package/src/runner.js +4 -2
|
@@ -1,473 +1,266 @@
|
|
|
1
1
|
// Back/Forward Cache (bfcache) Analysis
|
|
2
2
|
// https://webperf-snippets.nucliweb.net
|
|
3
3
|
|
|
4
|
-
(() => {
|
|
4
|
+
(async () => {
|
|
5
|
+
// Only what the browser can report is analysed. Several blockers cannot be detected from
|
|
6
|
+
// JavaScript (unload listeners added with addEventListener, open WebSocket, BroadcastChannel
|
|
7
|
+
// or IndexedDB connections). After a back navigation, the NotRestoredReasons API reports
|
|
8
|
+
// the blockers the browser found, including those of embedded frames.
|
|
5
9
|
const results = {
|
|
6
10
|
supported: 'PerformanceNavigationTiming' in window,
|
|
7
11
|
wasRestored: false,
|
|
8
12
|
eligibility: null,
|
|
9
13
|
blockingReasons: [],
|
|
14
|
+
notRestoredReasons: null,
|
|
10
15
|
recommendations: [],
|
|
11
16
|
};
|
|
12
17
|
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'color: #22c55e; font-weight: bold; font-size: 14px;'
|
|
22
|
-
);
|
|
23
|
-
console.log(' Navigation was instant (0ms)');
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Check via Performance Navigation Timing
|
|
28
|
-
if (results.supported) {
|
|
29
|
-
const navEntry = performance.getEntriesByType('navigation')[0];
|
|
30
|
-
if (navEntry && navEntry.type === 'back_forward') {
|
|
31
|
-
// Check activation timing for bfcache restore
|
|
32
|
-
if (navEntry.activationStart > 0) {
|
|
33
|
-
results.wasRestored = true;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
18
|
+
// A pageshow event with persisted = true is the only reliable sign of a bfcache restore
|
|
19
|
+
window.addEventListener('pageshow', (event) => {
|
|
20
|
+
if (event.persisted) {
|
|
21
|
+
results.wasRestored = true;
|
|
22
|
+
console.log(
|
|
23
|
+
'%c⚡ Page restored from bfcache!',
|
|
24
|
+
'color: #22c55e; font-weight: bold; font-size: 14px;'
|
|
25
|
+
);
|
|
36
26
|
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const REASON_HELP = {
|
|
30
|
+
'unload-listener': 'unload event listeners block bfcache. Use pagehide or visibilitychange instead.',
|
|
31
|
+
'response-cache-control-no-store': 'Cache-Control: no-store on the page response prevents caching. Use no-cache instead.',
|
|
32
|
+
'websocket': 'Open WebSocket connections prevent bfcache. Close them on pagehide.',
|
|
33
|
+
'broadcastchannel': 'Open BroadcastChannel instances prevent bfcache. Close them on pagehide.',
|
|
34
|
+
'indexeddb-connection': 'Open IndexedDB connections prevent bfcache. Close them on pagehide.',
|
|
35
|
+
'masked': 'The browser does not disclose the exact reason (for example, a cross-origin frame).',
|
|
37
36
|
};
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const serializeReasons = (node) =>
|
|
39
|
+
node && {
|
|
40
|
+
url: node.url ?? null,
|
|
41
|
+
src: node.src ?? null,
|
|
42
|
+
id: node.id ?? null,
|
|
43
|
+
name: node.name ?? null,
|
|
44
|
+
// Each entry is a NotRestoredReasonDetails object with a reason property
|
|
45
|
+
// (plain strings are accepted as well)
|
|
46
|
+
reasons: (node.reasons || []).map((r) => (typeof r === 'string' ? r : r?.reason)).filter(Boolean),
|
|
47
|
+
children: (node.children || []).map(serializeReasons),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const flattenReasons = (node, frame) => [
|
|
51
|
+
...node.reasons.map((reason) => ({ reason, frame })),
|
|
52
|
+
...node.children.flatMap((child, i) =>
|
|
53
|
+
flattenReasons(child, child.src || child.url || child.name || child.id || `iframe ${i + 1}`)
|
|
54
|
+
),
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const analyze = async () => {
|
|
41
58
|
const issues = [];
|
|
42
|
-
const
|
|
59
|
+
const navEntry = performance.getEntriesByType('navigation')[0];
|
|
43
60
|
|
|
44
|
-
// 1.
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
// 1. Blockers reported by the browser (after a back/forward navigation)
|
|
62
|
+
results.notRestoredReasons = serializeReasons(navEntry?.notRestoredReasons);
|
|
63
|
+
const confirmed = results.notRestoredReasons
|
|
64
|
+
? flattenReasons(results.notRestoredReasons, results.notRestoredReasons.url || 'main frame')
|
|
65
|
+
: [];
|
|
66
|
+
confirmed.forEach(({ reason, frame }) => {
|
|
47
67
|
issues.push({
|
|
48
|
-
reason:
|
|
68
|
+
reason: `${reason} (${frame})`,
|
|
49
69
|
severity: 'high',
|
|
50
|
-
|
|
70
|
+
source: 'browser',
|
|
71
|
+
description: REASON_HELP[reason.toLowerCase()] || 'Reported by the browser as a bfcache blocker',
|
|
51
72
|
});
|
|
52
|
-
|
|
53
|
-
}
|
|
73
|
+
});
|
|
54
74
|
|
|
55
|
-
// 2.
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
if (meta && meta.content.includes('no-store')) {
|
|
75
|
+
// 2. unload handler assigned as a property. Listeners added with addEventListener
|
|
76
|
+
// cannot be detected from JavaScript.
|
|
77
|
+
if (window.onunload !== null && !confirmed.some(({ reason }) => reason === 'unload-listener')) {
|
|
59
78
|
issues.push({
|
|
60
|
-
reason: '
|
|
79
|
+
reason: 'window.onunload handler set',
|
|
61
80
|
severity: 'high',
|
|
62
|
-
|
|
81
|
+
source: 'detected',
|
|
82
|
+
description: REASON_HELP['unload-listener'],
|
|
63
83
|
});
|
|
64
|
-
recs.push('Remove Cache-Control: no-store or change to no-cache.');
|
|
65
84
|
}
|
|
66
85
|
|
|
67
|
-
// 3.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
// 3. Cache-Control: no-store on the page response. A HEAD request to the current URL
|
|
87
|
+
// exposes the header; the value can differ from the original navigation.
|
|
88
|
+
if (/^https?:$/.test(location.protocol)) {
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch(location.href, { method: 'HEAD', credentials: 'same-origin' });
|
|
91
|
+
const cacheControl = res.headers.get('cache-control') || '';
|
|
92
|
+
if (/no-store/i.test(cacheControl) && !confirmed.some(({ reason }) => reason === 'response-cache-control-no-store')) {
|
|
93
|
+
issues.push({
|
|
94
|
+
reason: 'Cache-Control: no-store on the page response',
|
|
95
|
+
severity: 'medium',
|
|
96
|
+
source: 'detected',
|
|
97
|
+
description: REASON_HELP['response-cache-control-no-store'],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// The header could not be read; nothing is reported
|
|
80
102
|
}
|
|
81
103
|
}
|
|
82
104
|
|
|
83
|
-
// 4.
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
// Just a warning - we can't detect if actually in use
|
|
87
|
-
issues.push({
|
|
88
|
-
reason: 'BroadcastChannel API available (check if in use)',
|
|
89
|
-
severity: 'low',
|
|
90
|
-
description: 'Open BroadcastChannel connections may block bfcache',
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 5. Check for embedded iframes with issues
|
|
95
|
-
const iframes = document.querySelectorAll('iframe');
|
|
96
|
-
if (iframes.length > 0) {
|
|
105
|
+
// 4. Context that is worth knowing but is not a blocker by itself
|
|
106
|
+
const iframeCount = document.querySelectorAll('iframe').length;
|
|
107
|
+
if (iframeCount > 0) {
|
|
97
108
|
issues.push({
|
|
98
|
-
reason: `${
|
|
99
|
-
severity: '
|
|
100
|
-
|
|
109
|
+
reason: `${iframeCount} iframe(s) on the page`,
|
|
110
|
+
severity: 'info',
|
|
111
|
+
source: 'note',
|
|
112
|
+
description: 'A bfcache blocker inside an iframe blocks the whole page. See notRestoredReasons after a back navigation.',
|
|
101
113
|
});
|
|
102
|
-
recs.push('Ensure iframes are also bfcache compatible.');
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
// 6. Check for Service Worker
|
|
106
116
|
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
|
|
107
|
-
// Service workers are OK, but just noting it
|
|
108
117
|
issues.push({
|
|
109
|
-
reason: 'Service Worker
|
|
118
|
+
reason: 'Service Worker controls the page',
|
|
110
119
|
severity: 'info',
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// 7. Check for open WebSocket/WebRTC
|
|
116
|
-
// Can't directly detect, but we can check for common libraries
|
|
117
|
-
if (window.WebSocket) {
|
|
118
|
-
issues.push({
|
|
119
|
-
reason: 'WebSocket API available (check if connections are open)',
|
|
120
|
-
severity: 'medium',
|
|
121
|
-
description: 'Open WebSocket connections block bfcache',
|
|
122
|
-
});
|
|
123
|
-
recs.push('Close WebSocket connections before page hide.');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// 8. Check for ongoing fetch/XHR
|
|
127
|
-
// Check if there are active resource requests
|
|
128
|
-
const resources = performance.getEntriesByType('resource');
|
|
129
|
-
const recent = resources.filter(r => r.responseEnd === 0 || (performance.now() - r.responseEnd < 100));
|
|
130
|
-
if (recent.length > 0) {
|
|
131
|
-
issues.push({
|
|
132
|
-
reason: `${recent.length} recent/ongoing network requests`,
|
|
133
|
-
severity: 'low',
|
|
134
|
-
description: 'Ongoing requests may prevent bfcache',
|
|
120
|
+
source: 'note',
|
|
121
|
+
description: 'Service Workers are compatible with bfcache.',
|
|
135
122
|
});
|
|
136
|
-
recs.push('Ensure requests complete or are aborted on page hide.');
|
|
137
123
|
}
|
|
138
124
|
|
|
139
125
|
results.blockingReasons = issues;
|
|
140
|
-
results.recommendations =
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
126
|
+
results.recommendations = [
|
|
127
|
+
...new Set(
|
|
128
|
+
issues
|
|
129
|
+
.filter((i) => i.severity !== 'info')
|
|
130
|
+
.map((i) => i.description)
|
|
131
|
+
),
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
if (issues.some((i) => i.source === 'browser')) {
|
|
147
135
|
results.eligibility = 'blocked';
|
|
148
|
-
} else if (
|
|
136
|
+
} else if (issues.some((i) => i.source === 'detected')) {
|
|
149
137
|
results.eligibility = 'likely-blocked';
|
|
150
|
-
} else if (issues.length > 0) {
|
|
151
|
-
results.eligibility = 'potentially-eligible';
|
|
152
138
|
} else {
|
|
153
|
-
results.eligibility = '
|
|
139
|
+
results.eligibility = 'no-blockers-detected';
|
|
154
140
|
}
|
|
155
141
|
|
|
156
142
|
return results.eligibility;
|
|
157
143
|
};
|
|
158
144
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
'likely-blocked': '#fb923c',
|
|
172
|
-
'blocked': '#ef4444',
|
|
173
|
-
};
|
|
145
|
+
const printReasons = (node, indent) => {
|
|
146
|
+
const pad = ' '.repeat(indent);
|
|
147
|
+
node.reasons.forEach((reason) => {
|
|
148
|
+
console.log(`${pad}• ${reason}`);
|
|
149
|
+
const help = REASON_HELP[reason.toLowerCase()];
|
|
150
|
+
if (help) console.log(`${pad} 💡 ${help}`);
|
|
151
|
+
});
|
|
152
|
+
node.children.forEach((child, i) => {
|
|
153
|
+
console.log(`${pad}Frame: ${child.src || child.url || child.name || child.id || `iframe ${i + 1}`}`);
|
|
154
|
+
printReasons(child, indent + 3);
|
|
155
|
+
});
|
|
156
|
+
};
|
|
174
157
|
|
|
158
|
+
const displayResults = () => {
|
|
159
|
+
const statusIcons = { 'no-blockers-detected': '🟢', 'likely-blocked': '🟠', blocked: '🔴' };
|
|
160
|
+
const statusColors = { 'no-blockers-detected': '#22c55e', 'likely-blocked': '#fb923c', blocked: '#ef4444' };
|
|
175
161
|
const statusText = {
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
|
|
179
|
-
'blocked': 'Blocked',
|
|
162
|
+
'no-blockers-detected': 'No blockers detected',
|
|
163
|
+
'likely-blocked': 'Likely blocked',
|
|
164
|
+
blocked: 'Blocked (reported by the browser)',
|
|
180
165
|
};
|
|
181
166
|
|
|
182
|
-
const icon = statusIcons[results.eligibility] || '⚪';
|
|
183
|
-
const color = statusColors[results.eligibility] || '#6b7280';
|
|
184
|
-
const text = statusText[results.eligibility] || 'Unknown';
|
|
185
|
-
|
|
186
167
|
console.group(
|
|
187
|
-
`%c${
|
|
188
|
-
`color: ${
|
|
168
|
+
`%c${statusIcons[results.eligibility] || '⚪'} bfcache: ${statusText[results.eligibility] || 'Unknown'}`,
|
|
169
|
+
`color: ${statusColors[results.eligibility] || '#6b7280'}; font-weight: bold; font-size: 14px;`
|
|
189
170
|
);
|
|
190
171
|
|
|
172
|
+
const navEntry = performance.getEntriesByType('navigation')[0];
|
|
191
173
|
console.log('');
|
|
192
174
|
console.log('%c📊 Status:', 'font-weight: bold;');
|
|
193
|
-
|
|
194
175
|
if (results.wasRestored) {
|
|
195
|
-
console.log('%c ✅ This page
|
|
196
|
-
|
|
176
|
+
console.log('%c ✅ This page was restored from bfcache', 'color: #22c55e;');
|
|
177
|
+
} else if (navEntry?.type === 'back_forward') {
|
|
178
|
+
console.log(' ℹ️ This page was loaded by a back/forward navigation, not restored from bfcache');
|
|
197
179
|
} else {
|
|
198
|
-
console.log(' ℹ️ This page was
|
|
199
|
-
console.log(' (Either first visit or bfcache was blocked on previous navigation)');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Navigation timing comparison
|
|
203
|
-
if (results.supported) {
|
|
204
|
-
const navEntry = performance.getEntriesByType('navigation')[0];
|
|
205
|
-
if (navEntry) {
|
|
206
|
-
console.log('');
|
|
207
|
-
console.log('%c🕐 Navigation Timing:', 'font-weight: bold;');
|
|
208
|
-
console.log(` Type: ${navEntry.type}`);
|
|
209
|
-
console.log(` Duration: ${Math.round(navEntry.duration)}ms`);
|
|
210
|
-
|
|
211
|
-
if (navEntry.type === 'back_forward') {
|
|
212
|
-
if (navEntry.duration < 10) {
|
|
213
|
-
console.log('%c ⚡ Fast back/forward (likely from bfcache)', 'color: #22c55e;');
|
|
214
|
-
} else {
|
|
215
|
-
console.log('%c 🐌 Slow back/forward (full reload)', 'color: #ef4444;');
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
180
|
+
console.log(' ℹ️ This page was loaded normally. Restoration is reported by a pageshow event.');
|
|
219
181
|
}
|
|
182
|
+
if (navEntry) console.log(` Navigation type: ${navEntry.type}`);
|
|
220
183
|
|
|
221
|
-
// Display issues
|
|
222
184
|
if (results.blockingReasons.length > 0) {
|
|
223
185
|
console.log('');
|
|
224
|
-
console.log('%c🔍
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
console.table(issueTable);
|
|
186
|
+
console.log('%c🔍 Findings:', 'font-weight: bold;');
|
|
187
|
+
console.table(
|
|
188
|
+
results.blockingReasons.map((i) => ({
|
|
189
|
+
Severity: i.severity.toUpperCase(),
|
|
190
|
+
Finding: i.reason,
|
|
191
|
+
Detail: i.description,
|
|
192
|
+
}))
|
|
193
|
+
);
|
|
233
194
|
} else {
|
|
234
195
|
console.log('');
|
|
235
|
-
console.log('%c✅ No
|
|
196
|
+
console.log('%c✅ No bfcache blockers detected', 'color: #22c55e; font-weight: bold;');
|
|
236
197
|
}
|
|
237
198
|
|
|
238
|
-
|
|
239
|
-
if (results.recommendations.length > 0) {
|
|
199
|
+
if (results.notRestoredReasons) {
|
|
240
200
|
console.log('');
|
|
241
|
-
console.
|
|
242
|
-
results.
|
|
243
|
-
console.log(
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// How to test
|
|
248
|
-
console.log('');
|
|
249
|
-
console.log('%c🧪 How to Test:', 'font-weight: bold;');
|
|
250
|
-
console.log('%c IMPORTANT: Run snippet BEFORE navigating away!', 'color: #f59e0b;');
|
|
251
|
-
console.log('');
|
|
252
|
-
console.log(' 1. Run this snippet (you already did this ✓)');
|
|
253
|
-
console.log(' 2. Navigate to another page');
|
|
254
|
-
console.log(' 3. Click browser Back button');
|
|
255
|
-
console.log(' 4. Check console for restoration message');
|
|
256
|
-
console.log('');
|
|
257
|
-
console.log(' Or use Chrome DevTools → Application → Back/forward cache');
|
|
258
|
-
|
|
259
|
-
console.groupEnd();
|
|
260
|
-
|
|
261
|
-
return results;
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
// Check for NotRestoredReasons API (Chrome 123+)
|
|
265
|
-
const checkNotRestoredReasons = () => {
|
|
266
|
-
if (!('PerformanceNavigationTiming' in window)) {
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const navEntry = performance.getEntriesByType('navigation')[0];
|
|
271
|
-
if (!navEntry || !navEntry.notRestoredReasons) {
|
|
272
|
-
return null;
|
|
201
|
+
console.group('%c🔬 NotRestoredReasons (reported by the browser)', 'font-weight: bold; color: #3b82f6;');
|
|
202
|
+
if (results.notRestoredReasons.reasons.length === 0 && results.notRestoredReasons.children.length === 0) {
|
|
203
|
+
console.log(' No reasons reported');
|
|
204
|
+
}
|
|
205
|
+
printReasons(results.notRestoredReasons, 3);
|
|
206
|
+
console.groupEnd();
|
|
273
207
|
}
|
|
274
208
|
|
|
275
|
-
console.group('%c🔬 Not Restored Reasons (Chrome 123+)', 'font-weight: bold; color: #3b82f6;');
|
|
276
|
-
|
|
277
|
-
const reasons = navEntry.notRestoredReasons;
|
|
278
|
-
|
|
279
209
|
console.log('');
|
|
280
|
-
console.log(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
} else {
|
|
285
|
-
console.log('%c ✅ Page was not blocked', 'color: #22c55e;');
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (reasons.url) {
|
|
289
|
-
console.log(` URL: ${reasons.url}`);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (reasons.id) {
|
|
293
|
-
console.log(` Frame ID: ${reasons.id}`);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (reasons.name) {
|
|
297
|
-
console.log(` Frame name: ${reasons.name}`);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (reasons.src) {
|
|
301
|
-
console.log(` Source: ${reasons.src}`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Parse NotRestoredReasonDetails array
|
|
305
|
-
// Each entry is an object with { reason: string, source: string }
|
|
306
|
-
// Common reasons: "WebSocket", "unload-listener", "IndexedDB", etc.
|
|
307
|
-
// Sources: "JavaScript", "UserAgentOnly", etc.
|
|
308
|
-
if (reasons.reasons && reasons.reasons.length > 0) {
|
|
309
|
-
console.log('');
|
|
310
|
-
console.log('%cBlocking reasons:', 'font-weight: bold; color: #ef4444;');
|
|
311
|
-
|
|
312
|
-
reasons.reasons.forEach((reasonDetail, idx) => {
|
|
313
|
-
// reasonDetail is a NotRestoredReasonDetails object
|
|
314
|
-
const reasonName = reasonDetail.reason || 'Unknown reason';
|
|
315
|
-
const reasonSource = reasonDetail.source || 'Unknown source';
|
|
316
|
-
|
|
317
|
-
console.group(` ${idx + 1}. ${reasonName}`);
|
|
318
|
-
|
|
319
|
-
// Show detailed information
|
|
320
|
-
console.log(` Reason: ${reasonName}`);
|
|
321
|
-
console.log(` Source: ${reasonSource}`);
|
|
322
|
-
|
|
323
|
-
// Add helpful context for common reasons
|
|
324
|
-
const reasonExplanations = {
|
|
325
|
-
'WebSocket': 'Open WebSocket connections prevent bfcache. Close them on pagehide event.',
|
|
326
|
-
'unload-listener': 'unload event listeners block bfcache. Use pagehide or visibilitychange instead.',
|
|
327
|
-
'response-cache-control-no-store': 'Cache-Control: no-store header prevents caching. Change to no-cache.',
|
|
328
|
-
'IndexedDB': 'Open IndexedDB transactions block bfcache. Close connections on pagehide.',
|
|
329
|
-
'BroadcastChannel': 'Open BroadcastChannel prevents bfcache. Close it on pagehide.',
|
|
330
|
-
'dedicated-worker': 'Dedicated workers can block bfcache. Terminate them on pagehide.',
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
if (reasonExplanations[reasonName]) {
|
|
334
|
-
console.log(` 💡 ${reasonExplanations[reasonName]}`);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
console.groupEnd();
|
|
338
|
-
});
|
|
339
|
-
}
|
|
210
|
+
console.log(
|
|
211
|
+
'%cNot every blocker is detectable from JavaScript: unload listeners added with addEventListener and open WebSocket, BroadcastChannel or IndexedDB connections only show up in NotRestoredReasons after a back navigation, or in DevTools → Application → Back/forward cache.',
|
|
212
|
+
'color: #6b7280;'
|
|
213
|
+
);
|
|
340
214
|
|
|
341
|
-
|
|
342
|
-
if (reasons.children && reasons.children.length > 0) {
|
|
215
|
+
if (results.recommendations.length > 0) {
|
|
343
216
|
console.log('');
|
|
344
|
-
console.log('%
|
|
345
|
-
|
|
346
|
-
reasons.children.forEach((child, idx) => {
|
|
347
|
-
console.group(` ${idx + 1}. ${child.url || child.src || 'iframe'}`);
|
|
348
|
-
|
|
349
|
-
if (child.blocked) {
|
|
350
|
-
console.log('%c Status: BLOCKED', 'color: #ef4444;');
|
|
351
|
-
} else {
|
|
352
|
-
console.log('%c Status: OK', 'color: #22c55e;');
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (child.id) {
|
|
356
|
-
console.log(` Frame ID: ${child.id}`);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (child.name) {
|
|
360
|
-
console.log(` Frame name: ${child.name}`);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (child.reasons && child.reasons.length > 0) {
|
|
364
|
-
console.log(' Blocking reasons:');
|
|
365
|
-
child.reasons.forEach(reasonDetail => {
|
|
366
|
-
console.log(` • ${reasonDetail.reason || 'Unknown'}`);
|
|
367
|
-
if (reasonDetail.source) {
|
|
368
|
-
console.log(` Source: ${reasonDetail.source}`);
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
console.groupEnd();
|
|
374
|
-
});
|
|
217
|
+
console.log('%c💡 Recommendations:', 'color: #3b82f6; font-weight: bold;');
|
|
218
|
+
results.recommendations.forEach((rec, idx) => console.log(` ${idx + 1}. ${rec}`));
|
|
375
219
|
}
|
|
376
220
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const reasonsTable = reasons.reasons.map(r => ({
|
|
383
|
-
Reason: r.reason || 'Unknown',
|
|
384
|
-
Source: r.source || 'N/A',
|
|
385
|
-
}));
|
|
386
|
-
|
|
387
|
-
console.table(reasonsTable);
|
|
388
|
-
}
|
|
221
|
+
console.log('');
|
|
222
|
+
console.log('%c🧪 How to test:', 'font-weight: bold;');
|
|
223
|
+
console.log(' 1. Navigate to another page');
|
|
224
|
+
console.log(' 2. Click the browser Back button');
|
|
225
|
+
console.log(' 3. Run this snippet again to read NotRestoredReasons, or use DevTools → Application → Back/forward cache');
|
|
389
226
|
|
|
390
227
|
console.groupEnd();
|
|
391
|
-
return reasons;
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
// Check if snippet was executed after a back/forward navigation
|
|
395
|
-
const checkExecutionTiming = () => {
|
|
396
|
-
const navEntry = performance.getEntriesByType('navigation')[0];
|
|
397
|
-
if (navEntry && navEntry.type === 'back_forward') {
|
|
398
|
-
console.log('');
|
|
399
|
-
console.group('%c⚠️ Timing Warning', 'color: #f59e0b; font-weight: bold;');
|
|
400
|
-
console.log('%cSnippet executed AFTER back/forward navigation.', 'color: #f59e0b;');
|
|
401
|
-
console.log('');
|
|
402
|
-
console.log('For complete analysis including bfcache restoration detection:');
|
|
403
|
-
console.log(' 1. Run this snippet FIRST');
|
|
404
|
-
console.log(' 2. Then navigate away');
|
|
405
|
-
console.log(' 3. Then click Back button');
|
|
406
|
-
console.log('');
|
|
407
|
-
console.log('Current analysis shows NotRestoredReasons from the navigation that just occurred.');
|
|
408
|
-
console.groupEnd();
|
|
409
|
-
console.log('');
|
|
410
|
-
}
|
|
411
228
|
};
|
|
412
229
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}, 100);
|
|
230
|
+
const buildResult = () => ({
|
|
231
|
+
script: 'Back-Forward-Cache',
|
|
232
|
+
status: 'ok',
|
|
233
|
+
details: {
|
|
234
|
+
eligibility: results.eligibility,
|
|
235
|
+
wasRestored: results.wasRestored,
|
|
236
|
+
supported: results.supported,
|
|
237
|
+
navigationType: performance.getEntriesByType('navigation')[0]?.type ?? null,
|
|
238
|
+
notRestoredReasons: results.notRestoredReasons,
|
|
239
|
+
},
|
|
240
|
+
issues: results.blockingReasons.map((i) => ({
|
|
241
|
+
severity: i.severity === 'high' ? 'error' : i.severity === 'medium' ? 'warning' : 'info',
|
|
242
|
+
message: i.reason,
|
|
243
|
+
})),
|
|
244
|
+
});
|
|
429
245
|
|
|
430
246
|
// Expose function for manual check
|
|
431
|
-
window.checkBfcache = () => {
|
|
432
|
-
|
|
247
|
+
window.checkBfcache = async () => {
|
|
248
|
+
await analyze();
|
|
433
249
|
displayResults();
|
|
434
|
-
|
|
435
|
-
return {
|
|
436
|
-
script: "Back-Forward-Cache",
|
|
437
|
-
status: "ok",
|
|
438
|
-
details: {
|
|
439
|
-
eligibility: results.eligibility,
|
|
440
|
-
wasRestored: results.wasRestored,
|
|
441
|
-
supported: results.supported,
|
|
442
|
-
},
|
|
443
|
-
issues: results.blockingReasons.map(i => ({
|
|
444
|
-
severity: i.severity === 'high' ? 'error' : i.severity === 'medium' ? 'warning' : 'info',
|
|
445
|
-
message: i.reason,
|
|
446
|
-
})),
|
|
447
|
-
};
|
|
250
|
+
return buildResult();
|
|
448
251
|
};
|
|
449
252
|
|
|
450
|
-
console.log('%c🚀 bfcache Analysis
|
|
451
|
-
console.log(' Results will appear shortly.');
|
|
253
|
+
console.log('%c🚀 bfcache Analysis', 'font-weight: bold; font-size: 14px;');
|
|
452
254
|
console.log(
|
|
453
255
|
' Call %ccheckBfcache()%c anytime to re-run analysis.',
|
|
454
256
|
'font-family: monospace; background: #f3f4f6; padding: 2px 4px;',
|
|
455
257
|
''
|
|
456
258
|
);
|
|
457
259
|
|
|
260
|
+
await analyze();
|
|
261
|
+
displayResults();
|
|
458
262
|
return {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
details: {
|
|
462
|
-
eligibility: results.eligibility,
|
|
463
|
-
wasRestored: results.wasRestored,
|
|
464
|
-
supported: results.supported,
|
|
465
|
-
},
|
|
466
|
-
issues: results.blockingReasons.map(i => ({
|
|
467
|
-
severity: i.severity === 'high' ? 'error' : i.severity === 'medium' ? 'warning' : 'info',
|
|
468
|
-
message: i.reason,
|
|
469
|
-
})),
|
|
470
|
-
message: "bfcache analysis running. Call checkBfcache() to re-run analysis.",
|
|
471
|
-
getDataFn: "checkBfcache",
|
|
263
|
+
...buildResult(),
|
|
264
|
+
message: 'bfcache analysis complete. Call checkBfcache() to re-run analysis.',
|
|
472
265
|
};
|
|
473
266
|
})();
|