1
+ import { IncrementalSource , MouseInteractions , record } from '@sentry-internal/rrweb' ;
1
2
import type { Breadcrumb } from '@sentry/types' ;
2
3
3
4
import { WINDOW } from '../constants' ;
4
5
import type {
6
+ RecordingEvent ,
5
7
ReplayClickDetector ,
6
8
ReplayContainer ,
7
9
ReplayMultiClickFrame ,
8
10
ReplaySlowClickFrame ,
9
11
SlowClickConfig ,
10
12
} from '../types' ;
13
+ import { ReplayEventTypeFullSnapshot , ReplayEventTypeIncrementalSnapshot } from '../types' ;
11
14
import { timestampToS } from '../util/timestamp' ;
12
15
import { addBreadcrumbEvent } from './util/addBreadcrumbEvent' ;
13
- import { getClickTargetNode } from './util/domUtils' ;
16
+ import { getClosestInteractive } from './util/domUtils' ;
14
17
import { onWindowOpen } from './util/onWindowOpen' ;
15
18
16
19
type ClickBreadcrumb = Breadcrumb & {
@@ -26,6 +29,16 @@ interface Click {
26
29
node : HTMLElement ;
27
30
}
28
31
32
+ type IncrementalRecordingEvent = RecordingEvent & {
33
+ type : typeof ReplayEventTypeIncrementalSnapshot ;
34
+ data : { source : IncrementalSource } ;
35
+ } ;
36
+
37
+ type IncrementalMouseInteractionRecordingEvent = IncrementalRecordingEvent & {
38
+ type : typeof ReplayEventTypeIncrementalSnapshot ;
39
+ data : { type : MouseInteractions ; id : number } ;
40
+ } ;
41
+
29
42
/** Handle a click. */
30
43
export function handleClick ( clickDetector : ReplayClickDetector , clickBreadcrumb : Breadcrumb , node : HTMLElement ) : void {
31
44
clickDetector . handleClick ( clickBreadcrumb , node ) ;
@@ -70,48 +83,14 @@ export class ClickDetector implements ReplayClickDetector {
70
83
71
84
/** Register click detection handlers on mutation or scroll. */
72
85
public addListeners ( ) : void {
73
- const mutationHandler = ( ) : void => {
74
- this . _lastMutation = nowInSeconds ( ) ;
75
- } ;
76
-
77
- const scrollHandler = ( ) : void => {
78
- this . _lastScroll = nowInSeconds ( ) ;
79
- } ;
80
-
81
86
const cleanupWindowOpen = onWindowOpen ( ( ) => {
82
87
// Treat window.open as mutation
83
88
this . _lastMutation = nowInSeconds ( ) ;
84
89
} ) ;
85
90
86
- const clickHandler = ( event : MouseEvent ) : void => {
87
- if ( ! event . target ) {
88
- return ;
89
- }
90
-
91
- const node = getClickTargetNode ( event ) ;
92
- if ( node ) {
93
- this . _handleMultiClick ( node as HTMLElement ) ;
94
- }
95
- } ;
96
-
97
- const obs = new MutationObserver ( mutationHandler ) ;
98
-
99
- obs . observe ( WINDOW . document . documentElement , {
100
- attributes : true ,
101
- characterData : true ,
102
- childList : true ,
103
- subtree : true ,
104
- } ) ;
105
-
106
- WINDOW . addEventListener ( 'scroll' , scrollHandler , { passive : true } ) ;
107
- WINDOW . addEventListener ( 'click' , clickHandler , { passive : true } ) ;
108
-
109
91
this . _teardown = ( ) => {
110
- WINDOW . removeEventListener ( 'scroll' , scrollHandler ) ;
111
- WINDOW . removeEventListener ( 'click' , clickHandler ) ;
112
92
cleanupWindowOpen ( ) ;
113
93
114
- obs . disconnect ( ) ;
115
94
this . _clicks = [ ] ;
116
95
this . _lastMutation = 0 ;
117
96
this . _lastScroll = 0 ;
@@ -129,7 +108,7 @@ export class ClickDetector implements ReplayClickDetector {
129
108
}
130
109
}
131
110
132
- /** Handle a click */
111
+ /** @inheritDoc */
133
112
public handleClick ( breadcrumb : Breadcrumb , node : HTMLElement ) : void {
134
113
if ( ignoreElement ( node , this . _ignoreSelector ) || ! isClickBreadcrumb ( breadcrumb ) ) {
135
114
return ;
@@ -158,6 +137,22 @@ export class ClickDetector implements ReplayClickDetector {
158
137
}
159
138
}
160
139
140
+ /** @inheritDoc */
141
+ public registerMutation ( timestamp = Date . now ( ) ) : void {
142
+ this . _lastMutation = timestampToS ( timestamp ) ;
143
+ }
144
+
145
+ /** @inheritDoc */
146
+ public registerScroll ( timestamp = Date . now ( ) ) : void {
147
+ this . _lastScroll = timestampToS ( timestamp ) ;
148
+ }
149
+
150
+ /** @inheritDoc */
151
+ public registerClick ( element : HTMLElement ) : void {
152
+ const node = getClosestInteractive ( element ) ;
153
+ this . _handleMultiClick ( node as HTMLElement ) ;
154
+ }
155
+
161
156
/** Count multiple clicks on elements. */
162
157
private _handleMultiClick ( node : HTMLElement ) : void {
163
158
this . _getClicks ( node ) . forEach ( click => {
@@ -311,3 +306,47 @@ function isClickBreadcrumb(breadcrumb: Breadcrumb): breadcrumb is ClickBreadcrum
311
306
function nowInSeconds ( ) : number {
312
307
return Date . now ( ) / 1000 ;
313
308
}
309
+
310
+ /** Update the click detector based on a recording event of rrweb. */
311
+ export function updateClickDetectorForRecordingEvent ( clickDetector : ReplayClickDetector , event : RecordingEvent ) : void {
312
+ try {
313
+ // We interpret a full snapshot as a mutation (this may not be true, but there is no way for us to know)
314
+ if ( event . type === ReplayEventTypeFullSnapshot ) {
315
+ clickDetector . registerMutation ( event . timestamp ) ;
316
+ }
317
+
318
+ if ( ! isIncrementalEvent ( event ) ) {
319
+ return ;
320
+ }
321
+
322
+ const { source } = event . data ;
323
+ if ( source === IncrementalSource . Mutation ) {
324
+ clickDetector . registerMutation ( event . timestamp ) ;
325
+ }
326
+
327
+ if ( source === IncrementalSource . Scroll ) {
328
+ clickDetector . registerScroll ( event . timestamp ) ;
329
+ }
330
+
331
+ if ( isIncrementalMouseInteraction ( event ) ) {
332
+ const { type, id } = event . data ;
333
+ const node = record . mirror . getNode ( id ) ;
334
+
335
+ if ( node instanceof HTMLElement && type === MouseInteractions . Click ) {
336
+ clickDetector . registerClick ( node ) ;
337
+ }
338
+ }
339
+ } catch {
340
+ // ignore errors here, e.g. if accessing something that does not exist
341
+ }
342
+ }
343
+
344
+ function isIncrementalEvent ( event : RecordingEvent ) : event is IncrementalRecordingEvent {
345
+ return event . type === ReplayEventTypeIncrementalSnapshot ;
346
+ }
347
+
348
+ function isIncrementalMouseInteraction (
349
+ event : IncrementalRecordingEvent ,
350
+ ) : event is IncrementalMouseInteractionRecordingEvent {
351
+ return event . data . source === IncrementalSource . MouseInteraction ;
352
+ }
0 commit comments