-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
426 lines (393 loc) · 18.2 KB
/
index.html
File metadata and controls
426 lines (393 loc) · 18.2 KB
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Firefox Session Restore Bug — Test Page</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
max-width: 780px;
margin: 0 auto;
padding: 24px;
line-height: 1.6;
color: #1a1a1a;
background: #ffffff;
}
h1 { font-size: 1.4em; margin-bottom: 4px; }
h2 { font-size: 1.1em; margin: 20px 0 8px; }
.subtitle { font-size: 0.85em; color: #666; margin-bottom: 16px; }
.result-banner {
padding: 14px 18px;
border-radius: 6px;
margin-bottom: 20px;
font-weight: 600;
font-size: 1em;
}
.result-pass { background: #d4edda; color: #155724; border: 2px solid #28a745; }
.result-fail { background: #f8d7da; color: #721c24; border: 2px solid #dc3545; }
.result-warn { background: #fff3cd; color: #856404; border: 2px solid #ffc107; }
.result-wait { background: #e2e3e5; color: #383d41; border: 2px solid #6c757d; }
.probe-table {
width: 100%;
border-collapse: collapse;
margin: 12px 0;
font-size: 0.88em;
}
.probe-table th {
text-align: left;
padding: 6px 10px;
background: #f0f0f0;
border: 1px solid #ddd;
font-weight: 600;
}
.probe-table td {
padding: 6px 10px;
border: 1px solid #ddd;
font-family: monospace;
word-break: break-all;
}
.probe-table .pass { background: #d4edda; }
.probe-table .fail { background: #f8d7da; }
.probe-table .warn { background: #fff3cd; }
/* Ad placeholders — visible by default, hidden by SW-injected <style> */
.ad-placeholder {
background: #dc3545;
color: #fff;
padding: 16px;
margin: 10px 0;
border-radius: 4px;
font-weight: 600;
text-align: center;
font-size: 0.95em;
}
/* SW status indicator — hidden by default, shown by SW-injected <style> */
#sw-status-indicator {
display: none;
padding: 10px 14px;
border-radius: 4px;
font-size: 0.9em;
font-weight: 600;
margin: 8px 0;
}
.section {
margin: 20px 0;
padding: 16px;
background: #fafafa;
border: 1px solid #eee;
border-radius: 6px;
}
.section h2 { margin-top: 0; }
.section ol, .section ul { padding-left: 20px; }
.section li { margin: 4px 0; font-size: 0.9em; }
.note { font-size: 0.85em; color: #666; margin-top: 8px; }
kbd {
background: #eee; border: 1px solid #ccc;
border-radius: 3px; padding: 1px 5px; font-size: 0.85em;
}
.log { margin: 8px 0; max-height: 200px; overflow-y: auto; }
.log-entry {
font-family: monospace;
font-size: 0.8em;
padding: 1px 0;
color: #555;
}
.log-entry.error { color: #dc3545; font-weight: 600; }
</style>
</head>
<body>
<h1>Firefox Session Restore — Network Bypass Test</h1>
<p class="subtitle">
Uses a Service Worker to simulate a network proxy (like AdGuard).
On session restore, if the browser bypasses the SW, injected styles are lost.
</p>
<div id="result-banner" class="result-banner result-wait">
Waiting for Service Worker registration...
</div>
<!--
SW STATUS INDICATOR
Hidden by default CSS above. The SW injects a <style> that makes this
visible with a green background. If the SW is bypassed, this stays hidden.
-->
<div id="sw-status-indicator">
SW proxy active
</div>
<table class="probe-table">
<tr>
<th>Probe</th>
<th>What it checks</th>
<th>Result</th>
</tr>
<tr id="probe-sw">
<td><strong>1. SW Injection</strong></td>
<td>Does <code><style id="sw-proxy-injected"></code> exist? (injected by SW during fetch)</td>
<td>—</td>
</tr>
<tr id="probe-sw-css">
<td><strong>2. SW CSS Effect</strong></td>
<td>Is <code>--sw-proxy-active</code> CSS variable set to <code>1</code>?</td>
<td>—</td>
</tr>
<tr id="probe-ads">
<td><strong>3. Ad Visibility</strong></td>
<td>Are <code>.ad-placeholder</code> elements hidden? (SW injects hiding CSS)</td>
<td>—</td>
</tr>
<tr id="probe-nav">
<td>4. Navigation Type</td>
<td><code>navigation.type</code> + <code>unloadEventStart</code></td>
<td>—</td>
</tr>
<tr id="probe-transfer">
<td>5. Transfer Size</td>
<td><code>transferSize</code> — 0 means served from cache</td>
<td>—</td>
</tr>
<tr id="probe-bfcache">
<td>6. bfcache</td>
<td><code>pageshow.persisted</code></td>
<td>waiting...</td>
</tr>
</table>
<!-- Ad placeholders — visible by default.
The SW injects CSS that hides these: .ad-placeholder { display: none !important; }
If the SW is bypassed on session restore, these flash back into visibility. -->
<div class="ad-placeholder" id="ad-top">
🚨 SIMULATED AD — This red box is hidden by the Service Worker's
injected <style>. If you see this, the SW was bypassed!
</div>
<div class="ad-placeholder" id="ad-bottom">
🚨 SIMULATED AD #2 — Another placeholder hidden by SW injection.
</div>
<div class="section">
<h2>How to Test</h2>
<ol>
<li>Open this page over <strong>HTTPS</strong> (Service Workers require HTTPS or localhost)</li>
<li>Wait for the banner to show <strong>"SW active"</strong> (green) — the red ad boxes should disappear</li>
<li>
<strong>Test A — Ctrl+Shift+T:</strong><br>
Close tab (<kbd>Ctrl+W</kbd>) → Restore (<kbd>Ctrl+Shift+T</kbd>)
</li>
<li>
<strong>Test B — Full browser restart</strong> (more reliable):<br>
Quit the browser entirely → Reopen → Session restore activates the tab
</li>
<li>Check results:
<ul>
<li><span style="color:#dc3545;font-weight:600">Red ad boxes visible</span> = SW was bypassed → the bug</li>
<li><span style="color:#28a745;font-weight:600">Ads hidden, banner green</span> = SW was consulted → correct behavior</li>
</ul>
</li>
<li>Repeat in <strong>Chrome</strong> to compare</li>
</ol>
<p class="note"><strong>Note:</strong> On first visit, the SW registers but may not intercept until the next navigation. Reload once after seeing "SW registered" to activate interception.</p>
</div>
<div class="section">
<h2>What This Page Does</h2>
<p>A <strong>Service Worker</strong> (<code>sw.js</code>) intercepts every navigation request and modifies the HTML response — exactly like a network proxy (AdGuard) does during HTTP transit:</p>
<ul>
<li>Injects <code><style id="sw-proxy-injected"></code> into <code><head></code></li>
<li>The injected CSS hides <code>.ad-placeholder</code> elements and sets a CSS variable</li>
<li>On session restore, if the browser serves from cache without going through the SW, the injected style is <strong>missing</strong></li>
</ul>
<p>This directly simulates the real-world scenario where <strong>AdGuard</strong> injects CSS into <code>423down.com</code> during HTTP transit, and Firefox's session restore loses those injections.</p>
<p class="note">Related: <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=706970">bug 706970</a>, <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1395510">bug 1395510</a>, <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1440408">bug 1440408</a></p>
</div>
<h2>Event Log</h2>
<div id="log" class="log"></div>
<script>
(function () {
"use strict";
var now = new Date().toISOString();
var logEl = document.getElementById("log");
function log(msg, isError) {
var div = document.createElement("div");
div.className = "log-entry" + (isError ? " error" : "");
div.textContent = "[" + now + "] " + msg;
logEl.appendChild(div);
}
function setProbe(id, result, cls) {
var row = document.getElementById(id);
if (row) row.lastElementChild.textContent = result;
if (row && cls) row.lastElementChild.className = cls;
}
function setBanner(text, cls) {
var b = document.getElementById("result-banner");
b.className = "result-banner " + cls;
b.textContent = text;
}
// ==============================================================
// PROBE 1: SW-INJECTED <style> PRESENCE
// The SW injects <style id="sw-proxy-injected"> into <head>.
// If it's missing, the SW was bypassed (page served from cache).
// ==============================================================
var swStyleEl = document.getElementById("sw-proxy-injected");
var swInjectionPresent = !!swStyleEl;
// ==============================================================
// PROBE 2: SW CSS VARIABLE
// The SW-injected style sets :root { --sw-proxy-active: 1; }
// ==============================================================
var swCssVar = getComputedStyle(document.documentElement)
.getPropertyValue("--sw-proxy-active").trim();
var swCssActive = swCssVar === "1";
// ==============================================================
// PROBE 3: AD VISIBILITY
// The SW-injected style includes .ad-placeholder { display: none !important; }
// Check if ads are actually hidden.
// ==============================================================
var adEl = document.getElementById("ad-top");
var adHidden = adEl && getComputedStyle(adEl).display === "none";
// ==============================================================
// PROBE 4: NAVIGATION TYPE + SESSION RESTORE DETECTION
// back_forward + unloadEventStart===0 means session restore
// after browser restart (no previous document in this tab).
// ==============================================================
var navType = "unknown";
var transferBytes = -1;
var unloadStart = -1;
var isSessionRestore = false;
try {
var navEntry = performance.getEntriesByType("navigation")[0];
navType = navEntry ? navEntry.type : "unavailable";
transferBytes = navEntry ? navEntry.transferSize : -1;
unloadStart = navEntry ? navEntry.unloadEventStart : -1;
isSessionRestore = (navType === "back_forward" && unloadStart === 0);
} catch (e) { /* ignore */ }
// ==============================================================
// SET PROBE RESULTS
// ==============================================================
// Probe 1
if (swInjectionPresent) {
setProbe("probe-sw", "\u2705 Present — SW intercepted the request", "pass");
log("PROBE 1 OK: <style id='sw-proxy-injected'> found in DOM");
} else {
setProbe("probe-sw", "\u274c MISSING — SW was bypassed!", "fail");
log("PROBE 1 FAIL: <style id='sw-proxy-injected'> NOT found — SW did not intercept", true);
}
// Probe 2
if (swCssActive) {
setProbe("probe-sw-css", "\u2705 --sw-proxy-active = 1", "pass");
log("PROBE 2 OK: CSS variable is set");
} else {
setProbe("probe-sw-css", "\u274c --sw-proxy-active not set", "fail");
log("PROBE 2 FAIL: CSS variable not set — SW styles missing", true);
}
// Probe 3
if (adHidden) {
setProbe("probe-ads", "\u2705 Ads hidden by SW-injected CSS", "pass");
log("PROBE 3 OK: ad-placeholder is hidden");
} else {
setProbe("probe-ads", "\u274c Ads VISIBLE — SW CSS is missing!", "fail");
log("PROBE 3 FAIL: ad-placeholder is visible — SW hiding CSS missing", true);
}
// Probe 4
if (isSessionRestore) {
setProbe("probe-nav", navType + " + unloadEventStart=0 \u2192 SESSION RESTORE", "warn");
log("PROBE 4: Session restore detected (back_forward + no previous document)");
} else if (navType === "back_forward") {
setProbe("probe-nav", navType + " (Ctrl+Shift+T or back/forward)", "warn");
log("PROBE 4: back_forward navigation (not a cold session restore)");
} else {
setProbe("probe-nav", navType + " \u2705", "pass");
log("PROBE 4: " + navType + " navigation");
}
// Probe 5
if (transferBytes === 0) {
setProbe("probe-transfer", "0 bytes (served from cache)", "warn");
log("PROBE 5: transferSize=0 — served from HTTP cache");
} else if (transferBytes > 0) {
setProbe("probe-transfer", transferBytes + " bytes \u2705 (network fetch)", "pass");
log("PROBE 5: transferSize=" + transferBytes);
} else {
setProbe("probe-transfer", "unavailable", "warn");
}
// ==============================================================
// OVERALL RESULT BANNER
// ==============================================================
var swBypassed = !swInjectionPresent;
var swNotYetRegistered = !swInjectionPresent && navType === "navigate" && !isSessionRestore;
if (swBypassed && isSessionRestore) {
// THE BUG: Session restore bypassed the SW!
setBanner(
"\u26a0 BUG: Service Worker was BYPASSED on session restore! "
+ "The SW-injected <style> is missing. This is the Firefox bug — "
+ "the page was served from HTTP cache without going through "
+ "the Service Worker (network proxy).",
"result-fail"
);
log("RESULT: \u26a0 BUG CONFIRMED — SW bypassed on session restore!", true);
} else if (swBypassed && navType === "back_forward") {
setBanner(
"\u26a0 Service Worker was BYPASSED on back_forward navigation! "
+ "SW-injected styles are missing.",
"result-fail"
);
log("RESULT: SW bypassed on back_forward navigation", true);
} else if (swInjectionPresent) {
setBanner(
"\u2705 Service Worker active — all injections present. "
+ "The page was loaded through the SW (simulated proxy).",
"result-pass"
);
log("RESULT: SW active, all injections present — correct behavior");
} else {
setBanner(
"\u2139\ufe0f Service Worker not yet intercepting. "
+ "This is normal on first visit — reload the page once to activate.",
"result-warn"
);
log("RESULT: SW not intercepting yet. Reload to activate.");
}
// ==============================================================
// PROBE 6: bfcache
// ==============================================================
window.addEventListener("pageshow", function (e) {
if (e.persisted) {
setProbe("probe-bfcache", "\u2139\ufe0f bfcache (page thawed from memory)", "warn");
log("PROBE 6: bfcache restore — page was frozen/thawed");
setBanner(
"\u2139\ufe0f bfcache restore — page was frozen/thawed from memory. "
+ "This is normal. Close the ENTIRE browser and reopen to test session restore.",
"result-warn"
);
} else {
setProbe("probe-bfcache", "\u2705 No (regular load)", "pass");
log("PROBE 6: Not bfcache (pageshow.persisted=false)");
}
});
// ==============================================================
// SERVICE WORKER REGISTRATION
// ==============================================================
if ("serviceWorker" in navigator && location.protocol !== "file:") {
navigator.serviceWorker.register("sw.js", { scope: "./" })
.then(function (reg) {
log("SW registered (scope: " + reg.scope + ")");
if (reg.installing) {
log("SW is installing... reload page after installation to activate interception");
} else if (reg.active) {
log("SW is already active");
}
})
.catch(function (err) {
log("SW registration FAILED: " + err.message, true);
setBanner(
"\u26a0 Service Worker registration failed: " + err.message
+ ". Make sure you are loading over HTTPS.",
"result-fail"
);
});
} else if (location.protocol === "file:") {
log("Service Workers not available on file:// protocol", true);
setBanner(
"\u26a0 file:// protocol — Service Workers require HTTPS or localhost. "
+ "Use: python3 -m http.server 8090",
"result-fail"
);
} else {
log("Service Workers not supported in this browser", true);
}
})();
</script>
</body>
</html>