1- import type { SpanAttributes } from '@sentry/core' ;
21import {
32 browserPerformanceTimeOrigin ,
43 getActiveSpan ,
@@ -9,13 +8,12 @@ import {
98 SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
109 spanToJSON ,
1110 startSpan ,
12- timestampInSeconds ,
1311} from '@sentry/core' ;
1412import { addPerformanceInstrumentationHandler } from './instrument' ;
1513import { getBrowserPerformanceAPI , msToSec } from './utils' ;
1614
1715// ElementTiming interface based on the W3C spec
18- interface PerformanceElementTiming extends PerformanceEntry {
16+ export interface PerformanceElementTiming extends PerformanceEntry {
1917 renderTime : number ;
2018 loadTime : number ;
2119 intersectionRect : DOMRectReadOnly ;
@@ -57,71 +55,50 @@ export const _onElementTiming = ({ entries }: { entries: PerformanceEntry[] }):
5755 }
5856
5957 entries . forEach ( entry => {
60- const elementEntry = entry as PerformanceElementTiming ;
58+ const { naturalWidth, naturalHeight, url, identifier, name, renderTime, loadTime, startTime, id, element } =
59+ entry as PerformanceElementTiming ;
6160
62- // Skip entries without identifier (elementtiming attribute)
63- if ( ! elementEntry . identifier ) {
61+ // Skip:
62+ // - entries without identifier (elementtiming attribute)
63+ // - entries without startTime (e.g. 3rd party Image nodes w/o Timing-Allow-Origin header returned instantly from cache)
64+ if ( ! identifier || ! startTime ) {
6465 return ;
6566 }
6667
67- // `name` contains the type of the element paint. Can be `'image-paint'` or `'text-paint'`.
68- // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceElementTiming#instance_properties
69- const paintType = elementEntry . name as 'image-paint' | 'text-paint' | undefined ;
70-
71- const renderTime = elementEntry . renderTime ;
72- const loadTime = elementEntry . loadTime ;
73-
74- // starting the span at:
75- // - `loadTime` if available (should be available for all "image-paint" entries, 0 otherwise)
76- // - `renderTime` if available (available for all entries, except 3rd party images, but these should be covered by `loadTime`, 0 otherwise)
77- // - `timestampInSeconds()` as a safeguard
78- // see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceElementTiming/renderTime#cross-origin_image_render_time
79- const [ spanStartTime , spanStartTimeSource ] = loadTime
80- ? [ msToSec ( timeOrigin + loadTime ) , 'load-time' ]
81- : renderTime
82- ? [ msToSec ( timeOrigin + renderTime ) , 'render-time' ]
83- : [ timestampInSeconds ( ) , 'entry-emission' ] ;
84-
85- const duration =
86- paintType === 'image-paint'
87- ? // for image paints, we can acually get a duration because image-paint entries also have a `loadTime`
88- // and `renderTime`. `loadTime` is the time when the image finished loading and `renderTime` is the
89- // time when the image finished rendering.
90- msToSec ( Math . max ( 0 , ( renderTime ?? 0 ) - ( loadTime ?? 0 ) ) )
91- : // for `'text-paint'` entries, we can't get a duration because the `loadTime` is always zero.
92- 0 ;
93-
94- const attributes : SpanAttributes = {
95- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.ui.browser.elementtiming' ,
96- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'ui.elementtiming' ,
97- // name must be user-entered, so we can assume low cardinality
98- [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'component' ,
99- // recording the source of the span start time, as it varies depending on available data
100- 'sentry.span_start_time_source' : spanStartTimeSource ,
101- 'sentry.transaction_name' : transactionName ,
102- 'ui.element.id' : elementEntry . id ,
103- 'ui.element.type' : elementEntry . element ?. tagName ?. toLowerCase ( ) || 'unknown' ,
104- 'ui.element.dimensions' :
105- elementEntry . naturalWidth && elementEntry . naturalHeight
106- ? `${ elementEntry . naturalWidth } x${ elementEntry . naturalHeight } `
107- : undefined ,
108- 'ui.element.render_time' : renderTime ,
109- 'ui.element.load_time' : loadTime ,
110- // `url` is `0`(number) for text paints (hence we fall back to undefined)
111- 'ui.element.url' : elementEntry . url || undefined ,
112- 'ui.element.identifier' : elementEntry . identifier ,
113- 'ui.element.paint_type' : paintType ,
114- } ;
68+ // Span durations
69+ // Case 1: Text nodes: point-in-time spans at `renderTime`
70+ // Case 2: Image nodes: spans from `loadTime` to `renderTime` (i.e. "effective render time")
71+ // Case 3: 3rd party Image nodes w/o Timing-Allow-Origin header: point-in-time spans at `loadTime`
72+ // Case 4: Both times are 0 is already covered by the `startTime` check above
73+ const relativeStartTime = loadTime > 0 ? loadTime : renderTime ;
74+ const relativeEndTime = renderTime > 0 ? renderTime : loadTime ;
11575
11676 startSpan (
11777 {
118- name : `element[${ elementEntry . identifier } ]` ,
119- attributes,
120- startTime : spanStartTime ,
78+ name : `element[${ identifier } ]` ,
79+ attributes : {
80+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.ui.browser.elementtiming' ,
81+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'ui.elementtiming' ,
82+ // name must be user-entered, so we can assume low cardinality
83+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'component' ,
84+ 'sentry.transaction_name' : transactionName ,
85+ 'ui.element.id' : id ,
86+ 'ui.element.type' : element ?. tagName ?. toLowerCase ( ) || 'unknown' ,
87+ 'ui.element.width' : naturalWidth ,
88+ 'ui.element.height' : naturalHeight ,
89+ 'ui.element.render_time' : renderTime ,
90+ 'ui.element.load_time' : loadTime ,
91+ // `url` is `0`(number) for text paints (hence we fall back to undefined)
92+ 'ui.element.url' : url || undefined ,
93+ 'ui.element.identifier' : identifier ,
94+ // `name` contains the type of the element paint. Can be `'image-paint'` or `'text-paint'`.
95+ 'ui.element.paint_type' : name ,
96+ } ,
97+ startTime : msToSec ( timeOrigin + relativeStartTime ) ,
12198 onlyIfParent : true ,
12299 } ,
123100 span => {
124- span . end ( spanStartTime + duration ) ;
101+ span . end ( msToSec ( timeOrigin + relativeEndTime ) ) ;
125102 } ,
126103 ) ;
127104 } ) ;
0 commit comments