@@ -12,6 +12,7 @@ import type {
12
12
import { isString } from './is' ;
13
13
import type { ConsoleLevel } from './logger' ;
14
14
import { CONSOLE_LEVELS , logger , originalConsoleMethods } from './logger' ;
15
+ import { uuid4 } from './misc' ;
15
16
import { addNonEnumerableProperty , fill } from './object' ;
16
17
import { getFunctionName } from './stacktrace' ;
17
18
import { supportsHistory , supportsNativeFetch } from './supports' ;
@@ -404,21 +405,24 @@ function instrumentHistory(): void {
404
405
405
406
const DEBOUNCE_DURATION = 1000 ;
406
407
let debounceTimerID : number | undefined ;
407
- let lastCapturedEvent : Event | undefined ;
408
+ let lastCapturedEventType : string | undefined ;
409
+ let lastCapturedEventTargetId : string | undefined ;
410
+
411
+ type SentryWrappedTarget = HTMLElement & { _sentryId ?: string } ;
408
412
409
413
/**
410
- * Check whether two DOM events are similar to eachother . For example, two click events on the same button.
414
+ * Check whether the event is similar to the last captured one . For example, two click events on the same button.
411
415
*/
412
- function areSimilarDomEvents ( a : Event , b : Event ) : boolean {
416
+ function isSimilarToLastCapturedEvent ( event : Event ) : boolean {
413
417
// If both events have different type, then user definitely performed two separate actions. e.g. click + keypress.
414
- if ( a . type !== b . type ) {
418
+ if ( event . type !== lastCapturedEventType ) {
415
419
return false ;
416
420
}
417
421
418
422
try {
419
423
// If both events have the same type, it's still possible that actions were performed on different targets.
420
424
// e.g. 2 clicks on different buttons.
421
- if ( a . target !== b . target ) {
425
+ if ( ! event . target || ( event . target as SentryWrappedTarget ) . _sentryId !== lastCapturedEventTargetId ) {
422
426
return false ;
423
427
}
424
428
} catch ( e ) {
@@ -436,30 +440,33 @@ function areSimilarDomEvents(a: Event, b: Event): boolean {
436
440
* Decide whether an event should be captured.
437
441
* @param event event to be captured
438
442
*/
439
- function shouldSkipDOMEvent ( event : Event ) : boolean {
443
+ function shouldSkipDOMEvent ( eventType : string , target : SentryWrappedTarget | null ) : boolean {
440
444
// We are only interested in filtering `keypress` events for now.
441
- if ( event . type !== 'keypress' ) {
445
+ if ( eventType !== 'keypress' ) {
442
446
return false ;
443
447
}
444
448
445
- try {
446
- const target = event . target as HTMLElement ;
449
+ if ( ! target || ! target . tagName ) {
450
+ return true ;
451
+ }
447
452
448
- if ( ! target || ! target . tagName ) {
449
- return true ;
450
- }
453
+ // Only consider keypress events on actual input elements. This will disregard keypresses targeting body
454
+ // e.g.tabbing through elements, hotkeys, etc.
455
+ if ( target . tagName === 'INPUT' || target . tagName === 'TEXTAREA' || target . isContentEditable ) {
456
+ return false ;
457
+ }
451
458
452
- // Only consider keypress events on actual input elements. This will disregard keypresses targeting body
453
- // e.g.tabbing through elements, hotkeys, etc.
454
- if ( target . tagName === 'INPUT' || target . tagName === 'TEXTAREA' || target . isContentEditable ) {
455
- return false ;
456
- }
459
+ return true ;
460
+ }
461
+
462
+ function getEventTarget ( event : Event ) : SentryWrappedTarget | null {
463
+ try {
464
+ return event . target as SentryWrappedTarget | null ;
457
465
} catch ( e ) {
458
466
// just accessing `target` property can throw an exception in some rare circumstances
459
467
// see: https://github.com/getsentry/sentry-javascript/issues/838
468
+ return null ;
460
469
}
461
-
462
- return true ;
463
470
}
464
471
465
472
/**
@@ -478,32 +485,41 @@ function makeDOMEventHandler(handler: Function, globalListener: boolean = false)
478
485
return ;
479
486
}
480
487
488
+ const target = getEventTarget ( event ) ;
489
+
481
490
// We always want to skip _some_ events.
482
- if ( shouldSkipDOMEvent ( event ) ) {
491
+ if ( shouldSkipDOMEvent ( event . type , target ) ) {
483
492
return ;
484
493
}
485
494
486
495
// Mark event as "seen"
487
496
addNonEnumerableProperty ( event , '_sentryCaptured' , true ) ;
488
497
498
+ if ( target && ! target . _sentryId ) {
499
+ // Add UUID to event target so we can identify if
500
+ addNonEnumerableProperty ( target , '_sentryId' , uuid4 ( ) ) ;
501
+ }
502
+
489
503
const name = event . type === 'keypress' ? 'input' : event . type ;
490
504
491
505
// If there is no last captured event, it means that we can safely capture the new event and store it for future comparisons.
492
506
// If there is a last captured event, see if the new event is different enough to treat it as a unique one.
493
507
// If that's the case, emit the previous event and store locally the newly-captured DOM event.
494
- if ( lastCapturedEvent === undefined || ! areSimilarDomEvents ( lastCapturedEvent , event ) ) {
508
+ if ( ! isSimilarToLastCapturedEvent ( event ) ) {
495
509
handler ( {
496
510
event : event ,
497
511
name,
498
512
global : globalListener ,
499
513
} ) ;
500
- lastCapturedEvent = event ;
514
+ lastCapturedEventType = event . type ;
515
+ lastCapturedEventTargetId = target ? target . _sentryId : undefined ;
501
516
}
502
517
503
518
// Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.
504
519
clearTimeout ( debounceTimerID ) ;
505
520
debounceTimerID = WINDOW . setTimeout ( ( ) => {
506
- lastCapturedEvent = undefined ;
521
+ lastCapturedEventTargetId = undefined ;
522
+ lastCapturedEventType = undefined ;
507
523
} , DEBOUNCE_DURATION ) ;
508
524
} ;
509
525
}
0 commit comments