@@ -12,6 +12,7 @@ import type {
1212import { isString } from './is' ;
1313import type { ConsoleLevel } from './logger' ;
1414import { CONSOLE_LEVELS , logger , originalConsoleMethods } from './logger' ;
15+ import { uuid4 } from './misc' ;
1516import { addNonEnumerableProperty , fill } from './object' ;
1617import { getFunctionName } from './stacktrace' ;
1718import { supportsHistory , supportsNativeFetch } from './supports' ;
@@ -404,21 +405,24 @@ function instrumentHistory(): void {
404405
405406const DEBOUNCE_DURATION = 1000 ;
406407let 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 } ;
408412
409413/**
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.
411415 */
412- function areSimilarDomEvents ( a : Event , b : Event ) : boolean {
416+ function isSimilarToLastCapturedEvent ( event : Event ) : boolean {
413417 // 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 ) {
415419 return false ;
416420 }
417421
418422 try {
419423 // If both events have the same type, it's still possible that actions were performed on different targets.
420424 // e.g. 2 clicks on different buttons.
421- if ( a . target !== b . target ) {
425+ if ( ! event . target || ( event . target as SentryWrappedTarget ) . _sentryId !== lastCapturedEventTargetId ) {
422426 return false ;
423427 }
424428 } catch ( e ) {
@@ -436,30 +440,33 @@ function areSimilarDomEvents(a: Event, b: Event): boolean {
436440 * Decide whether an event should be captured.
437441 * @param event event to be captured
438442 */
439- function shouldSkipDOMEvent ( event : Event ) : boolean {
443+ function shouldSkipDOMEvent ( eventType : string , target : SentryWrappedTarget | null ) : boolean {
440444 // We are only interested in filtering `keypress` events for now.
441- if ( event . type !== 'keypress' ) {
445+ if ( eventType !== 'keypress' ) {
442446 return false ;
443447 }
444448
445- try {
446- const target = event . target as HTMLElement ;
449+ if ( ! target || ! target . tagName ) {
450+ return true ;
451+ }
447452
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+ }
451458
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 ;
457465 } catch ( e ) {
458466 // just accessing `target` property can throw an exception in some rare circumstances
459467 // see: https://github.com/getsentry/sentry-javascript/issues/838
468+ return null ;
460469 }
461-
462- return true ;
463470}
464471
465472/**
@@ -478,32 +485,41 @@ function makeDOMEventHandler(handler: Function, globalListener: boolean = false)
478485 return ;
479486 }
480487
488+ const target = getEventTarget ( event ) ;
489+
481490 // We always want to skip _some_ events.
482- if ( shouldSkipDOMEvent ( event ) ) {
491+ if ( shouldSkipDOMEvent ( event . type , target ) ) {
483492 return ;
484493 }
485494
486495 // Mark event as "seen"
487496 addNonEnumerableProperty ( event , '_sentryCaptured' , true ) ;
488497
498+ if ( target && ! target . _sentryId ) {
499+ // Add UUID to event target so we can identify if
500+ addNonEnumerableProperty ( target , '_sentryId' , uuid4 ( ) ) ;
501+ }
502+
489503 const name = event . type === 'keypress' ? 'input' : event . type ;
490504
491505 // If there is no last captured event, it means that we can safely capture the new event and store it for future comparisons.
492506 // If there is a last captured event, see if the new event is different enough to treat it as a unique one.
493507 // 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 ) ) {
495509 handler ( {
496510 event : event ,
497511 name,
498512 global : globalListener ,
499513 } ) ;
500- lastCapturedEvent = event ;
514+ lastCapturedEventType = event . type ;
515+ lastCapturedEventTargetId = target ? target . _sentryId : undefined ;
501516 }
502517
503518 // Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.
504519 clearTimeout ( debounceTimerID ) ;
505520 debounceTimerID = WINDOW . setTimeout ( ( ) => {
506- lastCapturedEvent = undefined ;
521+ lastCapturedEventTargetId = undefined ;
522+ lastCapturedEventType = undefined ;
507523 } , DEBOUNCE_DURATION ) ;
508524 } ;
509525}
0 commit comments