diff --git a/packages/browser-utils/src/index.ts b/packages/browser-utils/src/index.ts index 52da63c3d6b6..ef64f0a5bc70 100644 --- a/packages/browser-utils/src/index.ts +++ b/packages/browser-utils/src/index.ts @@ -24,6 +24,7 @@ export { export { elementTimingIntegration, startTrackingElementTiming } from './performance/elementTiming'; export { interactionsIntegration } from './performance/interactions'; +export { longTasksIntegration } from './performance/longTasks'; export { isBotUserAgent } from './isBotUserAgent'; diff --git a/packages/browser-utils/src/performance/longTasks.ts b/packages/browser-utils/src/performance/longTasks.ts new file mode 100644 index 000000000000..d95d3d59a9fd --- /dev/null +++ b/packages/browser-utils/src/performance/longTasks.ts @@ -0,0 +1,45 @@ +import type { IntegrationFn } from '@sentry/core'; +import { defineIntegration, GLOBAL_OBJ } from '@sentry/core'; +import { startTrackingLongAnimationFrames, startTrackingLongTasks } from './entries'; + +interface LongTasksOptions { + /** + * If true, long tasks are captured as spans when long animation frames are disabled or not supported. + * + * Default: true + */ + enableLongTask?: boolean; + + /** + * If true, long animation frames are captured as spans in browsers that support them. + * + * Default: true + */ + enableLongAnimationFrame?: boolean; +} + +const INTEGRATION_NAME = 'LongTasks'; + +const _longTasksIntegration = ((options: LongTasksOptions = {}) => { + const { enableLongTask = true, enableLongAnimationFrame = true } = options; + + return { + name: INTEGRATION_NAME, + setup() { + if ( + enableLongAnimationFrame && + GLOBAL_OBJ.PerformanceObserver && + PerformanceObserver.supportedEntryTypes?.includes('long-animation-frame') + ) { + startTrackingLongAnimationFrames(); + } else if (enableLongTask) { + startTrackingLongTasks(); + } + }, + }; +}) satisfies IntegrationFn; + +/** + * Captures long animation frames, or long tasks where those are not supported, as spans on the active span. + */ +export const longTasksIntegration = defineIntegration(_longTasksIntegration); diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index ffda26d539ff..0cab1fdf912f 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -51,6 +51,7 @@ export { } from '@sentry/browser-utils'; export type { CachedRouteProvider, RouteProvider } from '@sentry/browser-utils'; export { interactionsIntegration } from './integrations/interactions'; +export { longTasksIntegration } from './integrations/longTasks'; export type { RequestInstrumentationOptions } from './tracing/request'; export { diff --git a/packages/browser/src/integrations/longTasks.ts b/packages/browser/src/integrations/longTasks.ts new file mode 100644 index 000000000000..6801fdbe57fa --- /dev/null +++ b/packages/browser/src/integrations/longTasks.ts @@ -0,0 +1 @@ +export { longTasksIntegration } from '@sentry/browser-utils'; diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index 25ab7da3d44d..252a27032b2f 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -18,7 +18,6 @@ import { getClient, getCurrentScope, getDynamicSamplingContextFromSpan, - GLOBAL_OBJ, hasSpansEnabled, hasSpanStreamingEnabled, NAVIGATION_SPAN_NAME_FALLBACK, @@ -36,11 +35,10 @@ import { import { _INTERNAL_ensureBrowserSpanStreaming, startIdleSpan, startInactiveSpan } from '@sentry/core/browser'; import { addHistoryInstrumentationHandler, + longTasksIntegration, addPerformanceEntries, getLocationHref, isBotUserAgent, - startTrackingLongAnimationFrames, - startTrackingLongTasks, } from '@sentry/browser-utils'; import { BROWSER_NAVIGATION_TYPE } from '@sentry/conventions/attributes'; import { DEBUG_BUILD } from '../debug-build'; @@ -286,7 +284,10 @@ export interface BrowserTracingOptions { * Options for `browserTracingCoreIntegration`: the options of `browserTracingIntegration`, minus the ones * for features that `browserTracingIntegration` registers as standalone integrations. */ -export type BrowserTracingCoreOptions = Omit; +export type BrowserTracingCoreOptions = Omit< + BrowserTracingOptions, + 'enableInp' | 'webVitals' | 'enableElementTiming' | 'enableLongTask' | 'enableLongAnimationFrame' +>; const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingCoreOptions = { ...TRACING_DEFAULTS, @@ -294,8 +295,6 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingCoreOptions = { instrumentBfcacheRestore: true, instrumentPageLoad: true, markBackgroundSpan: true, - enableLongTask: true, - enableLongAnimationFrame: true, ignoreResourceSpans: [], detectRedirects: true, linkPreviousTrace: 'in-memory', @@ -324,7 +323,7 @@ export const browserTracingIntegration = ((options: Partial { // Custom clients and test doubles may not implement it. @@ -339,6 +338,10 @@ export const browserTracingIntegration = ((options: Partial { lastInteractionTimestamp = timestampInSeconds(); diff --git a/packages/browser/test/tracing/browserTracingIntegration.test.ts b/packages/browser/test/tracing/browserTracingIntegration.test.ts index 927080f1755b..0ad2754a5ea4 100644 --- a/packages/browser/test/tracing/browserTracingIntegration.test.ts +++ b/packages/browser/test/tracing/browserTracingIntegration.test.ts @@ -225,6 +225,27 @@ describe('browserTracingIntegration', () => { expect(client.getIntegrationByName('WebVitals')).toBeUndefined(); }); + it.each([ + ['registers longTasksIntegration by default', {}, true], + ['registers longTasksIntegration with only long tasks enabled', { enableLongAnimationFrame: false }, true], + [ + 'does not register longTasksIntegration when both options are off', + { enableLongTask: false, enableLongAnimationFrame: false }, + false, + ], + ])('%s', (_name, options, registered) => { + const client = new BrowserClient( + getDefaultBrowserClientOptions({ + tracesSampleRate: 1, + integrations: [browserTracingIntegration(options)], + }), + ); + setCurrentClient(client); + client.init(); + + expect(!!client.getIntegrationByName('LongTasks')).toBe(registered); + }); + it('keeps the webVitalsIntegration the user supplies', () => { const userWebVitals = webVitalsModule.webVitalsIntegration({ softNavigations: false });