@@ -250,6 +250,23 @@ export interface BrowserTracingOptions {
250
250
*/
251
251
consistentTraceSampling : boolean ;
252
252
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
+
253
270
/**
254
271
* _experiments allows the user to send options to define how this integration works.
255
272
*
@@ -297,6 +314,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
297
314
detectRedirects : true ,
298
315
linkPreviousTrace : 'in-memory' ,
299
316
consistentTraceSampling : false ,
317
+ explicitPageloadEnd : false ,
300
318
_experiments : { } ,
301
319
...defaultRequestInstrumentationOptions ,
302
320
} ;
@@ -345,6 +363,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
345
363
detectRedirects,
346
364
linkPreviousTrace,
347
365
consistentTraceSampling,
366
+ explicitPageloadEnd,
348
367
onRequestSpanStart,
349
368
} = {
350
369
...DEFAULT_BROWSER_TRACING_OPTIONS ,
@@ -354,9 +373,11 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
354
373
let _collectWebVitals : undefined | ( ( ) => void ) ;
355
374
let lastInteractionTimestamp : number | undefined ;
356
375
376
+ let _pageloadSpan : Span | undefined ;
377
+
357
378
/** Create routing idle transaction. */
358
379
function _createRouteSpan ( client : Client , startSpanOptions : StartSpanOptions , makeActive = true ) : void {
359
- const isPageloadTransaction = startSpanOptions . op === 'pageload' ;
380
+ const isPageloadSpan = startSpanOptions . op === 'pageload' ;
360
381
361
382
const initialSpanName = startSpanOptions . name ;
362
383
const finalStartSpanOptions : StartSpanOptions = beforeStartSpan
@@ -390,7 +411,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
390
411
finalTimeout,
391
412
childSpanTimeout,
392
413
// should wait for finish signal if it's a pageload transaction
393
- disableAutoFinish : isPageloadTransaction ,
414
+ disableAutoFinish : isPageloadSpan ,
394
415
beforeSpanEnd : span => {
395
416
// This will generally always be defined here, because it is set in `setup()` of the integration
396
417
// but technically, it is optional, so we guard here to be extra safe
@@ -418,6 +439,10 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
418
439
} ,
419
440
} ) ;
420
441
442
+ if ( isPageloadSpan && explicitPageloadEnd ) {
443
+ _pageloadSpan = idleSpan ;
444
+ }
445
+
421
446
setActiveIdleSpan ( client , idleSpan ) ;
422
447
423
448
function emitFinish ( ) : void {
@@ -426,7 +451,8 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
426
451
}
427
452
}
428
453
429
- if ( isPageloadTransaction && optionalWindowDocument ) {
454
+ // Enable auto finish of the pageload span if users are not explicitly ending it
455
+ if ( isPageloadSpan && ! explicitPageloadEnd && optionalWindowDocument ) {
430
456
optionalWindowDocument . addEventListener ( 'readystatechange' , ( ) => {
431
457
emitFinish ( ) ;
432
458
} ) ;
@@ -574,6 +600,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
574
600
} ) ;
575
601
} ) ;
576
602
} ,
603
+
577
604
afterAllSetup ( client ) {
578
605
let startingUrl : string | undefined = getLocationHref ( ) ;
579
606
@@ -654,6 +681,13 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
654
681
onRequestSpanStart,
655
682
} ) ;
656
683
} ,
684
+
685
+ endPageloadSpan ( ) {
686
+ if ( _pageloadSpan && explicitPageloadEnd ) {
687
+ _pageloadSpan . end ( ) ;
688
+ _pageloadSpan = undefined ;
689
+ }
690
+ } ,
657
691
} ;
658
692
} ) satisfies IntegrationFn ;
659
693
@@ -723,6 +757,25 @@ export function getMetaContent(metaName: string): string | undefined {
723
757
return metaTag ?. getAttribute ( 'content' ) || undefined ;
724
758
}
725
759
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
+
726
779
/** Start listener for interaction transactions */
727
780
function registerInteractionListener (
728
781
client : Client ,
0 commit comments