Skip to content

Commit 9291435

Browse files
authored
Merge branch 'develop' into timfish/feat/disable-blocked-detection
2 parents 6980de1 + 9c7c6bf commit 9291435

File tree

8 files changed

+151
-156
lines changed

8 files changed

+151
-156
lines changed

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ sentryTest('captures a "GOOD" CLS vital with its source as a standalone span', a
6565
'sentry.exclusive_time': 0,
6666
'sentry.op': 'ui.webvital.cls',
6767
'sentry.origin': 'auto.http.browser.cls',
68+
'sentry.report_event': 'pagehide',
6869
transaction: expect.stringContaining('index.html'),
6970
'user_agent.original': expect.stringContaining('Chrome'),
7071
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
@@ -134,6 +135,7 @@ sentryTest('captures a "MEH" CLS vital with its source as a standalone span', as
134135
'sentry.exclusive_time': 0,
135136
'sentry.op': 'ui.webvital.cls',
136137
'sentry.origin': 'auto.http.browser.cls',
138+
'sentry.report_event': 'pagehide',
137139
transaction: expect.stringContaining('index.html'),
138140
'user_agent.original': expect.stringContaining('Chrome'),
139141
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
@@ -201,6 +203,7 @@ sentryTest('captures a "POOR" CLS vital with its source as a standalone span.',
201203
'sentry.exclusive_time': 0,
202204
'sentry.op': 'ui.webvital.cls',
203205
'sentry.origin': 'auto.http.browser.cls',
206+
'sentry.report_event': 'pagehide',
204207
transaction: expect.stringContaining('index.html'),
205208
'user_agent.original': expect.stringContaining('Chrome'),
206209
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
@@ -269,6 +272,7 @@ sentryTest(
269272
'sentry.exclusive_time': 0,
270273
'sentry.op': 'ui.webvital.cls',
271274
'sentry.origin': 'auto.http.browser.cls',
275+
'sentry.report_event': 'pagehide',
272276
transaction: expect.stringContaining('index.html'),
273277
'user_agent.original': expect.stringContaining('Chrome'),
274278
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
@@ -342,6 +346,8 @@ sentryTest(
342346
// Ensure the CLS span is connected to the pageload span and trace
343347
expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadSpanId);
344348
expect(spanEnvelopeItem.trace_id).toEqual(pageloadTraceId);
349+
350+
expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('pagehide');
345351
},
346352
);
347353

@@ -374,6 +380,8 @@ sentryTest('sends CLS of the initial page when soft-navigating to a new page', a
374380
expect(spanEnvelopeItem.measurements?.cls?.value).toBeLessThan(0.15);
375381
expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadEventData.contexts?.trace?.span_id);
376382
expect(spanEnvelopeItem.trace_id).toEqual(pageloadTraceId);
383+
384+
expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation');
377385
});
378386

