2
2
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN , getActiveSpan , startInactiveSpan } from '@sentry/core' ;
3
3
import { setMeasurement } from '@sentry/core' ;
4
4
import type { Measurements , Span , SpanAttributes , StartSpanOptions } from '@sentry/types' ;
5
- import { browserPerformanceTimeOrigin , getComponentName , htmlTreeAsString , logger , parseUrl } from '@sentry/utils' ;
5
+ import {
6
+ browserPerformanceTimeOrigin ,
7
+ consoleSandbox ,
8
+ getComponentName ,
9
+ htmlTreeAsString ,
10
+ logger ,
11
+ parseUrl ,
12
+ } from '@sentry/utils' ;
6
13
7
14
import { spanToJSON } from '@sentry/core' ;
8
15
import { DEBUG_BUILD } from '../debug-build' ;
@@ -215,6 +222,8 @@ export { startTrackingINP, registerInpInteractionListener } from './inp';
215
222
216
223
/** Starts tracking the Cumulative Layout Shift on the current page. */
217
224
function _trackCLS ( ) : ( ) => void {
225
+ trySetZeroClsValue ( ) ;
226
+
218
227
return addClsInstrumentationHandler ( ( { metric } ) => {
219
228
const entry = metric . entries [ metric . entries . length - 1 ] ;
220
229
if ( ! entry ) {
@@ -227,6 +236,32 @@ function _trackCLS(): () => void {
227
236
} , true ) ;
228
237
}
229
238
239
+ /**
240
+ * Why does this function exist? A very good question!
241
+ *
242
+ * The `PerformanceObserver` emits `LayoutShift` entries whenever a layout shift occurs.
243
+ * If none occurs (which is great!), the observer will never emit any entries. Makes sense so far!
244
+ *
245
+ * This is problematic for the Sentry product though. We can't differentiate between a CLS of 0 and not having received
246
+ * CLS data at all. So in both cases, we'd show users that the CLS score simply is not available. When in fact, it can
247
+ * be 0, which is a very good score. This function is a workaround to emit a CLS of 0 right at the start of
248
+ * listening to CLS events. This way, we can differentiate between a CLS of 0 and no CLS at all. If a layout shift
249
+ * occurs later, the real CLS value will be emitted and the 0 value will be ignored.
250
+ * We also only send this artificial 0 value if the browser supports reporting the `layout-shift` entry type.
251
+ */
252
+ function trySetZeroClsValue ( ) : void {
253
+ try {
254
+ const canReportLayoutShift = PerformanceObserver . supportedEntryTypes . includes ( 'layout-shift' ) ;
255
+ if ( canReportLayoutShift ) {
256
+ DEBUG_BUILD && logger . log ( '[Measurements] Adding CLS 0' ) ;
257
+ _measurements [ 'cls' ] = { value : 0 , unit : '' } ;
258
+ // TODO: Do we have to set _clsEntry here as well? If so, what attribution should we give it?
259
+ }
260
+ } catch {
261
+ // catching and ignoring access errors for bundle size reduction
262
+ }
263
+ }
264
+
230
265
/** Starts tracking the Largest Contentful Paint on the current page. */
231
266
function _trackLCP ( ) : ( ) => void {
232
267
return addLcpInstrumentationHandler ( ( { metric } ) => {
0 commit comments