11import { API , getCurrentHub } from '@sentry/core' ;
2- import { Breadcrumb , BreadcrumbHint , Integration , Severity } from '@sentry/types' ;
2+ import { Breadcrumb , BreadcrumbHint , Integration , Severity , WrappedFunction } from '@sentry/types' ;
33import {
44 fill ,
55 getEventDescription ,
@@ -19,7 +19,6 @@ import { breadcrumbEventHandler, keypressEventHandler, wrap } from './helpers';
1919
2020const global = getGlobalObject < Window > ( ) ;
2121let lastHref : string | undefined ;
22-
2322/**
2423 * @hidden
2524 */
@@ -77,8 +76,7 @@ export class Breadcrumbs implements Integration {
7776 if ( ! ( 'console' in global ) ) {
7877 return ;
7978 }
80- const levels = [ 'log' , 'info' , 'warn' , 'error' , 'debug' , 'assert' ] ;
81- levels . forEach ( function ( level : string ) : void {
79+ [ 'debug' , 'info' , 'warn' , 'error' , 'log' , 'assert' ] . forEach ( function ( level : string ) : void {
8280 if ( ! ( level in global . console ) ) {
8381 return ;
8482 }
@@ -123,10 +121,82 @@ export class Breadcrumbs implements Integration {
123121 if ( ! ( 'document' in global ) ) {
124122 return ;
125123 }
124+
126125 // Capture breadcrumbs from any click that is unhandled / bubbled up all the way
127126 // to the document. Do this before we instrument addEventListener.
128127 global . document . addEventListener ( 'click' , breadcrumbEventHandler ( 'click' ) , false ) ;
129128 global . document . addEventListener ( 'keypress' , keypressEventHandler ( ) , false ) ;
129+
130+ // After hooking into document bubbled up click and keypresses events, we also hook into user handled click & keypresses.
131+ [ 'EventTarget' , 'Node' ] . forEach ( ( target : string ) => {
132+ const proto = ( global as any ) [ target ] && ( global as any ) [ target ] . prototype ;
133+
134+ if ( ! proto || ! proto . hasOwnProperty || ! proto . hasOwnProperty ( 'addEventListener' ) ) {
135+ return ;
136+ }
137+
138+ fill ( proto , 'addEventListener' , function (
139+ original : ( ) => void ,
140+ ) : (
141+ eventName : string ,
142+ fn : EventListenerOrEventListenerObject ,
143+ options ?: boolean | AddEventListenerOptions ,
144+ ) => void {
145+ return function (
146+ this : any ,
147+ eventName : string ,
148+ fn : EventListenerOrEventListenerObject ,
149+ options ?: boolean | AddEventListenerOptions ,
150+ ) : ( eventName : string , fn : EventListenerOrEventListenerObject , capture ?: boolean , secure ?: boolean ) => void {
151+ if ( ( fn as any ) . handleEvent ) {
152+ if ( eventName === 'click' ) {
153+ fill ( fn , 'handleEvent' , function ( innerOriginal : ( ) => void ) : ( caughtEvent : Event ) => void {
154+ return function ( this : any , event : Event ) : ( event : Event ) => void {
155+ breadcrumbEventHandler ( 'click' ) ( event ) ;
156+ return innerOriginal . call ( this , event ) ;
157+ } ;
158+ } ) ;
159+ }
160+ if ( eventName === 'keypress' ) {
161+ fill ( fn , 'handleEvent' , keypressEventHandler ( ) ) ;
162+ }
163+ } else {
164+ if ( eventName === 'click' ) {
165+ breadcrumbEventHandler ( 'click' , true ) ( this ) ;
166+ }
167+ if ( eventName === 'keypress' ) {
168+ keypressEventHandler ( ) ( this ) ;
169+ }
170+ }
171+
172+ return original . call ( this , eventName , fn , options ) ;
173+ } ;
174+ } ) ;
175+
176+ fill ( proto , 'removeEventListener' , function (
177+ original : ( ) => void ,
178+ ) : (
179+ this : any ,
180+ eventName : string ,
181+ fn : EventListenerObject ,
182+ options ?: boolean | EventListenerOptions ,
183+ ) => ( ) => void {
184+ return function (
185+ this : any ,
186+ eventName : string ,
187+ fn : EventListenerObject ,
188+ options ?: boolean | EventListenerOptions ,
189+ ) : ( ) => void {
190+ let callback = ( fn as any ) as WrappedFunction ;
191+ try {
192+ callback = callback && ( callback . __sentry_wrapped__ || callback ) ;
193+ } catch ( e ) {
194+ // ignore, accessing __sentry_wrapped__ will throw in some Selenium environments
195+ }
196+ return original . call ( this , eventName , callback , options ) ;
197+ } ;
198+ } ) ;
199+ } ) ;
130200 }
131201
132202 /** JSDoc */
0 commit comments