Skip to content

Commit 92026ca

Browse files
committed
ref: Move LCP tracking to static side of Tracing
This allows us to optionally disable LCP based on config later.
1 parent 8b32154 commit 92026ca

File tree

1 file changed

+75
-70
lines changed

1 file changed

+75
-70
lines changed

packages/apm/src/integrations/tracing.ts

Lines changed: 75 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -14,73 +14,6 @@ import { Span as SpanClass } from '../span';
1414
import { SpanStatus } from '../spanstatus';
1515
import { Transaction } from '../transaction';
1616

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-
8417
/**
8518
* Options for Tracing integration
8619
*/
@@ -222,6 +155,14 @@ export class Tracing implements Integration {
222155

223156
private static _heartbeatCounter: number = 0;
224157

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+
225166
/**
226167
* Constructor for Tracing
227168
*
@@ -230,6 +171,7 @@ export class Tracing implements Integration {
230171
public constructor(_options?: Partial<TracingOptions>) {
231172
if (global.performance) {
232173
global.performance.mark('sentry-tracing-init');
174+
Tracing._trackLCP();
233175
}
234176
const defaults = {
235177
debug: {
@@ -575,10 +517,10 @@ export class Tracing implements Integration {
575517
// FIXME: depending on the 'op' directly is brittle.
576518
if (transactionSpan.op === 'pageload') {
577519
// Force any pending records to be dispatched.
578-
forceLCP();
579-
if (lcp) {
520+
Tracing._forceLCP();
521+
if (Tracing._lcp) {
580522
// 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 }));
582524
}
583525
}
584526

@@ -706,6 +648,69 @@ export class Tracing implements Integration {
706648
// tslint:enable: no-unsafe-any
707649
}
708650

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+
709714
/**
710715
* Sets the status of the current active transaction (if there is one)
711716
*/

0 commit comments

Comments
 (0)