Skip to content

Commit 5e5b6bc

Browse files
committed
feat(browser): Explicitly end pageload span via Sentry.reportPageLoaded
1 parent 3c76c5d commit 5e5b6bc

File tree

1 file changed

+56
-3
lines changed

1 file changed

+56
-3
lines changed

packages/browser/src/tracing/browserTracingIntegration.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,23 @@ export interface BrowserTracingOptions {
250250
*/
251251
consistentTraceSampling: boolean;
252252

253+
/**
254+
* If set to `true`, the pageload span will not end itself automatically, unless it
255+
* runs until the {@link BrowserTracingOptions.finalTimeout} (30 seconds by default) is reached.
256+
*
257+
* Set this option to `true`, if you want full control over the pageload span duration.
258+
* You can use `Sentry.reportPageLoaded()` to manually end the pageload span whenever convenient.
259+
* Be aware that you have to ensure that this is always called, regardless of the chosen route
260+
* or path in the application.
261+
*
262+
* @default `false`. By default, the pageload span will end itself automatically, based on
263+
* the {@link BrowserTracingOptions.finalTimeout}, {@link BrowserTracingOptions.idleTimeout}
264+
* and {@link BrowserTracingOptions.childSpanTimeout}. This is more convenient to use but means
265+
* that the pageload duration can be arbitrary and might not be fully representative of a perceived
266+
* page load time.
267+
*/
268+
explicitPageloadEnd: boolean;
269+
253270
/**
254271
* _experiments allows the user to send options to define how this integration works.
255272
*
@@ -297,6 +314,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
297314
detectRedirects: true,
298315
linkPreviousTrace: 'in-memory',
299316
consistentTraceSampling: false,
317+
explicitPageloadEnd: false,
300318
_experiments: {},
301319
...defaultRequestInstrumentationOptions,
302320
};
@@ -345,6 +363,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
345363
detectRedirects,
346364
linkPreviousTrace,
347365
consistentTraceSampling,
366+
explicitPageloadEnd,
348367
onRequestSpanStart,
349368
} = {
350369
...DEFAULT_BROWSER_TRACING_OPTIONS,
@@ -354,9 +373,11 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
354373
let _collectWebVitals: undefined | (() => void);
355374
let lastInteractionTimestamp: number | undefined;
356375

376+
let _pageloadSpan: Span | undefined;
377+
357378
/** Create routing idle transaction. */
358379
function _createRouteSpan(client: Client, startSpanOptions: StartSpanOptions, makeActive = true): void {
359-
const isPageloadTransaction = startSpanOptions.op === 'pageload';
380+
const isPageloadSpan = startSpanOptions.op === 'pageload';
360381

361382
const initialSpanName = startSpanOptions.name;
362383
const finalStartSpanOptions: StartSpanOptions = beforeStartSpan
@@ -390,7 +411,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
390411
finalTimeout,
391412
childSpanTimeout,
392413
// should wait for finish signal if it's a pageload transaction
393-
disableAutoFinish: isPageloadTransaction,
414+
disableAutoFinish: isPageloadSpan,
394415
beforeSpanEnd: span => {
395416
// This will generally always be defined here, because it is set in `setup()` of the integration
396417
// but technically, it is optional, so we guard here to be extra safe
@@ -418,6 +439,10 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
418439
},
419440
});
420441

442+
if (isPageloadSpan && explicitPageloadEnd) {
443+
_pageloadSpan = idleSpan;
444+
}
445+
421446
setActiveIdleSpan(client, idleSpan);
422447

423448
function emitFinish(): void {
@@ -426,7 +451,8 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
426451
}
427452
}
428453

429-
if (isPageloadTransaction && optionalWindowDocument) {
454+
// Enable auto finish of the pageload span if users are not explicitly ending it
455+
if (isPageloadSpan && !explicitPageloadEnd && optionalWindowDocument) {
430456
optionalWindowDocument.addEventListener('readystatechange', () => {
431457
emitFinish();
432458
});
@@ -574,6 +600,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
574600
});
575601
});
576602
},
603+
577604
afterAllSetup(client) {
578605
let startingUrl: string | undefined = getLocationHref();
579606

@@ -654,6 +681,13 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
654681
onRequestSpanStart,
655682
});
656683
},
684+
685+
endPageloadSpan() {
686+
if (_pageloadSpan && explicitPageloadEnd) {
687+
_pageloadSpan.end();
688+
_pageloadSpan = undefined;
689+
}
690+
},
657691
};
658692
}) satisfies IntegrationFn;
659693

@@ -723,6 +757,25 @@ export function getMetaContent(metaName: string): string | undefined {
723757
return metaTag?.getAttribute('content') || undefined;
724758
}
725759

760+
/**
761+
* Manually report the end of the page load, resulting in the SDK ending the pageload span.
762+
* This only works if {@link BrowserTracingOptions.explicitPageloadEnd} is set to `true`.
763+
* Otherwise, the pageload span will end itself based on the {@link BrowserTracingOptions.finalTimeout},
764+
* {@link BrowserTracingOptions.idleTimeout} and {@link BrowserTracingOptions.childSpanTimeout}.
765+
*
766+
* @param client - The client to use. If not provided, the global client will be used.
767+
*/
768+
export function reportPageLoaded(client?: Client): void {
769+
const clientToUse = client ?? getClient();
770+
if (clientToUse) {
771+
const browserTracing =
772+
clientToUse.getIntegrationByName<ReturnType<typeof browserTracingIntegration>>('BrowserTracing');
773+
if (browserTracing) {
774+
browserTracing.endPageloadSpan();
775+
}
776+
}
777+
}
778+
726779
/** Start listener for interaction transactions */
727780
function registerInteractionListener(
728781
client: Client,

0 commit comments

Comments
 (0)