From b4db9924767ebb9e1bd8618f74550dadb41bff70 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 18 Jul 2025 10:37:28 +0200 Subject: [PATCH 1/6] feat(browser)!: Remove FID web vital collection --- .../suites/replay/customEvents/test.ts | 3 +- .../suites/replay/multiple-pages/test.ts | 5 - .../metrics/web-vitals-fid/template.html | 9 -- .../tracing/metrics/web-vitals-fid/test.ts | 28 ---- .../utils/replayEventTemplates.ts | 13 -- .../tests/fixtures/ReplayRecordingData.ts | 19 --- packages/browser-utils/src/index.ts | 1 - .../src/metrics/browserMetrics.ts | 33 ----- packages/browser-utils/src/metrics/inp.ts | 1 - .../browser-utils/src/metrics/instrument.ts | 34 +---- .../src/metrics/web-vitals/README.md | 4 + .../src/metrics/web-vitals/getFID.ts | 76 ---------- .../metrics/web-vitals/lib/interactions.ts | 136 ------------------ .../src/metrics/web-vitals/lib/observe.ts | 1 - .../src/metrics/web-vitals/lib/onHidden.ts | 2 +- .../src/metrics/web-vitals/types.ts | 2 - .../src/metrics/web-vitals/types/base.ts | 8 +- .../src/metrics/web-vitals/types/fid.ts | 65 --------- .../src/metrics/web-vitals/types/polyfills.ts | 21 --- .../src/coreHandlers/performanceObserver.ts | 3 - .../replay-internal/src/types/replayFrame.ts | 2 +- .../src/util/createPerformanceEntries.ts | 9 -- .../unit/util/createPerformanceEntry.test.ts | 21 --- 23 files changed, 13 insertions(+), 483 deletions(-) delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/template.html delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts delete mode 100644 packages/browser-utils/src/metrics/web-vitals/getFID.ts delete mode 100644 packages/browser-utils/src/metrics/web-vitals/lib/interactions.ts delete mode 100644 packages/browser-utils/src/metrics/web-vitals/types/fid.ts delete mode 100644 packages/browser-utils/src/metrics/web-vitals/types/polyfills.ts diff --git a/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts b/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts index 336e09a331e1..4bb925507c49 100644 --- a/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts @@ -4,7 +4,6 @@ import { expectedClickBreadcrumb, expectedCLSPerformanceSpan, expectedFCPPerformanceSpan, - expectedFIDPerformanceSpan, expectedFPPerformanceSpan, expectedLCPPerformanceSpan, expectedMemoryPerformanceSpan, @@ -56,7 +55,7 @@ sentryTest( expectedNavigationPerformanceSpan, expectedLCPPerformanceSpan, expectedCLSPerformanceSpan, - expectedFIDPerformanceSpan, + expectedFPPerformanceSpan, expectedFPPerformanceSpan, expectedFCPPerformanceSpan, expectedMemoryPerformanceSpan, // two memory spans - once per flush diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts index 2c059bb226f4..14d6ce3783b1 100644 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts @@ -4,7 +4,6 @@ import { expectedClickBreadcrumb, expectedCLSPerformanceSpan, expectedFCPPerformanceSpan, - expectedFIDPerformanceSpan, expectedFPPerformanceSpan, expectedLCPPerformanceSpan, expectedMemoryPerformanceSpan, @@ -77,7 +76,6 @@ sentryTest( expectedNavigationPerformanceSpan, expectedLCPPerformanceSpan, expectedCLSPerformanceSpan, - expectedFIDPerformanceSpan, expectedFPPerformanceSpan, expectedFCPPerformanceSpan, expectedMemoryPerformanceSpan, // two memory spans - once per flush @@ -117,7 +115,6 @@ sentryTest( expectedReloadPerformanceSpan, expectedLCPPerformanceSpan, expectedCLSPerformanceSpan, - expectedFIDPerformanceSpan, expectedFPPerformanceSpan, expectedFCPPerformanceSpan, expectedMemoryPerformanceSpan, @@ -188,7 +185,6 @@ sentryTest( expectedNavigationPerformanceSpan, expectedLCPPerformanceSpan, expectedCLSPerformanceSpan, - expectedFIDPerformanceSpan, expectedFPPerformanceSpan, expectedFCPPerformanceSpan, expectedMemoryPerformanceSpan, @@ -326,7 +322,6 @@ sentryTest( expectedNavigationPerformanceSpan, expectedLCPPerformanceSpan, expectedCLSPerformanceSpan, - expectedFIDPerformanceSpan, expectedFPPerformanceSpan, expectedFCPPerformanceSpan, expectedMemoryPerformanceSpan, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/template.html b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/template.html deleted file mode 100644 index a3aeb9048dd8..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/template.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts deleted file mode 100644 index a481364077a1..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should capture a FID vital.', async ({ browserName, getLocalTestUrl, page }) => { - // FID measurement is not generated on webkit - if (shouldSkipTracingTest() || browserName === 'webkit') { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.goto(url); - // To trigger FID - await page.locator('#fid-btn').click(); - - const eventData = await getFirstSentryEnvelopeRequest(page); - - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.fid?.value).toBeDefined(); - - const fidSpan = eventData.spans?.filter(({ description }) => description === 'first input delay')[0]; - - expect(fidSpan).toBeDefined(); - expect(fidSpan?.op).toBe('ui.action'); - expect(fidSpan?.parent_span_id).toBe(eventData.contexts?.trace?.span_id); -}); diff --git a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts index 56ff33f878fa..97787c2de26e 100644 --- a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts +++ b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts @@ -148,19 +148,6 @@ export const expectedCLSPerformanceSpan = { }, }; -export const expectedFIDPerformanceSpan = { - op: 'web-vital', - description: 'first-input-delay', - startTimestamp: expect.any(Number), - endTimestamp: expect.any(Number), - data: { - value: expect.any(Number), - rating: expect.any(String), - size: expect.any(Number), - nodeIds: expect.any(Array), - }, -}; - export const expectedINPPerformanceSpan = { op: 'web-vital', description: 'interaction-to-next-paint', diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts index 76abdb1fa6b5..c11fba897aff 100644 --- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/fixtures/ReplayRecordingData.ts @@ -245,25 +245,6 @@ export const ReplayRecordingData = [ }, }, }, - { - type: 5, - timestamp: expect.any(Number), - data: { - tag: 'performanceSpan', - payload: { - op: 'web-vital', - description: 'first-input-delay', - startTimestamp: expect.any(Number), - endTimestamp: expect.any(Number), - data: { - value: expect.any(Number), - size: expect.any(Number), - rating: expect.any(String), - nodeIds: [10], - }, - }, - }, - }, { type: 5, timestamp: expect.any(Number), diff --git a/packages/browser-utils/src/index.ts b/packages/browser-utils/src/index.ts index 0a2d9e85ade9..bf6605f3f399 100644 --- a/packages/browser-utils/src/index.ts +++ b/packages/browser-utils/src/index.ts @@ -1,7 +1,6 @@ export { addPerformanceInstrumentationHandler, addClsInstrumentationHandler, - addFidInstrumentationHandler, addTtfbInstrumentationHandler, addLcpInstrumentationHandler, addInpInstrumentationHandler, diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index d182c64030fa..870558ada39d 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -17,7 +17,6 @@ import { trackClsAsStandaloneSpan } from './cls'; import { type PerformanceLongAnimationFrameTiming, addClsInstrumentationHandler, - addFidInstrumentationHandler, addLcpInstrumentationHandler, addPerformanceInstrumentationHandler, addTtfbInstrumentationHandler, @@ -103,13 +102,11 @@ export function startTrackingWebVitals({ if (performance.mark) { WINDOW.performance.mark('sentry-tracing-init'); } - const fidCleanupCallback = _trackFID(); const lcpCleanupCallback = recordLcpStandaloneSpans ? trackLcpAsStandaloneSpan(client) : _trackLCP(); const ttfbCleanupCallback = _trackTtfb(); const clsCleanupCallback = recordClsStandaloneSpans ? trackClsAsStandaloneSpan(client) : _trackCLS(); return (): void => { - fidCleanupCallback(); lcpCleanupCallback?.(); ttfbCleanupCallback(); clsCleanupCallback?.(); @@ -277,21 +274,6 @@ function _trackLCP(): () => void { }, true); } -/** Starts tracking the First Input Delay on the current page. */ -function _trackFID(): () => void { - return addFidInstrumentationHandler(({ metric }) => { - const entry = metric.entries[metric.entries.length - 1]; - if (!entry) { - return; - } - - const timeOrigin = msToSec(browserPerformanceTimeOrigin() as number); - const startTime = msToSec(entry.startTime); - _measurements['fid'] = { value: metric.value, unit: 'millisecond' }; - _measurements['mark.fid'] = { value: timeOrigin + startTime, unit: 'second' }; - }); -} - function _trackTtfb(): () => void { return addTtfbInstrumentationHandler(({ metric }) => { const entry = metric.entries[metric.entries.length - 1]; @@ -415,21 +397,6 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries if (op === 'pageload') { _addTtfbRequestTimeToMeasurements(_measurements); - const fidMark = _measurements['mark.fid']; - if (fidMark && _measurements['fid']) { - // create span for FID - startAndEndSpan(span, fidMark.value, fidMark.value + msToSec(_measurements['fid'].value), { - name: 'first input delay', - op: 'ui.action', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - }, - }); - - // Delete mark.fid as we don't want it to be part of final payload - delete _measurements['mark.fid']; - } - // If CLS standalone spans are enabled, don't record CLS as a measurement if (!options.recordClsOnPageloadSpan) { delete _measurements.cls; diff --git a/packages/browser-utils/src/metrics/inp.ts b/packages/browser-utils/src/metrics/inp.ts index 30a628b5997f..837f4571185f 100644 --- a/packages/browser-utils/src/metrics/inp.ts +++ b/packages/browser-utils/src/metrics/inp.ts @@ -182,5 +182,4 @@ export function registerInpInteractionListener(): void { }; addPerformanceInstrumentationHandler('event', handleEntries); - addPerformanceInstrumentationHandler('first-input', handleEntries); } diff --git a/packages/browser-utils/src/metrics/instrument.ts b/packages/browser-utils/src/metrics/instrument.ts index 23bbf57367d3..2918ca2a9e9b 100644 --- a/packages/browser-utils/src/metrics/instrument.ts +++ b/packages/browser-utils/src/metrics/instrument.ts @@ -1,22 +1,14 @@ import { debug, getFunctionName } from '@sentry/core'; import { DEBUG_BUILD } from '../debug-build'; import { onCLS } from './web-vitals/getCLS'; -import { onFID } from './web-vitals/getFID'; import { onINP } from './web-vitals/getINP'; import { onLCP } from './web-vitals/getLCP'; import { observe } from './web-vitals/lib/observe'; import { onTTFB } from './web-vitals/onTTFB'; -type InstrumentHandlerTypePerformanceObserver = - | 'longtask' - | 'event' - | 'navigation' - | 'paint' - | 'resource' - | 'first-input' - | 'element'; +type InstrumentHandlerTypePerformanceObserver = 'longtask' | 'event' | 'navigation' | 'paint' | 'resource' | 'element'; -type InstrumentHandlerTypeMetric = 'cls' | 'lcp' | 'fid' | 'ttfb' | 'inp'; +type InstrumentHandlerTypeMetric = 'cls' | 'lcp' | 'ttfb' | 'inp'; // We provide this here manually instead of relying on a global, as this is not available in non-browser environements // And we do not want to expose such types @@ -51,7 +43,7 @@ interface Metric { /** * The name of the metric (in acronym form). */ - name: 'CLS' | 'FCP' | 'FID' | 'INP' | 'LCP' | 'TTFB'; + name: 'CLS' | 'FCP' | 'INP' | 'LCP' | 'TTFB'; /** * The current value of the metric. @@ -111,7 +103,6 @@ const handlers: { [key in InstrumentHandlerType]?: InstrumentHandlerCallback[] } const instrumented: { [key in InstrumentHandlerType]?: boolean } = {}; let _previousCls: Metric | undefined; -let _previousFid: Metric | undefined; let _previousLcp: Metric | undefined; let _previousTtfb: Metric | undefined; let _previousInp: Metric | undefined; @@ -145,15 +136,7 @@ export function addLcpInstrumentationHandler( } /** - * Add a callback that will be triggered when a FID metric is available. - * Returns a cleanup callback which can be called to remove the instrumentation handler. - */ -export function addFidInstrumentationHandler(callback: (data: { metric: Metric }) => void): CleanupHandlerCallback { - return addMetricObserver('fid', callback, instrumentFid, _previousFid); -} - -/** - * Add a callback that will be triggered when a FID metric is available. + * Add a callback that will be triggered when a TTFD metric is available. */ export function addTtfbInstrumentationHandler(callback: (data: { metric: Metric }) => void): CleanupHandlerCallback { return addMetricObserver('ttfb', callback, instrumentTtfb, _previousTtfb); @@ -236,15 +219,6 @@ function instrumentCls(): StopListening { ); } -function instrumentFid(): void { - return onFID(metric => { - triggerHandlers('fid', { - metric, - }); - _previousFid = metric; - }); -} - function instrumentLcp(): StopListening { return onLCP( metric => { diff --git a/packages/browser-utils/src/metrics/web-vitals/README.md b/packages/browser-utils/src/metrics/web-vitals/README.md index 4f9d29e5f02f..0363e5c3c3de 100644 --- a/packages/browser-utils/src/metrics/web-vitals/README.md +++ b/packages/browser-utils/src/metrics/web-vitals/README.md @@ -27,6 +27,10 @@ web-vitals only report once per pageload. ## CHANGELOG +TODO: PR Url + +- Removed FID-related code with v10 of the SDK + https://github.com/getsentry/sentry-javascript/pull/16492 - Bumped from Web Vitals 4.2.5 to 5.0.2 diff --git a/packages/browser-utils/src/metrics/web-vitals/getFID.ts b/packages/browser-utils/src/metrics/web-vitals/getFID.ts deleted file mode 100644 index b549f4c07c7c..000000000000 --- a/packages/browser-utils/src/metrics/web-vitals/getFID.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * // Sentry: web-vitals removed FID reporting from v5. We're keeping it around - * for the time being. - * // TODO(v10): Remove FID reporting! - */ - -import { bindReporter } from './lib/bindReporter'; -import { getVisibilityWatcher } from './lib/getVisibilityWatcher'; -import { initMetric } from './lib/initMetric'; -import { observe } from './lib/observe'; -import { onHidden } from './lib/onHidden'; -import { runOnce } from './lib/runOnce'; -import { whenActivated } from './lib/whenActivated'; -import type { FIDMetric, MetricRatingThresholds, ReportOpts } from './types'; - -/** Thresholds for FID. See https://web.dev/articles/fid#what_is_a_good_fid_score */ -export const FIDThresholds: MetricRatingThresholds = [100, 300]; - -/** - * Calculates the [FID](https://web.dev/articles/fid) value for the current page and - * calls the `callback` function once the value is ready, along with the - * relevant `first-input` performance entry used to determine the value. The - * reported value is a `DOMHighResTimeStamp`. - * - * _**Important:** since FID is only reported after the user interacts with the - * page, it's possible that it will not be reported for some page loads._ - */ -export const onFID = (onReport: (metric: FIDMetric) => void, opts: ReportOpts = {}) => { - whenActivated(() => { - const visibilityWatcher = getVisibilityWatcher(); - const metric = initMetric('FID'); - // eslint-disable-next-line prefer-const - let report: ReturnType; - - const handleEntry = (entry: PerformanceEventTiming): void => { - // Only report if the page wasn't hidden prior to the first input. - if (entry.startTime < visibilityWatcher.firstHiddenTime) { - metric.value = entry.processingStart - entry.startTime; - metric.entries.push(entry); - report(true); - } - }; - - const handleEntries = (entries: FIDMetric['entries']) => { - (entries as PerformanceEventTiming[]).forEach(handleEntry); - }; - - const po = observe('first-input', handleEntries); - - report = bindReporter(onReport, metric, FIDThresholds, opts.reportAllChanges); - - if (po) { - // sentry: TODO: Figure out if we can use new whinIdleOrHidden insteard of onHidden - onHidden( - runOnce(() => { - handleEntries(po.takeRecords() as FIDMetric['entries']); - po.disconnect(); - }), - ); - } - }); -}; diff --git a/packages/browser-utils/src/metrics/web-vitals/lib/interactions.ts b/packages/browser-utils/src/metrics/web-vitals/lib/interactions.ts deleted file mode 100644 index 69ca920ddb67..000000000000 --- a/packages/browser-utils/src/metrics/web-vitals/lib/interactions.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getInteractionCount } from './polyfills/interactionCountPolyfill'; - -interface Interaction { - id: number; - latency: number; - entries: PerformanceEventTiming[]; -} - -interface EntryPreProcessingHook { - (entry: PerformanceEventTiming): void; -} - -// A list of longest interactions on the page (by latency) sorted so the -// longest one is first. The list is at most MAX_INTERACTIONS_TO_CONSIDER long. -export const longestInteractionList: Interaction[] = []; - -// A mapping of longest interactions by their interaction ID. -// This is used for faster lookup. -export const longestInteractionMap: Map = new Map(); - -// The default `durationThreshold` used across this library for observing -// `event` entries via PerformanceObserver. -export const DEFAULT_DURATION_THRESHOLD = 40; - -// Used to store the interaction count after a bfcache restore, since p98 -// interaction latencies should only consider the current navigation. -let prevInteractionCount = 0; - -/** - * Returns the interaction count since the last bfcache restore (or for the - * full page lifecycle if there were no bfcache restores). - */ -const getInteractionCountForNavigation = () => { - return getInteractionCount() - prevInteractionCount; -}; - -export const resetInteractions = () => { - prevInteractionCount = getInteractionCount(); - longestInteractionList.length = 0; - longestInteractionMap.clear(); -}; - -/** - * Returns the estimated p98 longest interaction based on the stored - * interaction candidates and the interaction count for the current page. - */ -export const estimateP98LongestInteraction = () => { - const candidateInteractionIndex = Math.min( - longestInteractionList.length - 1, - Math.floor(getInteractionCountForNavigation() / 50), - ); - - return longestInteractionList[candidateInteractionIndex]; -}; - -// To prevent unnecessary memory usage on pages with lots of interactions, -// store at most 10 of the longest interactions to consider as INP candidates. -const MAX_INTERACTIONS_TO_CONSIDER = 10; - -/** - * A list of callback functions to run before each entry is processed. - * Exposing this list allows the attribution build to hook into the - * entry processing pipeline. - */ -export const entryPreProcessingCallbacks: EntryPreProcessingHook[] = []; - -/** - * Takes a performance entry and adds it to the list of worst interactions - * if its duration is long enough to make it among the worst. If the - * entry is part of an existing interaction, it is merged and the latency - * and entries list is updated as needed. - */ -export const processInteractionEntry = (entry: PerformanceEventTiming) => { - entryPreProcessingCallbacks.forEach(cb => cb(entry)); - - // Skip further processing for entries that cannot be INP candidates. - if (!(entry.interactionId || entry.entryType === 'first-input')) return; - - // The least-long of the 10 longest interactions. - const minLongestInteraction = longestInteractionList[longestInteractionList.length - 1]; - - const existingInteraction = longestInteractionMap.get(entry.interactionId!); - - // Only process the entry if it's possibly one of the ten longest, - // or if it's part of an existing interaction. - if ( - existingInteraction || - longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER || - (minLongestInteraction && entry.duration > minLongestInteraction.latency) - ) { - // If the interaction already exists, update it. Otherwise create one. - if (existingInteraction) { - // If the new entry has a longer duration, replace the old entries, - // otherwise add to the array. - if (entry.duration > existingInteraction.latency) { - existingInteraction.entries = [entry]; - existingInteraction.latency = entry.duration; - } else if ( - entry.duration === existingInteraction.latency && - entry.startTime === existingInteraction.entries[0]?.startTime - ) { - existingInteraction.entries.push(entry); - } - } else { - const interaction = { - id: entry.interactionId!, - latency: entry.duration, - entries: [entry], - }; - longestInteractionMap.set(interaction.id, interaction); - longestInteractionList.push(interaction); - } - - // Sort the entries by latency (descending) and keep only the top ten. - longestInteractionList.sort((a, b) => b.latency - a.latency); - if (longestInteractionList.length > MAX_INTERACTIONS_TO_CONSIDER) { - longestInteractionList.splice(MAX_INTERACTIONS_TO_CONSIDER).forEach(i => longestInteractionMap.delete(i.id)); - } - } -}; diff --git a/packages/browser-utils/src/metrics/web-vitals/lib/observe.ts b/packages/browser-utils/src/metrics/web-vitals/lib/observe.ts index 6071893dfa8e..7644ec559d88 100644 --- a/packages/browser-utils/src/metrics/web-vitals/lib/observe.ts +++ b/packages/browser-utils/src/metrics/web-vitals/lib/observe.ts @@ -16,7 +16,6 @@ interface PerformanceEntryMap { event: PerformanceEventTiming[]; - 'first-input': PerformanceEventTiming[]; 'layout-shift': LayoutShift[]; 'largest-contentful-paint': LargestContentfulPaint[]; 'long-animation-frame': PerformanceLongAnimationFrameTiming[]; diff --git a/packages/browser-utils/src/metrics/web-vitals/lib/onHidden.ts b/packages/browser-utils/src/metrics/web-vitals/lib/onHidden.ts index 1844a616a479..5a3c1b4fc810 100644 --- a/packages/browser-utils/src/metrics/web-vitals/lib/onHidden.ts +++ b/packages/browser-utils/src/metrics/web-vitals/lib/onHidden.ts @@ -27,7 +27,7 @@ export interface OnHiddenCallback { // The PR removed listening to the `pagehide` event, in favour of only listening to `visibilitychange` event. // This is "more correct" but some browsers we still support (Safari <14.4) don't fully support `visibilitychange` // or have known bugs w.r.t the `visibilitychange` event. -// TODO (v10): If we decide to drop support for Safari 14.4, we can use the logic from web-vitals 4.2.4 +// TODO (v11): If we decide to drop support for Safari 14.4, we can use the logic from web-vitals 4.2.4 // In this case, we also need to update the integration tests that currently trigger the `pagehide` event to // simulate the page being hidden. export const onHidden = (cb: OnHiddenCallback) => { diff --git a/packages/browser-utils/src/metrics/web-vitals/types.ts b/packages/browser-utils/src/metrics/web-vitals/types.ts index 033fbee09926..8146849182b5 100644 --- a/packages/browser-utils/src/metrics/web-vitals/types.ts +++ b/packages/browser-utils/src/metrics/web-vitals/types.ts @@ -15,11 +15,9 @@ */ export * from './types/base'; -export * from './types/polyfills'; export * from './types/cls'; export * from './types/fcp'; -export * from './types/fid'; // FIX was removed in 5.0.2 but we keep it around for now export * from './types/inp'; export * from './types/lcp'; export * from './types/ttfb'; diff --git a/packages/browser-utils/src/metrics/web-vitals/types/base.ts b/packages/browser-utils/src/metrics/web-vitals/types/base.ts index d8315b817f4a..02cb566011ac 100644 --- a/packages/browser-utils/src/metrics/web-vitals/types/base.ts +++ b/packages/browser-utils/src/metrics/web-vitals/types/base.ts @@ -16,7 +16,6 @@ import type { CLSMetric, CLSMetricWithAttribution } from './cls'; import type { FCPMetric, FCPMetricWithAttribution } from './fcp'; -import type { FIDMetric, FIDMetricWithAttribution } from './fid'; import type { INPMetric, INPMetricWithAttribution } from './inp'; import type { LCPMetric, LCPMetricWithAttribution } from './lcp'; import type { TTFBMetric, TTFBMetricWithAttribution } from './ttfb'; @@ -24,9 +23,8 @@ import type { TTFBMetric, TTFBMetricWithAttribution } from './ttfb'; export interface Metric { /** * The name of the metric (in acronym form). - * // sentry: re-added FID here since we continue supporting it for now */ - name: 'CLS' | 'FCP' | 'FID' | 'INP' | 'LCP' | 'TTFB'; + name: 'CLS' | 'FCP' | 'INP' | 'LCP' | 'TTFB'; /** * The current value of the metric. @@ -79,14 +77,12 @@ export interface Metric { } /** The union of supported metric types. */ -// sentry: re-added FIDMetric here since we continue supporting it for now -export type MetricType = CLSMetric | FCPMetric | FIDMetric | INPMetric | LCPMetric | TTFBMetric; +export type MetricType = CLSMetric | FCPMetric | INPMetric | LCPMetric | TTFBMetric; /** The union of supported metric attribution types. */ export type MetricWithAttribution = | CLSMetricWithAttribution | FCPMetricWithAttribution - | FIDMetricWithAttribution | INPMetricWithAttribution | LCPMetricWithAttribution | TTFBMetricWithAttribution; diff --git a/packages/browser-utils/src/metrics/web-vitals/types/fid.ts b/packages/browser-utils/src/metrics/web-vitals/types/fid.ts deleted file mode 100644 index 953607adff98..000000000000 --- a/packages/browser-utils/src/metrics/web-vitals/types/fid.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { LoadState, Metric } from './base'; - -/** - * An FID-specific version of the Metric object. - */ -export interface FIDMetric extends Metric { - name: 'FID'; - entries: PerformanceEventTiming[]; -} - -/** - * An object containing potentially-helpful debugging information that - * can be sent along with the FID value for the current page visit in order - * to help identify issues happening to real-users in the field. - */ -export interface FIDAttribution { - /** - * A selector identifying the element that the user interacted with. This - * element will be the `target` of the `event` dispatched. - */ - eventTarget: string; - /** - * The time when the user interacted. This time will match the `timeStamp` - * value of the `event` dispatched. - */ - eventTime: number; - /** - * The `type` of the `event` dispatched from the user interaction. - */ - eventType: string; - /** - * The `PerformanceEventTiming` entry corresponding to FID. - */ - eventEntry: PerformanceEventTiming; - /** - * The loading state of the document at the time when the first interaction - * occurred (see `LoadState` for details). If the first interaction occurred - * while the document was loading and executing script (e.g. usually in the - * `dom-interactive` phase) it can result in long input delays. - */ - loadState: LoadState; -} - -/** - * An FID-specific version of the Metric object with attribution. - */ -export interface FIDMetricWithAttribution extends FIDMetric { - attribution: FIDAttribution; -} diff --git a/packages/browser-utils/src/metrics/web-vitals/types/polyfills.ts b/packages/browser-utils/src/metrics/web-vitals/types/polyfills.ts deleted file mode 100644 index c4314c0697fa..000000000000 --- a/packages/browser-utils/src/metrics/web-vitals/types/polyfills.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export type FirstInputPolyfillEntry = Omit; - -export interface FirstInputPolyfillCallback { - (entry: FirstInputPolyfillEntry): void; -} diff --git a/packages/replay-internal/src/coreHandlers/performanceObserver.ts b/packages/replay-internal/src/coreHandlers/performanceObserver.ts index 638ef53b05fb..cd3a2aafed4b 100644 --- a/packages/replay-internal/src/coreHandlers/performanceObserver.ts +++ b/packages/replay-internal/src/coreHandlers/performanceObserver.ts @@ -1,6 +1,5 @@ import { addClsInstrumentationHandler, - addFidInstrumentationHandler, addInpInstrumentationHandler, addLcpInstrumentationHandler, addPerformanceInstrumentationHandler, @@ -8,7 +7,6 @@ import { import type { ReplayContainer } from '../types'; import { getCumulativeLayoutShift, - getFirstInputDelay, getInteractionToNextPaint, getLargestContentfulPaint, webVitalHandler, @@ -39,7 +37,6 @@ export function setupPerformanceObserver(replay: ReplayContainer): () => void { clearCallbacks.push( addLcpInstrumentationHandler(webVitalHandler(getLargestContentfulPaint, replay)), addClsInstrumentationHandler(webVitalHandler(getCumulativeLayoutShift, replay)), - addFidInstrumentationHandler(webVitalHandler(getFirstInputDelay, replay)), addInpInstrumentationHandler(webVitalHandler(getInteractionToNextPaint, replay)), ); diff --git a/packages/replay-internal/src/types/replayFrame.ts b/packages/replay-internal/src/types/replayFrame.ts index 6eb1855b8f8a..d060204256bf 100644 --- a/packages/replay-internal/src/types/replayFrame.ts +++ b/packages/replay-internal/src/types/replayFrame.ts @@ -172,7 +172,7 @@ interface ReplayHistoryFrame extends ReplayBaseSpanFrame { interface ReplayWebVitalFrame extends ReplayBaseSpanFrame { data: WebVitalData; - op: 'largest-contentful-paint' | 'cumulative-layout-shift' | 'first-input-delay' | 'interaction-to-next-paint'; + op: 'largest-contentful-paint' | 'cumulative-layout-shift' | 'interaction-to-next-paint'; } interface ReplayMemoryFrame extends ReplayBaseSpanFrame { diff --git a/packages/replay-internal/src/util/createPerformanceEntries.ts b/packages/replay-internal/src/util/createPerformanceEntries.ts index 6df2343327fe..b8a39f233074 100644 --- a/packages/replay-internal/src/util/createPerformanceEntries.ts +++ b/packages/replay-internal/src/util/createPerformanceEntries.ts @@ -221,15 +221,6 @@ export function getCumulativeLayoutShift(metric: Metric): ReplayPerformanceEntry return getWebVital(metric, 'cumulative-layout-shift', nodes, layoutShifts); } -/** - * Add a FID event to the replay based on a FID metric. - */ -export function getFirstInputDelay(metric: Metric): ReplayPerformanceEntry { - const lastEntry = metric.entries[metric.entries.length - 1] as (PerformanceEntry & { target?: Node }) | undefined; - const node = lastEntry?.target ? [lastEntry.target] : undefined; - return getWebVital(metric, 'first-input-delay', node); -} - /** * Add an INP event to the replay based on an INP metric. */ diff --git a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts index a17c421c518b..c87d18bff325 100644 --- a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts +++ b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts @@ -4,7 +4,6 @@ import { WINDOW } from '../../../src/constants'; import { createPerformanceEntries, getCumulativeLayoutShift, - getFirstInputDelay, getInteractionToNextPaint, getLargestContentfulPaint, } from '../../../src/util/createPerformanceEntries'; @@ -108,26 +107,6 @@ describe('Unit | util | createPerformanceEntries', () => { }); }); - describe('getFirstInputDelay', () => { - it('works with an FID metric', async () => { - const metric = { - value: 5108.299, - rating: 'good' as const, - entries: [], - }; - - const event = getFirstInputDelay(metric); - - expect(event).toEqual({ - type: 'web-vital', - name: 'first-input-delay', - start: 1672531205.108299, - end: 1672531205.108299, - data: { value: 5108.299, size: 5108.299, rating: 'good', nodeIds: undefined, attributions: undefined }, - }); - }); - }); - describe('getInteractionToNextPaint', () => { it('works with an INP metric', async () => { const metric = { From c1145b28b690f71a3abe5c785d9784d568f673e3 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 18 Jul 2025 10:42:20 +0200 Subject: [PATCH 2/6] readd first-input in ww `observe.ts` --- packages/browser-utils/src/metrics/web-vitals/lib/observe.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/browser-utils/src/metrics/web-vitals/lib/observe.ts b/packages/browser-utils/src/metrics/web-vitals/lib/observe.ts index 7644ec559d88..6071893dfa8e 100644 --- a/packages/browser-utils/src/metrics/web-vitals/lib/observe.ts +++ b/packages/browser-utils/src/metrics/web-vitals/lib/observe.ts @@ -16,6 +16,7 @@ interface PerformanceEntryMap { event: PerformanceEventTiming[]; + 'first-input': PerformanceEventTiming[]; 'layout-shift': LayoutShift[]; 'largest-contentful-paint': LargestContentfulPaint[]; 'long-animation-frame': PerformanceLongAnimationFrameTiming[]; From 97dfd610090b8f2a76e7383c3e3919158893129e Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 18 Jul 2025 11:35:34 +0200 Subject: [PATCH 3/6] add migration entry --- MIGRATION.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 63b9b67e1f9d..3a21e9288656 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -70,3 +70,20 @@ Additionally, we hold ourselves accountable to any security issues, meaning that Note, that it is decided on a case-per-case basis, what gets backported or not. If you need a fix or feature in a previous version of the SDK, please reach out via a GitHub Issue. + +## 3. Behaviour Changes + +### Removal of First Input Delay (FID) Web Vital Reporting + +Affected SDKs: All SDKs running in browser applications (`@sentry/browser`, `@sentry/react`, `@sentry/nextjs`, etc.) + +In v10, the SDK stopped reporting the First Input Delay (FID) web vital. +This was done because FID has been replaced by Interaction to Next Paint (INP) and is therefore no longer relevant for assessing and tracking a website's performance. +For reference, FID has long been deprecated by Google's official `web-vitals` library and was eventually removed in version `5.0.0`. +Sentry now follows Google's lead by also removing it. + +The removal entails **no breaking API changes**. However, in rare cases, you might need to adjust some of your Sentry SDK and product setup: + +- Remove any logic in `beforeSend` or other filtering/event processing logic that depends on FID or replace it with INP logic. +- If you set up Sentry Alerts that depend on FID, be aware that these could trigger once you upgrade the SDK, due to a lack of new values. + To replace them, adjust your alerts (or dashbaords) to use INP. From be2eb3f9d65fde4e92ebc71cbf9271d011961d59 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 18 Jul 2025 17:01:24 +0200 Subject: [PATCH 4/6] impressive, cursor, impressive ... --- .../browser-integration-tests/suites/replay/customEvents/test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts b/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts index 4bb925507c49..a83f6e9fa5ce 100644 --- a/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts @@ -56,7 +56,6 @@ sentryTest( expectedLCPPerformanceSpan, expectedCLSPerformanceSpan, expectedFPPerformanceSpan, - expectedFPPerformanceSpan, expectedFCPPerformanceSpan, expectedMemoryPerformanceSpan, // two memory spans - once per flush expectedMemoryPerformanceSpan, From e75fa8f4e56a711c37840380c3eebd9afeb15758 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 18 Jul 2025 17:15:27 +0200 Subject: [PATCH 5/6] bring back first-input listener for our INP augmentation --- packages/browser-utils/src/metrics/inp.ts | 1 + packages/browser-utils/src/metrics/instrument.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/browser-utils/src/metrics/inp.ts b/packages/browser-utils/src/metrics/inp.ts index 837f4571185f..30a628b5997f 100644 --- a/packages/browser-utils/src/metrics/inp.ts +++ b/packages/browser-utils/src/metrics/inp.ts @@ -182,4 +182,5 @@ export function registerInpInteractionListener(): void { }; addPerformanceInstrumentationHandler('event', handleEntries); + addPerformanceInstrumentationHandler('first-input', handleEntries); } diff --git a/packages/browser-utils/src/metrics/instrument.ts b/packages/browser-utils/src/metrics/instrument.ts index 2918ca2a9e9b..8017bd4c89e1 100644 --- a/packages/browser-utils/src/metrics/instrument.ts +++ b/packages/browser-utils/src/metrics/instrument.ts @@ -6,7 +6,15 @@ import { onLCP } from './web-vitals/getLCP'; import { observe } from './web-vitals/lib/observe'; import { onTTFB } from './web-vitals/onTTFB'; -type InstrumentHandlerTypePerformanceObserver = 'longtask' | 'event' | 'navigation' | 'paint' | 'resource' | 'element'; +type InstrumentHandlerTypePerformanceObserver = + | 'longtask' + | 'event' + | 'navigation' + | 'paint' + | 'resource' + | 'element' + // fist-input is still needed for INP + | 'first-input'; type InstrumentHandlerTypeMetric = 'cls' | 'lcp' | 'ttfb' | 'inp'; From 165b96382aada95a44f452d4e87de319d4d58a84 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 23 Jul 2025 17:21:02 +0200 Subject: [PATCH 6/6] add PR URL to ww changelog --- packages/browser-utils/src/metrics/web-vitals/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser-utils/src/metrics/web-vitals/README.md b/packages/browser-utils/src/metrics/web-vitals/README.md index 0363e5c3c3de..a57937246cdd 100644 --- a/packages/browser-utils/src/metrics/web-vitals/README.md +++ b/packages/browser-utils/src/metrics/web-vitals/README.md @@ -27,7 +27,7 @@ web-vitals only report once per pageload. ## CHANGELOG -TODO: PR Url +https://github.com/getsentry/sentry-javascript/pull/17076 - Removed FID-related code with v10 of the SDK