From 12f008b85d760af5596f5e7d3afbe5779698663f Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Mon, 31 Aug 2026 11:42:52 -0400 Subject: [PATCH] feat(browser)!: Rename `bfcacheIntegration` to `bfcacheMetricsIntegration` bfcache is going to show up in more than one place (navigation spans, Web Vitals attribution), so the integration is named after what it actually does rather than the feature area. --- CHANGELOG.md | 6 +- .../browser-bfcache/README.md | 4 +- .../browser-bfcache/src/main.ts | 2 +- packages/browser/src/index.ts | 2 +- .../{bfcache.ts => bfcacheMetrics.ts} | 92 ++++++++++--------- ...bfcache.test.ts => bfcacheMetrics.test.ts} | 4 +- 6 files changed, 56 insertions(+), 54 deletions(-) rename packages/browser/src/integrations/{bfcache.ts => bfcacheMetrics.ts} (67%) rename packages/browser/test/integrations/{bfcache.test.ts => bfcacheMetrics.test.ts} (98%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5debc630aeb..e74f4483eaf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,14 +13,14 @@ Work in this release was contributed by @psh4607, @thijsw, @trinitiwowka, @nehap - `DenoMongoose` => `Mongoose` - `DenoMysql` => `Mysql` - `DenoPostgres` => `Postgres` -- **feat(browser): Add `bfcacheIntegration` to track back/forward cache health** +- **feat(browser): Add `bfcacheMetricsIntegration` to track back/forward cache health** - The new opt-in `bfcacheIntegration` emits metrics about browser back/forward cache (bfcache) navigations, so you can + The new opt-in `bfcacheMetricsIntegration` emits metrics about browser back/forward cache (bfcache) navigations, so you can measure how often back-button navigation is instant and what's blocking it. ```js Sentry.init({ - integrations: [Sentry.bfcacheIntegration()], + integrations: [Sentry.bfcacheMetricsIntegration()], }); ``` diff --git a/dev-packages/e2e-tests/test-applications/browser-bfcache/README.md b/dev-packages/e2e-tests/test-applications/browser-bfcache/README.md index 95505052cc6a..988fb2527945 100644 --- a/dev-packages/e2e-tests/test-applications/browser-bfcache/README.md +++ b/dev-packages/e2e-tests/test-applications/browser-bfcache/README.md @@ -1,6 +1,6 @@ # browser-bfcache -Exercises `bfcacheIntegration` against a **real** browser back/forward cache, covering hits, +Exercises `bfcacheMetricsIntegration` 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=` (see `src/main.ts`). @@ -22,7 +22,7 @@ permissive than web.dev's list suggests, so the individual `?botch=` cases and t 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 +at `packages/browser/test/integrations/bfcacheMetrics.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. diff --git a/dev-packages/e2e-tests/test-applications/browser-bfcache/src/main.ts b/dev-packages/e2e-tests/test-applications/browser-bfcache/src/main.ts index 6cac805d7d72..e53f75c4a45a 100644 --- a/dev-packages/e2e-tests/test-applications/browser-bfcache/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/browser-bfcache/src/main.ts @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/browser'; Sentry.init({ dsn: process.env.E2E_TEST_DSN, - integrations: [Sentry.bfcacheIntegration()], + integrations: [Sentry.bfcacheMetricsIntegration()], release: 'e2e-test', environment: 'qa', tunnel: 'http://localhost:3031', diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index b7a1f410bfea..86a58bbc9be6 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -39,7 +39,7 @@ export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; export { fetchStreamPerformanceIntegration } from './integrations/fetchStreamPerformance'; export { webVitalsIntegration } from './integrations/webVitals'; export { userTimingIntegration } from './integrations/usertiming'; -export { bfcacheIntegration } from './integrations/bfcache'; +export { bfcacheMetricsIntegration } from './integrations/bfcacheMetrics'; export { interactionsIntegration } from './integrations/interactions'; export type { RequestInstrumentationOptions } from './tracing/request'; diff --git a/packages/browser/src/integrations/bfcache.ts b/packages/browser/src/integrations/bfcacheMetrics.ts similarity index 67% rename from packages/browser/src/integrations/bfcache.ts rename to packages/browser/src/integrations/bfcacheMetrics.ts index 81616cbaef9c..82b1deac4852 100644 --- a/packages/browser/src/integrations/bfcache.ts +++ b/packages/browser/src/integrations/bfcacheMetrics.ts @@ -11,13 +11,13 @@ import { debug, defineIntegration, getCurrentScope, metrics } from '@sentry/core import { DEBUG_BUILD } from '../debug-build'; import { WINDOW } from '../helpers'; -const INTEGRATION_NAME = 'Bfcache'; +const INTEGRATION_NAME = 'BfcacheMetrics'; type BFCacheOutcome = 'hit' | 'miss'; type BFCacheFrame = 'top' | 'child'; -interface BFCacheIntegrationOptions { +interface BFCacheMetricsIntegrationOptions { /** * Maximum number of not-restored reasons to emit per miss. * @@ -48,56 +48,58 @@ interface CollectedReason { /** * Captures bfcache hit/miss counters and Chromium notRestoredReasons when available. */ -export const bfcacheIntegration = defineIntegration((options: Partial = {}) => { - const maxReasons = _resolveMaxReasons(options.maxReasons); - - return { - name: INTEGRATION_NAME, - - setupOnce() { - if (!WINDOW.addEventListener || !WINDOW.performance?.getEntriesByType) { - DEBUG_BUILD && debug.log(`[${INTEGRATION_NAME}] Browser APIs unavailable, skipping instrumentation.`); - return; - } - - function onPageShow(event: PageTransitionEvent) { - const routeName = _getSegmentName(); - if (event.persisted) { - _captureBFCacheNavigation('hit', 0, routeName); - return; - } +export const bfcacheMetricsIntegration = defineIntegration( + (options: Partial = {}) => { + const maxReasons = _resolveMaxReasons(options.maxReasons); - const navigationEntry = WINDOW.performance.getEntriesByType('navigation')[0] as - | NavigationTimingWithNotRestoredReasons - | undefined; + return { + name: INTEGRATION_NAME, - if (navigationEntry?.type !== 'back_forward') { + setupOnce() { + if (!WINDOW.addEventListener || !WINDOW.performance?.getEntriesByType) { + DEBUG_BUILD && debug.log(`[${INTEGRATION_NAME}] Browser APIs unavailable, skipping instrumentation.`); return; } - const reasons = _collectNotRestoredReasons(navigationEntry.notRestoredReasons, maxReasons); - _captureBFCacheNavigation('miss', reasons.length, routeName); - - // Measures how expensive the fallback reload was when a back/forward navigation missed bfcache. - if (typeof navigationEntry.duration === 'number' && navigationEntry.duration > 0) { - metrics.distribution('browser.bfcache.reload.duration', navigationEntry.duration, { - unit: 'millisecond', - attributes: _withOriginAttr({ - [SENTRY_SEGMENT_NAME]: routeName, - }), - }); + function onPageShow(event: PageTransitionEvent) { + const routeName = _getSegmentName(); + if (event.persisted) { + _captureBFCacheNavigation('hit', 0, routeName); + return; + } + + const navigationEntry = WINDOW.performance.getEntriesByType('navigation')[0] as + | NavigationTimingWithNotRestoredReasons + | undefined; + + if (navigationEntry?.type !== 'back_forward') { + return; + } + + const reasons = _collectNotRestoredReasons(navigationEntry.notRestoredReasons, maxReasons); + _captureBFCacheNavigation('miss', reasons.length, routeName); + + // Measures how expensive the fallback reload was when a back/forward navigation missed bfcache. + if (typeof navigationEntry.duration === 'number' && navigationEntry.duration > 0) { + metrics.distribution('browser.bfcache.reload.duration', navigationEntry.duration, { + unit: 'millisecond', + attributes: _withOriginAttr({ + [SENTRY_SEGMENT_NAME]: routeName, + }), + }); + } + + reasons.forEach(r => _captureBFCacheReason(r, routeName)); } - reasons.forEach(r => _captureBFCacheReason(r, routeName)); - } - - // Listener should stay active because the event can trigger for an initial show before the bfcache entry coming into the second one. - // This can be platform-dependent so we need to skip as many events till we get to the one containing the entry. - // So we can't have { once } or a cleanup logic here, which is fine because `setupOnce` registers it a single time regardless of how many clients are created. - WINDOW.addEventListener('pageshow', onPageShow, true); - }, - }; -}) satisfies IntegrationFn; + // Listener should stay active because the event can trigger for an initial show before the bfcache entry coming into the second one. + // This can be platform-dependent so we need to skip as many events till we get to the one containing the entry. + // So we can't have { once } or a cleanup logic here, which is fine because `setupOnce` registers it a single time regardless of how many clients are created. + WINDOW.addEventListener('pageshow', onPageShow, true); + }, + }; + }, +) satisfies IntegrationFn; /** * Captures a bf navigation as a metric and records the outcome and reason count. diff --git a/packages/browser/test/integrations/bfcache.test.ts b/packages/browser/test/integrations/bfcacheMetrics.test.ts similarity index 98% rename from packages/browser/test/integrations/bfcache.test.ts rename to packages/browser/test/integrations/bfcacheMetrics.test.ts index 8ac43575f37d..6f04081f948e 100644 --- a/packages/browser/test/integrations/bfcache.test.ts +++ b/packages/browser/test/integrations/bfcacheMetrics.test.ts @@ -1,8 +1,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { debug } from '@sentry/core/browser'; -import { _collectNotRestoredReasons, _resolveMaxReasons } from '../../src/integrations/bfcache'; +import { _collectNotRestoredReasons, _resolveMaxReasons } from '../../src/integrations/bfcacheMetrics'; -describe('bfcacheIntegration', () => { +describe('bfcacheMetricsIntegration', () => { describe('_resolveMaxReasons', () => { afterEach(() => { vi.restoreAllMocks();