A page older than its script is loaded again, not run with it (#449)
Safari on a Mac threw at load on 2026-11-10: the script wired #sleep, which the page it ran in did not have. #sleep and its script came in together on 11-08, so the page was older than the script. The service worker answers a navigation it cannot fetch with the page it kept last, here one from before 11-08, and that page asked for its script by the old hash; by then the network answered, and Caddy, which ignores ?v=, sent today's app.js. The worker even kept it under the old name. Every ?v= is the first 12 hex digits of the file's SHA-256, so the worker now checks what it fetched against the name. On a mismatch it does not keep or give the file, and loads the page again from the network, at most once in 30 seconds so a page still unreachable is not reloaded round and round. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -92,6 +92,23 @@ test('a place in an episode saved offline waits, and is saved once back', async
|
||||
expect((await call(page, '/api/entries/offline-blog/offline-2')).position).toBe(321);
|
||||
});
|
||||
|
||||
test('a page older than the script the server sends is loaded again, not run with it', async ({ page }) => {
|
||||
// #449: a page kept from before a deploy asks for its script by the old hash, and the server
|
||||
// sends the new one whatever the name says.
|
||||
await controlled(page);
|
||||
const mine = await page.evaluate(() => document.querySelector('script[src^="/app.js"]')!.getAttribute('src')!);
|
||||
const kept = () => page.evaluate(async () => (await (await caches.open('ipx-page')).keys()).map(k => new URL(k.url).pathname + new URL(k.url).search));
|
||||
await expect.poll(kept).toContain(mine);
|
||||
await page.evaluate(() => { (window as unknown as { stale: boolean }).stale = true; });
|
||||
const reloaded = page.waitForEvent('load');
|
||||
expect(await page.evaluate(() => fetch('/app.js?v=000000000000').then(() => 'given', () => 'refused'))).toBe('refused');
|
||||
await reloaded;
|
||||
expect(await page.evaluate(() => 'stale' in window), 'the page was loaded again').toBe(false);
|
||||
// What is kept is still the script under its own name, not the new one under the old.
|
||||
expect(await kept()).toContain(mine);
|
||||
expect(await kept()).not.toContain('/app.js?v=000000000000');
|
||||
});
|
||||
|
||||
test('online, a picture in an item\'s text still comes from its publisher, through the worker', async ({ page }) => {
|
||||
await controlled(page);
|
||||
await page.locator('#feedlist .feed', { hasText: 'Offline Blog' }).click({ timeout: 20_000 });
|
||||
|
||||
Reference in New Issue
Block a user