11/* eslint-disable max-lines */
22/* eslint-disable @typescript-eslint/no-explicit-any */
3- import { SpanContext } from '@sentry/types' ;
3+ import { Measurements , SpanContext } from '@sentry/types' ;
44import { browserPerformanceTimeOrigin , getGlobalObject , logger } from '@sentry/utils' ;
55
66import { Span } from '../span' ;
77import { Transaction } from '../transaction' ;
88import { msToSec } from '../utils' ;
9+ import { getFID } from './web-vitals/getFID' ;
10+ import { getLCP } from './web-vitals/getLCP' ;
911
1012const global = getGlobalObject < Window > ( ) ;
1113
1214/** Class tracking metrics */
1315export class MetricsInstrumentation {
14- private _lcp : Record < string , any > = { } ;
16+ private _measurements : Measurements = { } ;
1517
1618 private _performanceCursor : number = 0 ;
1719
@@ -22,6 +24,7 @@ export class MetricsInstrumentation {
2224 }
2325
2426 this . _trackLCP ( ) ;
27+ this . _trackFID ( ) ;
2528 }
2629 }
2730
@@ -34,16 +37,6 @@ export class MetricsInstrumentation {
3437
3538 logger . log ( '[Tracing] Adding & adjusting spans using Performance API' ) ;
3639
37- // TODO(fixme): depending on the 'op' directly is brittle.
38- if ( transaction . op === 'pageload' ) {
39- // Force any pending records to be dispatched.
40- this . _forceLCP ( ) ;
41- if ( this . _lcp ) {
42- // Set the last observed LCP score.
43- transaction . setData ( '_sentry_web_vitals' , { LCP : this . _lcp } ) ;
44- }
45- }
46-
4740 const timeOrigin = msToSec ( browserPerformanceTimeOrigin ) ;
4841 let entryScriptSrc : string | undefined ;
4942
@@ -85,6 +78,21 @@ export class MetricsInstrumentation {
8578 if ( tracingInitMarkStartTime === undefined && entry . name === 'sentry-tracing-init' ) {
8679 tracingInitMarkStartTime = startTimestamp ;
8780 }
81+
82+ // capture web vitals
83+
84+ if ( entry . name === 'first-paint' ) {
85+ logger . log ( '[Measurements] Adding FP' ) ;
86+ this . _measurements [ 'fp' ] = { value : entry . startTime } ;
87+ this . _measurements [ 'mark.fp' ] = { value : startTimestamp } ;
88+ }
89+
90+ if ( entry . name === 'first-contentful-paint' ) {
91+ logger . log ( '[Measurements] Adding FCP' ) ;
92+ this . _measurements [ 'fcp' ] = { value : entry . startTime } ;
93+ this . _measurements [ 'mark.fcp' ] = { value : startTimestamp } ;
94+ }
95+
8896 break ;
8997 }
9098 case 'resource' : {
@@ -111,73 +119,45 @@ export class MetricsInstrumentation {
111119 }
112120
113121 this . _performanceCursor = Math . max ( performance . getEntries ( ) . length - 1 , 0 ) ;
114- }
115122
116- private _forceLCP : ( ) => void = ( ) => {
117- /* No-op, replaced later if LCP API is available. */
118- return ;
119- } ;
123+ // Measurements are only available for pageload transactions
124+ if ( transaction . op === 'pageload' ) {
125+ transaction . setMeasurements ( this . _measurements ) ;
126+ }
127+ }
120128
121129 /** Starts tracking the Largest Contentful Paint on the current page. */
122130 private _trackLCP ( ) : void {
123- // Based on reference implementation from https://web.dev/lcp/#measure-lcp-in-javascript.
124- // Use a try/catch instead of feature detecting `largest-contentful-paint`
125- // support, since some browsers throw when using the new `type` option.
126- // https://bugs.webkit.org/show_bug.cgi?id=209216
127- try {
128- // Keep track of whether (and when) the page was first hidden, see:
129- // https://github.com/w3c/page-visibility/issues/29
130- // NOTE: ideally this check would be performed in the document <head>
131- // to avoid cases where the visibility state changes before this code runs.
132- let firstHiddenTime = document . visibilityState === 'hidden' ? 0 : Infinity ;
133- document . addEventListener (
134- 'visibilitychange' ,
135- event => {
136- firstHiddenTime = Math . min ( firstHiddenTime , event . timeStamp ) ;
137- } ,
138- { once : true } ,
139- ) ;
140-
141- const updateLCP = ( entry : PerformanceEntry ) : void => {
142- // Only include an LCP entry if the page wasn't hidden prior to
143- // the entry being dispatched. This typically happens when a page is
144- // loaded in a background tab.
145- if ( entry . startTime < firstHiddenTime ) {
146- // NOTE: the `startTime` value is a getter that returns the entry's
147- // `renderTime` value, if available, or its `loadTime` value otherwise.
148- // The `renderTime` value may not be available if the element is an image
149- // that's loaded cross-origin without the `Timing-Allow-Origin` header.
150- this . _lcp = {
151- // @ts -ignore can't access id on entry
152- ...( entry . id && { elementId : entry . id } ) ,
153- // @ts -ignore can't access id on entry
154- ...( entry . size && { elementSize : entry . size } ) ,
155- value : entry . startTime ,
156- } ;
157- }
158- } ;
131+ getLCP ( metric => {
132+ const entry = metric . entries . pop ( ) ;
159133
160- // Create a PerformanceObserver that calls `updateLCP` for each entry.
161- const po = new PerformanceObserver ( entryList => {
162- entryList . getEntries ( ) . forEach ( updateLCP ) ;
163- } ) ;
134+ if ( ! entry ) {
135+ return ;
136+ }
164137
165- // Observe entries of type `largest-contentful-paint`, including buffered entries,
166- // i.e. entries that occurred before calling `observe()` below.
167- po . observe ( {
168- buffered : true ,
169- // @ts -ignore type does not exist on obj
170- type : 'largest-contentful-paint' ,
171- } ) ;
138+ const timeOrigin = msToSec ( performance . timeOrigin ) ;
139+ const startTime = msToSec ( entry . startTime as number ) ;
140+ logger . log ( '[Measurements] Adding LCP' ) ;
141+ this . _measurements [ 'lcp' ] = { value : metric . value } ;
142+ this . _measurements [ 'mark.lcp' ] = { value : timeOrigin + startTime } ;
143+ } ) ;
144+ }
172145
173- this . _forceLCP = ( ) => {
174- if ( po . takeRecords ) {
175- po . takeRecords ( ) . forEach ( updateLCP ) ;
176- }
177- } ;
178- } catch ( e ) {
179- // Do nothing if the browser doesn't support this API.
180- }
146+ /** Starts tracking the First Input Delay on the current page. */
147+ private _trackFID ( ) : void {
148+ getFID ( metric => {
149+ const entry = metric . entries . pop ( ) ;
150+
151+ if ( ! entry ) {
152+ return ;
153+ }
154+
155+ const timeOrigin = msToSec ( performance . timeOrigin ) ;
156+ const startTime = msToSec ( entry . startTime as number ) ;
157+ logger . log ( '[Measurements] Adding FID' ) ;
158+ this . _measurements [ 'fid' ] = { value : metric . value } ;
159+ this . _measurements [ 'mark.fid' ] = { value : timeOrigin + startTime } ;
160+ } ) ;
181161 }
182162}
183163
0 commit comments