-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(browser): Add bfcacheIntegration
#22397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
logaretm
wants to merge
16
commits into
develop
Choose a base branch
from
awad/bfcache-integration
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
89c5bce
feat(browser): Add bfcacheMetricsIntegration
logaretm b7bd81b
test(browser): Add real bfcache e2e app, drop simulated suite
logaretm 27af84b
test(browser): Assert masked-frame notRestoredReason in bfcache e2e
logaretm 5840522
test(browser): Cover the IndexedDB versionchange bfcache blocker
logaretm 324d58c
test(browser): Cover the no-store + cookie-change bfcache miss
logaretm 5bee336
refactor(browser): Drop redundant bfcache navigation_type attribute
logaretm 2e0bd8f
test(browser): Cover bfcache iframe eligibility and child-frame reasons
logaretm c69076b
refactor(browser): Make bfcache frame purely positional
logaretm d9946e5
refactor(browser): Drop conditional spreads for bfcache metric attrib…
logaretm e438816
fix(browser): Report all bfcache reasons by default
logaretm ae20164
refactor(browser): Extract bfcache reason capture and hoist transacti…
logaretm 5b33a43
refactor(browser): Rename bfcacheMetricsIntegration to bfcacheIntegra…
logaretm 0779861
fix(browser): Register bfcache pageshow listener via setupOnce
logaretm e8e67c6
test(browser): Assert bfcache segment name across freeze, fallback, a…
logaretm 74d7f8b
refactor(browser): Rename bfcache transaction name to routeName
logaretm f4180f9
feat: use attributes from updated conventions
logaretm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
dev-packages/e2e-tests/test-applications/browser-bfcache/.gitignore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| /node_modules | ||
| /dist | ||
| .DS_Store | ||
|
|
||
| /test-results/ | ||
| /playwright-report/ | ||
| /playwright/.cache/ | ||
|
|
||
| *.log | ||
| pnpm-lock.yaml |
28 changes: 28 additions & 0 deletions
28
dev-packages/e2e-tests/test-applications/browser-bfcache/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # browser-bfcache | ||
|
|
||
| Exercises `bfcacheIntegration` against a **real** browser back/forward cache, covering hits, | ||
| misses, and the real `notRestoredReasons` the browser reports (Chromium-only, and this app is | ||
| Chromium). Deliberately bfcache-ineligible pages are produced via `?botch=<case>` (see `src/main.ts`). | ||
|
|
||
| Why this needs its own app rather than living in `browser-integration-tests`: | ||
|
|
||
| - **Real documents, real server.** bfcache only kicks in on a genuine cross-document history | ||
| traversal served over real HTTP. The shared browser suite serves pages via CDP `page.route` | ||
| interception on a fake host, which bfcache treats as ineligible. Here two static pages | ||
| (`index.html`, `page-2.html`) are built with Vite and served with `vite preview`. It must be the | ||
| production build, not `vite dev` (the dev server's HMR websocket is itself a bfcache blocker). | ||
| - **A specific Chromium launch recipe** (see `playwright.config.mjs`): the full Chrome-for-Testing | ||
| binary (`channel: 'chromium'`, not the default `chromium_headless_shell`, which has no bfcache), | ||
| with Playwright's `--disable-back-forward-cache` flag stripped and `BackForwardCache` enabled. | ||
| - **Renderer-initiated navigation.** Restores are triggered with `history.back()` from the page; | ||
| Playwright's CDP `goBack` bypasses bfcache. | ||
|
|
||
| Which conditions actually block bfcache (and the exact reason strings) is version-specific and more | ||
| permissive than web.dev's list suggests, so the individual `?botch=` cases and their assertions are | ||
| the source of truth, not prose here. Some are gated on the browser version where behavior changed. | ||
|
|
||
| Reason extraction/classification (top/child/masked frames, nesting, caps) is covered by the unit test | ||
| at `packages/browser/test/integrations/bfcache.test.ts`; this app verifies the real end-to-end | ||
| hit/miss + reason path. | ||
|
|
||
| If other tests later fit these same constraints, this app can be renamed to something broader. |
18 changes: 18 additions & 0 deletions
18
dev-packages/e2e-tests/test-applications/browser-bfcache/iframe.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <title>BFCache E2E - iframe</title> | ||
| <script> | ||
| // A same-origin child frame. With ?blocker=unload it registers an unload listener, which makes | ||
| // the *whole* embedding page bfcache-ineligible - the not-restored reason then comes from this | ||
| // child frame (frame: 'child'). | ||
| if (new URLSearchParams(window.location.search).get('blocker') === 'unload') { | ||
| window.addEventListener('unload', () => {}); | ||
| } | ||
| </script> | ||
| </head> | ||
| <body> | ||
| iframe | ||
| </body> | ||
| </html> |
13 changes: 13 additions & 0 deletions
13
dev-packages/e2e-tests/test-applications/browser-bfcache/index.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>BFCache E2E - Page 1</title> | ||
| <script type="module" src="/src/main.ts"></script> | ||
| </head> | ||
| <body> | ||
| <h1>BFCache E2E - Page 1</h1> | ||
| <a id="to-page-2" href="/page-2.html">Go to page 2</a> | ||
| </body> | ||
| </html> |
29 changes: 29 additions & 0 deletions
29
dev-packages/e2e-tests/test-applications/browser-bfcache/package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| { | ||
| "name": "browser-bfcache-test-app", | ||
| "private": true, | ||
| "version": "0.1.0", | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "vite build", | ||
| "preview": "vite preview --port 3030", | ||
| "test": "playwright test", | ||
| "clean": "npx rimraf node_modules pnpm-lock.yaml dist", | ||
| "test:build": "pnpm install && pnpm build", | ||
| "test:assert": "pnpm test" | ||
| }, | ||
| "dependencies": { | ||
| "@sentry/browser": "file:../../packed/sentry-browser-packed.tgz" | ||
| }, | ||
| "devDependencies": { | ||
| "@playwright/test": "~1.56.0", | ||
| "@sentry-internal/test-utils": "link:../../../test-utils", | ||
| "typescript": "~5.8.3", | ||
| "vite": "^7.3.2" | ||
| }, | ||
| "volta": { | ||
| "node": "20.19.2", | ||
| "yarn": "1.22.22", | ||
| "pnpm": "9.15.9" | ||
| } | ||
| } |
13 changes: 13 additions & 0 deletions
13
dev-packages/e2e-tests/test-applications/browser-bfcache/page-2.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>BFCache E2E - Page 2</title> | ||
| <script type="module" src="/src/main.ts"></script> | ||
| </head> | ||
| <body> | ||
| <h1>BFCache E2E - Page 2</h1> | ||
| <a id="to-page-1" href="/index.html">Back to page 1</a> | ||
| </body> | ||
| </html> |
30 changes: 30 additions & 0 deletions
30
dev-packages/e2e-tests/test-applications/browser-bfcache/playwright.config.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { getPlaywrightConfig } from '@sentry-internal/test-utils'; | ||
|
|
||
| const config = getPlaywrightConfig({ | ||
| startCommand: `pnpm preview`, | ||
| }); | ||
|
|
||
| // Real bfcache only restores under the full Chrome-for-Testing binary (`channel: 'chromium'`) with | ||
| // Playwright's default `--disable-back-forward-cache` flag stripped and the feature enabled. The | ||
| // default headless binary (`chromium_headless_shell`) has no bfcache, so without this the restore | ||
| // never happens and the test would be meaningless. | ||
| for (const project of config.projects ?? []) { | ||
| project.use = { | ||
| ...project.use, | ||
| channel: 'chromium', | ||
| launchOptions: { | ||
| ignoreDefaultArgs: ['--disable-back-forward-cache'], | ||
| args: ['--enable-features=BackForwardCache'], | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| // Extra server that holds a WebSocket open, used by the `?botch=websocket` blocker case. | ||
| config.webServer.push({ | ||
| command: 'node start-ws-server.mjs', | ||
| port: 3034, | ||
| stdout: 'pipe', | ||
| stderr: 'pipe', | ||
| }); | ||
|
|
||
| export default config; |
85 changes: 85 additions & 0 deletions
85
dev-packages/e2e-tests/test-applications/browser-bfcache/src/main.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import * as Sentry from '@sentry/browser'; | ||
|
|
||
| Sentry.init({ | ||
| dsn: process.env.E2E_TEST_DSN, | ||
| integrations: [Sentry.bfcacheIntegration()], | ||
| release: 'e2e-test', | ||
| environment: 'qa', | ||
| tunnel: 'http://localhost:3031', | ||
| }); | ||
|
|
||
| (window as unknown as { Sentry: typeof Sentry }).Sentry = Sentry; | ||
|
|
||
| // Test-only marker: lets the test distinguish a genuine bfcache restore (environment working) from a | ||
| // fresh reload (environment not restoring), independently of whether our integration emitted a metric. | ||
| window.addEventListener( | ||
| 'pageshow', | ||
| event => { | ||
| if ((event as PageTransitionEvent).persisted) { | ||
| (window as unknown as { __bfcacheRestored?: boolean }).__bfcacheRestored = true; | ||
| } | ||
| }, | ||
| true, | ||
| ); | ||
|
|
||
| // Deliberately make this page bfcache-ineligible when the test asks for it via `?botch=...`, so we can | ||
| // assert the real miss + notRestoredReasons the browser reports. Each botcher is a documented blocker. | ||
| const botch = new URLSearchParams(window.location.search).get('botch'); | ||
|
|
||
| if (botch === 'unload') { | ||
| // An `unload` listener is the canonical, version-stable bfcache blocker. | ||
| window.addEventListener('unload', () => {}); | ||
| } | ||
|
|
||
| if (botch === 'websocket') { | ||
| // An open WebSocket blocks bfcache in Chrome < 149; from 149 on it no longer does. | ||
| const ws = new WebSocket('ws://localhost:3034'); | ||
| const w = window as unknown as { __wsOpen?: boolean; __ws?: WebSocket }; | ||
| w.__wsOpen = false; | ||
| ws.addEventListener('open', () => { | ||
| w.__wsOpen = true; | ||
| }); | ||
| w.__ws = ws; | ||
| } | ||
|
|
||
| if (botch === 'nostore') { | ||
| // The document is served with `Cache-Control: no-store` (see vite.config.ts). A CCNS page is | ||
| // cached but evicted once a cookie changes, so mutate a cookie to force the miss. | ||
| document.cookie = 'bf=1; Path=/'; | ||
| (window as unknown as { __nostoreReady?: boolean }).__nostoreReady = true; | ||
| } | ||
|
|
||
| if (botch === 'indexeddb') { | ||
| // A plain open IndexedDB connection (or even an in-flight transaction) does NOT block bfcache in | ||
| // current Chrome. What still blocks is a connection holding up a version upgrade: open v1 without | ||
| // closing it on `versionchange`, then request v2 - the upgrade is blocked and the page holds it up. | ||
| // A fresh db name per load avoids cross-run persistence. | ||
| const dbName = `bf_${Math.random().toString(36).slice(2)}`; | ||
| const w = window as unknown as { __idbBlocked?: boolean; __db?: IDBDatabase }; | ||
| w.__idbBlocked = false; | ||
|
|
||
| const open1 = indexedDB.open(dbName, 1); | ||
| open1.addEventListener('upgradeneeded', event => { | ||
| (event.target as IDBOpenDBRequest).result.createObjectStore('s'); | ||
| }); | ||
| open1.addEventListener('success', event => { | ||
| w.__db = (event.target as IDBOpenDBRequest).result; // intentionally no `versionchange` handler | ||
| const open2 = indexedDB.open(dbName, 2); | ||
| open2.addEventListener('blocked', () => { | ||
| w.__idbBlocked = true; | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| if (botch === 'iframe-clean' || botch === 'iframe-unload') { | ||
| // Embed a same-origin child frame. A clean child keeps the top page eligible (hit); a child with an | ||
| // unload listener makes the whole top page ineligible, and the reason comes from the child frame. | ||
| const iframe = document.createElement('iframe'); | ||
| iframe.src = botch === 'iframe-unload' ? '/iframe.html?blocker=unload' : '/iframe.html'; | ||
| const w = window as unknown as { __iframeLoaded?: boolean }; | ||
| w.__iframeLoaded = false; | ||
| iframe.addEventListener('load', () => { | ||
| w.__iframeLoaded = true; | ||
| }); | ||
| document.body.appendChild(iframe); | ||
| } | ||
6 changes: 6 additions & 0 deletions
6
dev-packages/e2e-tests/test-applications/browser-bfcache/start-event-proxy.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { startEventProxyServer } from '@sentry-internal/test-utils'; | ||
|
|
||
| startEventProxyServer({ | ||
| port: 3031, | ||
| proxyServerName: 'browser-bfcache', | ||
| }); |
26 changes: 26 additions & 0 deletions
26
dev-packages/e2e-tests/test-applications/browser-bfcache/start-ws-server.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import crypto from 'crypto'; | ||
| import http from 'http'; | ||
|
|
||
| // Minimal WebSocket endpoint used only to hold an open connection open on the page, which makes the | ||
| // page bfcache-ineligible in Chrome < 149. It completes the handshake and then does nothing. | ||
| const server = http.createServer((_req, res) => { | ||
| res.writeHead(200, { 'Content-Type': 'text/plain' }); | ||
| res.end('ws server'); | ||
| }); | ||
|
|
||
| server.on('upgrade', (req, socket) => { | ||
| const key = req.headers['sec-websocket-key']; | ||
| if (!key) { | ||
| socket.destroy(); | ||
| return; | ||
| } | ||
| const accept = crypto.createHash('sha1').update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest('base64'); | ||
| socket.write( | ||
| `HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${accept}\r\n\r\n`, | ||
| ); | ||
| }); | ||
|
|
||
| server.listen(3034, () => { | ||
| // eslint-disable-next-line no-console | ||
| console.log('bfcache ws blocker server listening on 3034'); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.