22import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN , getActiveSpan , startInactiveSpan } from '@sentry/core' ;
33import { setMeasurement } from '@sentry/core' ;
44import 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' ;
613
714import { spanToJSON } from '@sentry/core' ;
815import { DEBUG_BUILD } from '../debug-build' ;
@@ -215,6 +222,8 @@ export { startTrackingINP, registerInpInteractionListener } from './inp';
215222
216223/** Starts tracking the Cumulative Layout Shift on the current page. */
217224function _trackCLS ( ) : ( ) => void {
225+ trySetZeroClsValue ( ) ;
226+
218227 return addClsInstrumentationHandler ( ( { metric } ) => {
219228 const entry = metric . entries [ metric . entries . length - 1 ] ;
220229 if ( ! entry ) {
@@ -227,6 +236,32 @@ function _trackCLS(): () => void {
227236 } , true ) ;
228237}
229238
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+
230265/** Starts tracking the Largest Contentful Paint on the current page. */
231266function _trackLCP ( ) : ( ) => void {
232267 return addLcpInstrumentationHandler ( ( { metric } ) => {
0 commit comments