1+ /* eslint-disable @typescript-eslint/no-explicit-any */
12import { SpanContext } from '@sentry/types' ;
23import { getGlobalObject , logger } from '@sentry/utils' ;
34
45import { Span } from '../span' ;
56import { Transaction } from '../transaction' ;
6-
77import { msToSec } from './utils' ;
88
99const global = getGlobalObject < Window > ( ) ;
@@ -14,71 +14,6 @@ export class MetricsInstrumentation {
1414
1515 private _performanceCursor : number = 0 ;
1616
17- private _forceLCP = ( ) => {
18- /* No-op, replaced later if LCP API is available. */
19- return ;
20- } ;
21-
22- /** Starts tracking the Largest Contentful Paint on the current page. */
23- private _trackLCP ( ) : void {
24- // Based on reference implementation from https://web.dev/lcp/#measure-lcp-in-javascript.
25- // Use a try/catch instead of feature detecting `largest-contentful-paint`
26- // support, since some browsers throw when using the new `type` option.
27- // https://bugs.webkit.org/show_bug.cgi?id=209216
28- try {
29- // Keep track of whether (and when) the page was first hidden, see:
30- // https://github.com/w3c/page-visibility/issues/29
31- // NOTE: ideally this check would be performed in the document <head>
32- // to avoid cases where the visibility state changes before this code runs.
33- let firstHiddenTime = document . visibilityState === 'hidden' ? 0 : Infinity ;
34- document . addEventListener (
35- 'visibilitychange' ,
36- event => {
37- firstHiddenTime = Math . min ( firstHiddenTime , event . timeStamp ) ;
38- } ,
39- { once : true } ,
40- ) ;
41-
42- const updateLCP = ( entry : PerformanceEntry ) => {
43- // Only include an LCP entry if the page wasn't hidden prior to
44- // the entry being dispatched. This typically happens when a page is
45- // loaded in a background tab.
46- if ( entry . startTime < firstHiddenTime ) {
47- // NOTE: the `startTime` value is a getter that returns the entry's
48- // `renderTime` value, if available, or its `loadTime` value otherwise.
49- // The `renderTime` value may not be available if the element is an image
50- // that's loaded cross-origin without the `Timing-Allow-Origin` header.
51- this . _lcp = {
52- // @ts -ignore
53- ...( entry . id && { elementId : entry . id } ) ,
54- // @ts -ignore
55- ...( entry . size && { elementSize : entry . size } ) ,
56- value : entry . startTime ,
57- } ;
58- }
59- } ;
60-
61- // Create a PerformanceObserver that calls `updateLCP` for each entry.
62- const po = new PerformanceObserver ( entryList => {
63- entryList . getEntries ( ) . forEach ( updateLCP ) ;
64- } ) ;
65-
66- // Observe entries of type `largest-contentful-paint`, including buffered entries,
67- // i.e. entries that occurred before calling `observe()` below.
68- po . observe ( {
69- buffered : true ,
70- // @ts -ignore
71- type : 'largest-contentful-paint' ,
72- } ) ;
73-
74- this . _forceLCP = ( ) => {
75- po . takeRecords ( ) . forEach ( updateLCP ) ;
76- } ;
77- } catch ( e ) {
78- // Do nothing if the browser doesn't support this API.
79- }
80- }
81-
8217 public constructor ( ) {
8318 if ( global && global . performance ) {
8419 if ( global . performance . mark ) {
@@ -112,7 +47,7 @@ export class MetricsInstrumentation {
11247 let entryScriptSrc : string | undefined ;
11348
11449 if ( global . document ) {
115- // tslint: disable-next-line: prefer-for-of
50+ // eslint- disable-next-line @typescript-eslint/ prefer-for-of
11651 for ( let i = 0 ; i < document . scripts . length ; i ++ ) {
11752 // We go through all scripts on the page and look for 'data-entry'
11853 // We remember the name and measure the time between this script finished loading and
@@ -144,20 +79,22 @@ export class MetricsInstrumentation {
14479 break ;
14580 case 'mark' :
14681 case 'paint' :
147- case 'measure' :
82+ case 'measure' : {
14883 const startTimestamp = addMeasureSpans ( transaction , entry , startTime , duration , timeOrigin ) ;
14984 if ( tracingInitMarkStartTime === undefined && entry . name === 'sentry-tracing-init' ) {
15085 tracingInitMarkStartTime = startTimestamp ;
15186 }
15287 break ;
153- case 'resource' :
88+ }
89+ case 'resource' : {
15490 const resourceName = ( entry . name as string ) . replace ( window . location . origin , '' ) ;
15591 const endTimestamp = addResourceSpans ( transaction , entry , resourceName , startTime , duration , timeOrigin ) ;
15692 // We remember the entry script end time to calculate the difference to the first init mark
15793 if ( entryScriptStartTimestamp === undefined && ( entryScriptSrc || '' ) . indexOf ( resourceName ) > - 1 ) {
15894 entryScriptStartTimestamp = endTimestamp ;
15995 }
16096 break ;
97+ }
16198 default :
16299 // Ignore other entry types.
163100 }
@@ -174,6 +111,71 @@ export class MetricsInstrumentation {
174111
175112 this . _performanceCursor = Math . max ( performance . getEntries ( ) . length - 1 , 0 ) ;
176113 }
114+
115+ private _forceLCP : ( ) => void = ( ) => {
116+ /* No-op, replaced later if LCP API is available. */
117+ return ;
118+ } ;
119+
120+ /** Starts tracking the Largest Contentful Paint on the current page. */
121+ private _trackLCP ( ) : void {
122+ // Based on reference implementation from https://web.dev/lcp/#measure-lcp-in-javascript.
123+ // Use a try/catch instead of feature detecting `largest-contentful-paint`
124+ // support, since some browsers throw when using the new `type` option.
125+ // https://bugs.webkit.org/show_bug.cgi?id=209216
126+ try {
127+ // Keep track of whether (and when) the page was first hidden, see:
128+ // https://github.com/w3c/page-visibility/issues/29
129+ // NOTE: ideally this check would be performed in the document <head>
130+ // to avoid cases where the visibility state changes before this code runs.
131+ let firstHiddenTime = document . visibilityState === 'hidden' ? 0 : Infinity ;
132+ document . addEventListener (
133+ 'visibilitychange' ,
134+ event => {
135+ firstHiddenTime = Math . min ( firstHiddenTime , event . timeStamp ) ;
136+ } ,
137+ { once : true } ,
138+ ) ;
139+
140+ const updateLCP = ( entry : PerformanceEntry ) : void => {
141+ // Only include an LCP entry if the page wasn't hidden prior to
142+ // the entry being dispatched. This typically happens when a page is
143+ // loaded in a background tab.
144+ if ( entry . startTime < firstHiddenTime ) {
145+ // NOTE: the `startTime` value is a getter that returns the entry's
146+ // `renderTime` value, if available, or its `loadTime` value otherwise.
147+ // The `renderTime` value may not be available if the element is an image
148+ // that's loaded cross-origin without the `Timing-Allow-Origin` header.
149+ this . _lcp = {
150+ // @ts -ignore can't access id on entry
151+ ...( entry . id && { elementId : entry . id } ) ,
152+ // @ts -ignore can't access id on entry
153+ ...( entry . size && { elementSize : entry . size } ) ,
154+ value : entry . startTime ,
155+ } ;
156+ }
157+ } ;
158+
159+ // Create a PerformanceObserver that calls `updateLCP` for each entry.
160+ const po = new PerformanceObserver ( entryList => {
161+ entryList . getEntries ( ) . forEach ( updateLCP ) ;
162+ } ) ;
163+
164+ // Observe entries of type `largest-contentful-paint`, including buffered entries,
165+ // i.e. entries that occurred before calling `observe()` below.
166+ po . observe ( {
167+ buffered : true ,
168+ // @ts -ignore type does not exist on obj
169+ type : 'largest-contentful-paint' ,
170+ } ) ;
171+
172+ this . _forceLCP = ( ) => {
173+ po . takeRecords ( ) . forEach ( updateLCP ) ;
174+ } ;
175+ } catch ( e ) {
176+ // Do nothing if the browser doesn't support this API.
177+ }
178+ }
177179}
178180
179181/** Instrument navigation entries */
0 commit comments