@@ -14,73 +14,6 @@ import { Span as SpanClass } from '../span';
14
14
import { SpanStatus } from '../spanstatus' ;
15
15
import { Transaction } from '../transaction' ;
16
16
17
- /** Holds the latest LargestContentfulPaint value (it changes during page load). */
18
- let lcp : { [ key : string ] : any } ;
19
-
20
- /** Force any pending LargestContentfulPaint records to be dispatched. */
21
- let forceLCP = ( ) => {
22
- /* No-op, replaced later if LCP API is available. */
23
- } ;
24
-
25
- // Based on reference implementation from https://web.dev/lcp/#measure-lcp-in-javascript.
26
- {
27
- // Use a try/catch instead of feature detecting `largest-contentful-paint`
28
- // support, since some browsers throw when using the new `type` option.
29
- // https://bugs.webkit.org/show_bug.cgi?id=209216
30
- try {
31
- // Keep track of whether (and when) the page was first hidden, see:
32
- // https://github.com/w3c/page-visibility/issues/29
33
- // NOTE: ideally this check would be performed in the document <head>
34
- // to avoid cases where the visibility state changes before this code runs.
35
- let firstHiddenTime = document . visibilityState === 'hidden' ? 0 : Infinity ;
36
- document . addEventListener (
37
- 'visibilitychange' ,
38
- event => {
39
- firstHiddenTime = Math . min ( firstHiddenTime , event . timeStamp ) ;
40
- } ,
41
- { once : true } ,
42
- ) ;
43
-
44
- const updateLCP = ( entry : PerformanceEntry ) => {
45
- // Only include an LCP entry if the page wasn't hidden prior to
46
- // the entry being dispatched. This typically happens when a page is
47
- // loaded in a background tab.
48
- if ( entry . startTime < firstHiddenTime ) {
49
- // NOTE: the `startTime` value is a getter that returns the entry's
50
- // `renderTime` value, if available, or its `loadTime` value otherwise.
51
- // The `renderTime` value may not be available if the element is an image
52
- // that's loaded cross-origin without the `Timing-Allow-Origin` header.
53
- lcp = {
54
- // @ts -ignore
55
- elementId : entry . id ,
56
- // @ts -ignore
57
- elementSize : entry . size ,
58
- largestContentfulPaint : entry . startTime ,
59
- } ;
60
- }
61
- } ;
62
-
63
- // Create a PerformanceObserver that calls `updateLCP` for each entry.
64
- const po = new PerformanceObserver ( entryList => {
65
- entryList . getEntries ( ) . forEach ( updateLCP ) ;
66
- } ) ;
67
-
68
- // Observe entries of type `largest-contentful-paint`, including buffered entries,
69
- // i.e. entries that occurred before calling `observe()` below.
70
- po . observe ( {
71
- buffered : true ,
72
- // @ts -ignore
73
- type : 'largest-contentful-paint' ,
74
- } ) ;
75
-
76
- forceLCP = ( ) => {
77
- po . takeRecords ( ) . forEach ( updateLCP ) ;
78
- } ;
79
- } catch ( e ) {
80
- // Do nothing if the browser doesn't support this API.
81
- }
82
- }
83
-
84
17
/**
85
18
* Options for Tracing integration
86
19
*/
@@ -222,6 +155,14 @@ export class Tracing implements Integration {
222
155
223
156
private static _heartbeatCounter : number = 0 ;
224
157
158
+ /** Holds the latest LargestContentfulPaint value (it changes during page load). */
159
+ private static _lcp : { [ key : string ] : any } ;
160
+
161
+ /** Force any pending LargestContentfulPaint records to be dispatched. */
162
+ private static _forceLCP = ( ) => {
163
+ /* No-op, replaced later if LCP API is available. */
164
+ } ;
165
+
225
166
/**
226
167
* Constructor for Tracing
227
168
*
@@ -230,6 +171,7 @@ export class Tracing implements Integration {
230
171
public constructor ( _options ?: Partial < TracingOptions > ) {
231
172
if ( global . performance ) {
232
173
global . performance . mark ( 'sentry-tracing-init' ) ;
174
+ Tracing . _trackLCP ( ) ;
233
175
}
234
176
const defaults = {
235
177
debug : {
@@ -575,10 +517,10 @@ export class Tracing implements Integration {
575
517
// FIXME: depending on the 'op' directly is brittle.
576
518
if ( transactionSpan . op === 'pageload' ) {
577
519
// Force any pending records to be dispatched.
578
- forceLCP ( ) ;
579
- if ( lcp ) {
520
+ Tracing . _forceLCP ( ) ;
521
+ if ( Tracing . _lcp ) {
580
522
// Set the last observed LCP score.
581
- transactionSpan . setData ( '_sentry_extra_metrics' , JSON . stringify ( { lcp } ) ) ;
523
+ transactionSpan . setData ( '_sentry_extra_metrics' , JSON . stringify ( { lcp : Tracing . _lcp } ) ) ;
582
524
}
583
525
}
584
526
@@ -706,6 +648,69 @@ export class Tracing implements Integration {
706
648
// tslint:enable: no-unsafe-any
707
649
}
708
650
651
+ /**
652
+ * Starts tracking the Largest Contentful Paint on the current page.
653
+ */
654
+ private static _trackLCP ( ) : void {
655
+ // Based on reference implementation from https://web.dev/lcp/#measure-lcp-in-javascript.
656
+
657
+ // Use a try/catch instead of feature detecting `largest-contentful-paint`
658
+ // support, since some browsers throw when using the new `type` option.
659
+ // https://bugs.webkit.org/show_bug.cgi?id=209216
660
+ try {
661
+ // Keep track of whether (and when) the page was first hidden, see:
662
+ // https://github.com/w3c/page-visibility/issues/29
663
+ // NOTE: ideally this check would be performed in the document <head>
664
+ // to avoid cases where the visibility state changes before this code runs.
665
+ let firstHiddenTime = document . visibilityState === 'hidden' ? 0 : Infinity ;
666
+ document . addEventListener (
667
+ 'visibilitychange' ,
668
+ event => {
669
+ firstHiddenTime = Math . min ( firstHiddenTime , event . timeStamp ) ;
670
+ } ,
671
+ { once : true } ,
672
+ ) ;
673
+
674
+ const updateLCP = ( entry : PerformanceEntry ) => {
675
+ // Only include an LCP entry if the page wasn't hidden prior to
676
+ // the entry being dispatched. This typically happens when a page is
677
+ // loaded in a background tab.
678
+ if ( entry . startTime < firstHiddenTime ) {
679
+ // NOTE: the `startTime` value is a getter that returns the entry's
680
+ // `renderTime` value, if available, or its `loadTime` value otherwise.
681
+ // The `renderTime` value may not be available if the element is an image
682
+ // that's loaded cross-origin without the `Timing-Allow-Origin` header.
683
+ Tracing . _lcp = {
684
+ // @ts -ignore
685
+ elementId : entry . id ,
686
+ // @ts -ignore
687
+ elementSize : entry . size ,
688
+ largestContentfulPaint : entry . startTime ,
689
+ } ;
690
+ }
691
+ } ;
692
+
693
+ // Create a PerformanceObserver that calls `updateLCP` for each entry.
694
+ const po = new PerformanceObserver ( entryList => {
695
+ entryList . getEntries ( ) . forEach ( updateLCP ) ;
696
+ } ) ;
697
+
698
+ // Observe entries of type `largest-contentful-paint`, including buffered entries,
699
+ // i.e. entries that occurred before calling `observe()` below.
700
+ po . observe ( {
701
+ buffered : true ,
702
+ // @ts -ignore
703
+ type : 'largest-contentful-paint' ,
704
+ } ) ;
705
+
706
+ Tracing . _forceLCP = ( ) => {
707
+ po . takeRecords ( ) . forEach ( updateLCP ) ;
708
+ } ;
709
+ } catch ( e ) {
710
+ // Do nothing if the browser doesn't support this API.
711
+ }
712
+ }
713
+
709
714
/**
710
715
* Sets the status of the current active transaction (if there is one)
711
716
*/
0 commit comments