Skip to content

Commit fcb9ef1

Browse files
committed
test(browser): Cover the IndexedDB versionchange bfcache blocker
A plain open IndexedDB connection (and even an in-flight transaction) no longer blocks bfcache in current Chrome, contrary to web.dev's list. The condition that still blocks is a connection holding up a version upgrade, which yields an `idbversionchangeevent` reason. Add a deterministic e2e case for it and document how real Chrome diverges from the article.
1 parent 09c9be9 commit fcb9ef1

3 files changed

Lines changed: 64 additions & 3 deletions

File tree

dev-packages/e2e-tests/test-applications/browser-bfcache/README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,17 @@ Why this needs its own app rather than living in `browser-integration-tests`:
1717
- **Renderer-initiated navigation.** Restores are triggered with `history.back()` from the page;
1818
Playwright's CDP `goBack` bypasses bfcache.
1919

20-
Blocker cases are version-sensitive (the pinned Chromium comes from the Playwright version):
21-
`unload` is a stable blocker; `Cache-Control: no-store` no longer blocks (CCNS bfcache landed); an
22-
open WebSocket blocks only before Chrome 149, so that assertion is gated on the browser version.
20+
Blocker cases are version-sensitive (the pinned Chromium comes from the Playwright version) and real
21+
Chrome is more permissive than web.dev's blocker list suggests. Verified against the pinned Chrome:
22+
23+
- `unload` listener: blocks (stable across versions). Reason `unload-listener` (plus a `masked` one).
24+
- Open WebSocket: blocks only before Chrome 149, so that assertion is gated on the browser version.
25+
- IndexedDB: a plain open connection and even an in-flight transaction do NOT block; only a
26+
connection holding up a version upgrade does (reason `idbversionchangeevent`).
27+
- `Cache-Control: no-store` and `beforeunload` no longer block.
28+
29+
Reason extraction/classification (top/child/masked frames, nesting, caps) is exhaustively covered by
30+
the unit test at `packages/browser/test/integrations/bfcache.test.ts`; this app verifies the real
31+
end-to-end hit/miss + reason path for the deterministic blockers above.
2332

2433
If other tests later fit these same constraints, this app can be renamed to something broader.

dev-packages/e2e-tests/test-applications/browser-bfcache/src/main.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,25 @@ if (botch === 'websocket') {
4141
});
4242
w.__ws = ws;
4343
}
44+
45+
if (botch === 'indexeddb') {
46+
// A plain open IndexedDB connection (or even an in-flight transaction) does NOT block bfcache in
47+
// current Chrome. What still blocks is a connection holding up a version upgrade: open v1 without
48+
// closing it on `versionchange`, then request v2 - the upgrade is blocked and the page holds it up.
49+
// A fresh db name per load avoids cross-run persistence.
50+
const dbName = `bf_${Math.random().toString(36).slice(2)}`;
51+
const w = window as unknown as { __idbBlocked?: boolean; __db?: IDBDatabase };
52+
w.__idbBlocked = false;
53+
54+
const open1 = indexedDB.open(dbName, 1);
55+
open1.addEventListener('upgradeneeded', event => {
56+
(event.target as IDBOpenDBRequest).result.createObjectStore('s');
57+
});
58+
open1.addEventListener('success', event => {
59+
w.__db = (event.target as IDBOpenDBRequest).result; // intentionally no `versionchange` handler
60+
const open2 = indexedDB.open(dbName, 2);
61+
open2.addEventListener('blocked', () => {
62+
w.__idbBlocked = true;
63+
});
64+
});
65+
}

dev-packages/e2e-tests/test-applications/browser-bfcache/tests/bfcache.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,36 @@ test('reports a miss for an open WebSocket on Chrome < 149 (a hit from 149 on)',
127127
}
128128
});
129129

130+
test('reports a miss with an idbversionchangeevent reason when a connection blocks an upgrade', async ({ page }) => {
131+
const missPromise = waitForMetric(PROXY_SERVER_NAME, metric => isNavigation(metric, 'miss'));
132+
const reasonPromise = waitForMetric(
133+
PROXY_SERVER_NAME,
134+
metric =>
135+
metric.name === 'browser.bfcache.not_restored' &&
136+
attr(metric, 'browser.bfcache.reason') === 'idbversionchangeevent',
137+
);
138+
139+
await page.goto('/?botch=indexeddb');
140+
await page.waitForFunction(() => document.title === 'BFCache E2E - Page 1');
141+
// Only proceed once the version upgrade is actually blocked by the open connection.
142+
await page.waitForFunction(() => (window as unknown as { __idbBlocked?: boolean }).__idbBlocked === true, {
143+
timeout: 5000,
144+
});
145+
146+
await page.click('#to-page-2');
147+
await page.waitForFunction(() => document.title === 'BFCache E2E - Page 2');
148+
await page.waitForTimeout(500);
149+
150+
await page.evaluate(() => history.back());
151+
152+
const miss = await missPromise;
153+
expect(miss.value).toBe(1);
154+
expect(attr(miss, 'browser.bfcache.navigation_type')).toBe('back-forward');
155+
156+
const reason = await reasonPromise;
157+
expect(attr(reason, 'browser.bfcache.frame')).toBe('top');
158+
});
159+
130160
test('does not treat an ordinary forward navigation as a restore', async ({ page }) => {
131161
await page.goto('/');
132162
await page.waitForFunction(() => document.title === 'BFCache E2E - Page 1');

0 commit comments

Comments
 (0)