@@ -12,7 +12,7 @@ import type {
1212import { isString } from './is' ;
1313import type { ConsoleLevel } from './logger' ;
1414import { CONSOLE_LEVELS , logger , originalConsoleMethods } from './logger' ;
15- import { fill } from './object' ;
15+ import { addNonEnumerableProperty , fill } from './object' ;
1616import { getFunctionName } from './stacktrace' ;
1717import { supportsHistory , supportsNativeFetch } from './supports' ;
1818import { getGlobalObject , GLOBAL_OBJ } from './worldwide' ;
@@ -400,31 +400,24 @@ function instrumentHistory(): void {
400400 fill ( WINDOW . history , 'replaceState' , historyReplacementFunction ) ;
401401}
402402
403- const debounceDuration = 1000 ;
403+ const DEBOUNCE_DURATION = 1000 ;
404404let debounceTimerID : number | undefined ;
405405let lastCapturedEvent : Event | undefined ;
406406
407407/**
408- * Decide whether the current event should finish the debounce of previously captured one.
409- * @param previous previously captured event
410- * @param current event to be captured
408+ * Check whether two DOM events are similar to eachother. For example, two click events on the same button.
411409 */
412- function shouldShortcircuitPreviousDebounce ( previous : Event | undefined , current : Event ) : boolean {
413- // If there was no previous event, it should always be swapped for the new one.
414- if ( ! previous ) {
415- return true ;
416- }
417-
410+ function areSimilarDomEvents ( a : Event , b : Event ) : boolean {
418411 // If both events have different type, then user definitely performed two separate actions. e.g. click + keypress.
419- if ( previous . type !== current . type ) {
420- return true ;
412+ if ( a . type !== b . type ) {
413+ return false ;
421414 }
422415
423416 try {
424417 // If both events have the same type, it's still possible that actions were performed on different targets.
425418 // e.g. 2 clicks on different buttons.
426- if ( previous . target !== current . target ) {
427- return true ;
419+ if ( a . target !== b . target ) {
420+ return false ;
428421 }
429422 } catch ( e ) {
430423 // just accessing `target` property can throw an exception in some rare circumstances
@@ -434,7 +427,7 @@ function shouldShortcircuitPreviousDebounce(previous: Event | undefined, current
434427 // If both events have the same type _and_ same `target` (an element which triggered an event, _not necessarily_
435428 // to which an event listener was attached), we treat them as the same action, as we want to capture
436429 // only one breadcrumb. e.g. multiple clicks on the same button, or typing inside a user input box.
437- return false ;
430+ return true ;
438431}
439432
440433/**
@@ -475,11 +468,11 @@ function shouldSkipDOMEvent(event: Event): boolean {
475468 * @hidden
476469 */
477470function makeDOMEventHandler ( handler : Function , globalListener : boolean = false ) : ( event : Event ) => void {
478- return ( event : Event ) : void => {
471+ return ( event : Event & { _sentryCaptured ?: true } ) : void => {
479472 // It's possible this handler might trigger multiple times for the same
480473 // event (e.g. event propagation through node ancestors).
481474 // Ignore if we've already captured that event.
482- if ( ! event || lastCapturedEvent === event ) {
475+ if ( ! event || event [ '_sentryCaptured' ] ) {
483476 return ;
484477 }
485478
@@ -488,20 +481,15 @@ function makeDOMEventHandler(handler: Function, globalListener: boolean = false)
488481 return ;
489482 }
490483
484+ // Mark event as "seen"
485+ addNonEnumerableProperty ( event , '_sentryCaptured' , true ) ;
486+
491487 const name = event . type === 'keypress' ? 'input' : event . type ;
492488
493- // If there is no debounce timer, it means that we can safely capture the new event and store it for future comparisons.
494- if ( debounceTimerID === undefined ) {
495- handler ( {
496- event : event ,
497- name,
498- global : globalListener ,
499- } ) ;
500- lastCapturedEvent = event ;
501- }
502- // If there is a debounce awaiting, see if the new event is different enough to treat it as a unique one.
489+ // If there is no last captured event, it means that we can safely capture the new event and store it for future comparisons.
490+ // If there is a last captured event, see if the new event is different enough to treat it as a unique one.
503491 // If that's the case, emit the previous event and store locally the newly-captured DOM event.
504- else if ( shouldShortcircuitPreviousDebounce ( lastCapturedEvent , event ) ) {
492+ if ( lastCapturedEvent === undefined || ! areSimilarDomEvents ( lastCapturedEvent , event ) ) {
505493 handler ( {
506494 event : event ,
507495 name,
@@ -513,8 +501,8 @@ function makeDOMEventHandler(handler: Function, globalListener: boolean = false)
513501 // Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.
514502 clearTimeout ( debounceTimerID ) ;
515503 debounceTimerID = WINDOW . setTimeout ( ( ) => {
516- debounceTimerID = undefined ;
517- } , debounceDuration ) ;
504+ lastCapturedEvent = undefined ;
505+ } , DEBOUNCE_DURATION ) ;
518506 } ;
519507}
520508
0 commit comments