379387
sentryTest("doesn't send further CLS after the first navigation", async ({ getLocalTestUrl, page }) => {
@@ -398,6 +406,7 @@ sentryTest("doesn't send further CLS after the first navigation", async ({ getLo
398406
const spanEnvelope = (await spanEnvelopePromise)[0];
399407
const spanEnvelopeItem = spanEnvelope[1][0][1];
400408
expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0);
409+
expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation');
401410

402411
getMultipleSentryEnvelopeRequests<SpanEnvelope>(page, 1, { envelopeType: 'span' }, () => {
403412
throw new Error('Unexpected span - This should not happen!');
@@ -442,6 +451,7 @@ sentryTest("doesn't send further CLS after the first page hide", async ({ getLoc
442451
const spanEnvelope = (await spanEnvelopePromise)[0];
443452
const spanEnvelopeItem = spanEnvelope[1][0][1];
444453
expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0);
454+
expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('pagehide');
445455

446456
getMultipleSentryEnvelopeRequests<SpanEnvelope>(page, 1, { envelopeType: 'span' }, () => {
447457
throw new Error('Unexpected span - This should not happen!');

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/test.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ sentryTest('captures LCP vital as a standalone span', async ({ getLocalTestUrl,
6464
'sentry.exclusive_time': 0,
6565
'sentry.op': 'ui.webvital.lcp',
6666
'sentry.origin': 'auto.http.browser.lcp',
67+
'sentry.report_event': 'pagehide',
6768
transaction: expect.stringContaining('index.html'),
6869
'user_agent.original': expect.stringContaining('Chrome'),
6970
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
@@ -181,6 +182,7 @@ sentryTest('sends LCP of the initial page when soft-navigating to a new page', a
181182

182183
expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0);
183184
expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadEventData.contexts?.trace?.span_id);
185+
expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation');
184186
expect(spanEnvelopeItem.trace_id).toBe(pageloadEventData.contexts?.trace?.trace_id);
185187
});
186188

@@ -194,10 +196,10 @@ sentryTest("doesn't send further LCP after the first navigation", async ({ getLo
194196

195197
const url = await getLocalTestUrl({ testDir: __dirname });
196198

197-
const eventData = await getFirstSentryEnvelopeRequest<SentryEvent>(page, url);
199+
const pageloadEventData = await getFirstSentryEnvelopeRequest<SentryEvent>(page, url);
198200

199-
expect(eventData.type).toBe('transaction');
200-
expect(eventData.contexts?.trace?.op).toBe('pageload');
201+
expect(pageloadEventData.type).toBe('transaction');
202+
expect(pageloadEventData.contexts?.trace?.op).toBe('pageload');
201203

202204
const spanEnvelopePromise = getMultipleSentryEnvelopeRequests<SpanEnvelope>(
203205
page,
@@ -214,6 +216,8 @@ sentryTest("doesn't send further LCP after the first navigation", async ({ getLo
214216
const spanEnvelope = (await spanEnvelopePromise)[0];
215217
const spanEnvelopeItem = spanEnvelope[1][0][1];
216218
expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0);
219+
expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation');
220+
expect(spanEnvelopeItem.trace_id).toBe(pageloadEventData.contexts?.trace?.trace_id);
217221

218222
getMultipleSentryEnvelopeRequests<SpanEnvelope>(page, 1, { envelopeType: 'span' }, () => {
219223
throw new Error('Unexpected span - This should not happen!');
@@ -246,10 +250,10 @@ sentryTest("doesn't send further LCP after the first page hide", async ({ getLoc
246250

247251
const url = await getLocalTestUrl({ testDir: __dirname });
248252

249-
const eventData = await getFirstSentryEnvelopeRequest<SentryEvent>(page, url);
253+
const pageloadEventData = await getFirstSentryEnvelopeRequest<SentryEvent>(page, url);
250254

251-
expect(eventData.type).toBe('transaction');
252-
expect(eventData.contexts?.trace?.op).toBe('pageload');
255+
expect(pageloadEventData.type).toBe('transaction');
256+
expect(pageloadEventData.contexts?.trace?.op).toBe('pageload');
253257

254258
const spanEnvelopePromise = getMultipleSentryEnvelopeRequests<SpanEnvelope>(
255259
page,
@@ -266,6 +270,8 @@ sentryTest("doesn't send further LCP after the first page hide", async ({ getLoc
266270
const spanEnvelope = (await spanEnvelopePromise)[0];
267271
const spanEnvelopeItem = spanEnvelope[1][0][1];
268272
expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0);
273+
expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('pagehide');
274+
expect(spanEnvelopeItem.trace_id).toBe(pageloadEventData.contexts?.trace?.trace_id);
269275

270276
getMultipleSentryEnvelopeRequests<SpanEnvelope>(page, 1, { envelopeType: 'span' }, () => {
271277
throw new Error('Unexpected span - This should not happen!');
Lines changed: 14 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
import type { SpanAttributes } from '@sentry/core';
22
import {
33
browserPerformanceTimeOrigin,
4-
getActiveSpan,
5-
getClient,
64
getCurrentScope,
7-
getRootSpan,
85
htmlTreeAsString,
96
logger,
107
SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME,
118
SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT,
129
SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE,
1310
SEMANTIC_ATTRIBUTE_SENTRY_OP,
1411
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
15-
spanToJSON,
1612
} from '@sentry/core';
1713
import { DEBUG_BUILD } from '../debug-build';
1814
import { addClsInstrumentationHandler } from './instrument';
19-
import { msToSec, startStandaloneWebVitalSpan } from './utils';
20-
import { onHidden } from './web-vitals/lib/onHidden';
15+
import type { WebVitalReportEvent } from './utils';
16+
import { listenForWebVitalReportEvents, msToSec, startStandaloneWebVitalSpan, supportsWebVital } from './utils';
2117

2218
/**
2319
* Starts tracking the Cumulative Layout Shift on the current page and collects the value once
@@ -31,24 +27,11 @@ import { onHidden } from './web-vitals/lib/onHidden';
3127
export function trackClsAsStandaloneSpan(): void {
3228
let standaloneCLsValue = 0;
3329
let standaloneClsEntry: LayoutShift | undefined;
34-
let pageloadSpanId: string | undefined;
3530

36-
if (!supportsLayoutShift()) {
31+
if (!supportsWebVital('layout-shift')) {
3732
return;
3833
}
3934

40-
let sentSpan = false;
41-
function _collectClsOnce() {
42-
if (sentSpan) {
43-
return;
44-
}
45-
sentSpan = true;
46-
if (pageloadSpanId) {
47-
sendStandaloneClsSpan(standaloneCLsValue, standaloneClsEntry, pageloadSpanId);
48-
}
49-
cleanupClsHandler();
50-
}
51-
5235
const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {
5336
const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;
5437
if (!entry) {
@@ -58,40 +41,18 @@ export function trackClsAsStandaloneSpan(): void {
5841
standaloneClsEntry = entry;
5942
}, true);
6043

61-
onHidden(() => {
62-
_collectClsOnce();
44+
listenForWebVitalReportEvents((reportEvent, pageloadSpanId) => {
45+
sendStandaloneClsSpan(standaloneCLsValue, standaloneClsEntry, pageloadSpanId, reportEvent);
46+
cleanupClsHandler();
6347
});
64-
65-
// Since the call chain of this function is synchronous and evaluates before the SDK client is created,
66-
// we need to wait with subscribing to a client hook until the client is created. Therefore, we defer
67-
// to the next tick after the SDK setup.
68-
setTimeout(() => {
69-
const client = getClient();
70-
71-
if (!client) {
72-
return;
73-
}
74-
75-
const unsubscribeStartNavigation = client.on('beforeStartNavigationSpan', (_, options) => {
76-
// we only want to collect LCP if we actually navigate. Redirects should be ignored.
77-
if (!options?.isRedirect) {
78-
_collectClsOnce();
79-
unsubscribeStartNavigation?.();
80-
}
81-
});
82-
83-
const activeSpan = getActiveSpan();
84-
if (activeSpan) {
85-
const rootSpan = getRootSpan(activeSpan);
86-
const spanJSON = spanToJSON(rootSpan);
87-
if (spanJSON.op === 'pageload') {
88-
pageloadSpanId = rootSpan.spanContext().spanId;
89-
}
90-
}
91-
}, 0);
9248
}
9349

94-
function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined, pageloadSpanId: string) {
50+
function sendStandaloneClsSpan(
51+
clsValue: number,
52+
entry: LayoutShift | undefined,
53+
pageloadSpanId: string,
54+
reportEvent: WebVitalReportEvent,
55+
) {
9556
DEBUG_BUILD && logger.log(`Sending CLS span (${clsValue})`);
9657

9758
const startTime = msToSec((browserPerformanceTimeOrigin() || 0) + (entry?.startTime || 0));
@@ -105,6 +66,8 @@ function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined,
10566
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry?.duration || 0,
10667
// attach the pageload span id to the CLS span so that we can link them in the UI
10768
'sentry.pageload.span_id': pageloadSpanId,
69+
// describes what triggered the web vital to be reported
70+
'sentry.report_event': reportEvent,
10871
};
10972

11073
// Add CLS sources as span attributes to help with debugging layout shifts
@@ -133,11 +96,3 @@ function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined,
13396
span.end(startTime);
13497
}
13598
}
136-
137-
function supportsLayoutShift(): boolean {
138-
try {
139-
return PerformanceObserver.supportedEntryTypes.includes('layout-shift');
140-
} catch {
141-
return false;
142-
}
143-
}
Lines changed: 9 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
import type { SpanAttributes } from '@sentry/core';
22
import {
33
browserPerformanceTimeOrigin,
4-
getActiveSpan,
5-
getClient,
64
getCurrentScope,
7-
getRootSpan,
85
htmlTreeAsString,
96
logger,
107
SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME,
118
SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT,
129
SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE,
1310
SEMANTIC_ATTRIBUTE_SENTRY_OP,
1411
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
15-
spanToJSON,
1612
} from '@sentry/core';
1713
import { DEBUG_BUILD } from '../debug-build';
1814
import { addLcpInstrumentationHandler } from './instrument';
19-
import { msToSec, startStandaloneWebVitalSpan } from './utils';
20-
import { onHidden } from './web-vitals/lib/onHidden';
15+
import type { WebVitalReportEvent } from './utils';
16+
import { listenForWebVitalReportEvents, msToSec, startStandaloneWebVitalSpan, supportsWebVital } from './utils';
2117

2218
/**
2319
* Starts tracking the Largest Contentful Paint on the current page and collects the value once
@@ -31,24 +27,11 @@ import { onHidden } from './web-vitals/lib/onHidden';
3127
export function trackLcpAsStandaloneSpan(): void {
3228
let standaloneLcpValue = 0;
3329
let standaloneLcpEntry: LargestContentfulPaint | undefined;
34-
let pageloadSpanId: string | undefined;
3530

36-
if (!supportsLargestContentfulPaint()) {
31+
if (!supportsWebVital('largest-contentful-paint')) {
3732
return;
3833
}
3934

40-
let sentSpan = false;
41-
function _collectLcpOnce() {
42-
if (sentSpan) {
43-
return;
44-
}
45-
sentSpan = true;
46-
if (pageloadSpanId) {
47-
_sendStandaloneLcpSpan(standaloneLcpValue, standaloneLcpEntry, pageloadSpanId);
48-
}
49-
cleanupLcpHandler();
50-
}
51-
5235
const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {
5336
const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;
5437
if (!entry) {
@@ -58,37 +41,10 @@ export function trackLcpAsStandaloneSpan(): void {
5841
standaloneLcpEntry = entry;
5942
}, true);
6043

61-
onHidden(() => {
62-
_collectLcpOnce();
44+
listenForWebVitalReportEvents((reportEvent, pageloadSpanId) => {
45+
_sendStandaloneLcpSpan(standaloneLcpValue, standaloneLcpEntry, pageloadSpanId, reportEvent);
46+
cleanupLcpHandler();
6347
});
64-
65-
// Since the call chain of this function is synchronous and evaluates before the SDK client is created,
66-
// we need to wait with subscribing to a client hook until the client is created. Therefore, we defer
67-
// to the next tick after the SDK setup.
68-
setTimeout(() => {
69-
const client = getClient();
70-
71-
if (!client) {
72-
return;
73-
}
74-
75-
const unsubscribeStartNavigation = client.on('beforeStartNavigationSpan', (_, options) => {
76-
// we only want to collect LCP if we actually navigate. Redirects should be ignored.
77-
if (!options?.isRedirect) {
78-
_collectLcpOnce();
79-
unsubscribeStartNavigation?.();
80-
}
81-
});
82-
83-
const activeSpan = getActiveSpan();
84-
if (activeSpan) {
85-
const rootSpan = getRootSpan(activeSpan);
86-
const spanJSON = spanToJSON(rootSpan);
87-
if (spanJSON.op === 'pageload') {
88-
pageloadSpanId = rootSpan.spanContext().spanId;
89-
}
90-
}
91-
}, 0);
9248
}
9349

9450
/**
@@ -98,6 +54,7 @@ export function _sendStandaloneLcpSpan(
9854
lcpValue: number,
9955
entry: LargestContentfulPaint | undefined,
10056
pageloadSpanId: string,
57+
reportEvent: WebVitalReportEvent,
10158
) {
10259
DEBUG_BUILD && logger.log(`Sending LCP span (${lcpValue})`);
10360

@@ -112,6 +69,8 @@ export function _sendStandaloneLcpSpan(
11269
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: 0, // LCP is a point-in-time metric
11370
// attach the pageload span id to the LCP span so that we can link them in the UI
11471
'sentry.pageload.span_id': pageloadSpanId,
72+
// describes what triggered the web vital to be reported
73+
'sentry.report_event': reportEvent,
11574
};
11675

11776
if (entry) {
@@ -149,11 +108,3 @@ export function _sendStandaloneLcpSpan(
149108
span.end(startTime);
150109
}
151110
}
152-
153-
function supportsLargestContentfulPaint(): boolean {
154-
try {
155-
return PerformanceObserver.supportedEntryTypes.includes('largest-contentful-paint');
156-
} catch {
157-
return false;
158-
}
159-
}

0 commit comments

Comments
 (0)