1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
import { SpanContext } from '@sentry/types' ;
2
3
import { getGlobalObject , logger } from '@sentry/utils' ;
3
4
4
5
import { Span } from '../span' ;
5
6
import { Transaction } from '../transaction' ;
6
-
7
7
import { msToSec } from './utils' ;
8
8
9
9
const global = getGlobalObject < Window > ( ) ;
@@ -14,71 +14,6 @@ export class MetricsInstrumentation {
14
14
15
15
private _performanceCursor : number = 0 ;
16
16
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
-
82
17
public constructor ( ) {
83
18
if ( global && global . performance ) {
84
19
if ( global . performance . mark ) {
@@ -112,7 +47,7 @@ export class MetricsInstrumentation {
112
47
let entryScriptSrc : string | undefined ;
113
48
114
49
if ( global . document ) {
115
- // tslint: disable-next-line: prefer-for-of
50
+ // eslint- disable-next-line @typescript-eslint/ prefer-for-of
116
51
for ( let i = 0 ; i < document . scripts . length ; i ++ ) {
117
52
// We go through all scripts on the page and look for 'data-entry'
118
53
// We remember the name and measure the time between this script finished loading and
@@ -144,20 +79,22 @@ export class MetricsInstrumentation {
144
79
break ;
145
80
case 'mark' :
146
81
case 'paint' :
147
- case 'measure' :
82
+ case 'measure' : {
148
83
const startTimestamp = addMeasureSpans ( transaction , entry , startTime , duration , timeOrigin ) ;
149
84
if ( tracingInitMarkStartTime === undefined && entry . name === 'sentry-tracing-init' ) {
150
85
tracingInitMarkStartTime = startTimestamp ;
151
86
}
152
87
break ;
153
- case 'resource' :
88
+ }
89
+ case 'resource' : {
154
90
const resourceName = ( entry . name as string ) . replace ( window . location . origin , '' ) ;
155
91
const endTimestamp = addResourceSpans ( transaction , entry , resourceName , startTime , duration , timeOrigin ) ;
156
92
// We remember the entry script end time to calculate the difference to the first init mark
157
93
if ( entryScriptStartTimestamp === undefined && ( entryScriptSrc || '' ) . indexOf ( resourceName ) > - 1 ) {
158
94
entryScriptStartTimestamp = endTimestamp ;
159
95
}
160
96
break ;
97
+ }
161
98
default :
162
99
// Ignore other entry types.
163
100
}
@@ -174,6 +111,71 @@ export class MetricsInstrumentation {
174
111
175
112
this . _performanceCursor = Math . max ( performance . getEntries ( ) . length - 1 , 0 ) ;
176
113
}
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
+ }
177
179
}
178
180
179
181
/** Instrument navigation entries */
0 commit comments