@@ -48,6 +48,11 @@ export class UserInteractionPlugin extends BasePlugin<unknown> {
4848 moduleName = this . component ;
4949 private _spansData = new WeakMap < api . Span , SpanData > ( ) ;
5050 private _zonePatched = false ;
51+ // for addEventListener/removeEventListener state
52+ private _wrappedListeners = new WeakMap <
53+ Function ,
54+ Map < string , Map < HTMLElement , Function > >
55+ > ( ) ;
5156
5257 constructor ( ) {
5358 super ( '@opentelemetry/plugin-user-interaction' , VERSION ) ;
@@ -165,6 +170,61 @@ export class UserInteractionPlugin extends BasePlugin<unknown> {
165170 }
166171 }
167172
173+ /**
174+ * Returns true iff we should use the patched callback; false if it's already been patched
175+ */
176+ private addPatchedListener (
177+ on : HTMLElement ,
178+ type : string ,
179+ listener : Function ,
180+ wrappedListener : Function
181+ ) : boolean {
182+ let listener2Type = this . _wrappedListeners . get ( listener ) ;
183+ if ( ! listener2Type ) {
184+ listener2Type = new Map ( ) ;
185+ this . _wrappedListeners . set ( listener , listener2Type ) ;
186+ }
187+ let element2patched = listener2Type . get ( type ) ;
188+ if ( ! element2patched ) {
189+ element2patched = new Map ( ) ;
190+ listener2Type . set ( type , element2patched ) ;
191+ }
192+ if ( element2patched . has ( on ) ) {
193+ return false ;
194+ }
195+ element2patched . set ( on , wrappedListener ) ;
196+ return true ;
197+ }
198+
199+ /**
200+ * Returns the patched version of the callback (or undefined)
201+ */
202+ private removePatchedListener (
203+ on : HTMLElement ,
204+ type : string ,
205+ listener : Function
206+ ) : Function | undefined {
207+ const listener2Type = this . _wrappedListeners . get ( listener ) ;
208+ if ( ! listener2Type ) {
209+ return undefined ;
210+ }
211+ const element2patched = listener2Type . get ( type ) ;
212+ if ( ! element2patched ) {
213+ return undefined ;
214+ }
215+ const patched = element2patched . get ( on ) ;
216+ if ( patched ) {
217+ element2patched . delete ( on ) ;
218+ if ( element2patched . size === 0 ) {
219+ listener2Type . delete ( type ) ;
220+ if ( listener2Type . size === 0 ) {
221+ this . _wrappedListeners . delete ( listener ) ;
222+ }
223+ }
224+ }
225+ return patched ;
226+ }
227+
168228 /**
169229 * This patches the addEventListener of HTMLElement to be able to
170230 * auto instrument the click events
@@ -179,8 +239,12 @@ export class UserInteractionPlugin extends BasePlugin<unknown> {
179239 listener : any ,
180240 useCapture : any
181241 ) {
242+ const once = useCapture && useCapture . once ;
182243 const patchedListener = ( ...args : any [ ] ) => {
183244 const target = this ;
245+ if ( once ) {
246+ plugin . removePatchedListener ( this , type , listener ) ;
247+ }
184248 const span = plugin . _createSpan ( target , 'click' ) ;
185249 if ( span ) {
186250 return plugin . _tracer . withSpan ( span , ( ) => {
@@ -193,7 +257,37 @@ export class UserInteractionPlugin extends BasePlugin<unknown> {
193257 return listener . apply ( target , args ) ;
194258 }
195259 } ;
196- return original . call ( this , type , patchedListener , useCapture ) ;
260+ if ( plugin . addPatchedListener ( this , type , listener , patchedListener ) ) {
261+ return original . call ( this , type , patchedListener , useCapture ) ;
262+ }
263+ } ;
264+ } ;
265+ }
266+
267+ /**
268+ * This patches the removeEventListener of HTMLElement to handle the fact that
269+ * we patched the original callbacks
270+ * This is done when zone is not available
271+ */
272+ private _patchRemoveEventListener ( ) {
273+ const plugin = this ;
274+ return ( original : Function ) => {
275+ return function removeEventListenerPatched (
276+ this : HTMLElement ,
277+ type : any ,
278+ listener : any ,
279+ useCapture : any
280+ ) {
281+ const wrappedListener = plugin . removePatchedListener (
282+ this ,
283+ type ,
284+ listener
285+ ) ;
286+ if ( wrappedListener ) {
287+ return original . call ( this , type , wrappedListener , useCapture ) ;
288+ } else {
289+ return original . call ( this , type , listener , useCapture ) ;
290+ }
197291 } ;
198292 } ;
199293 }
@@ -434,11 +528,22 @@ export class UserInteractionPlugin extends BasePlugin<unknown> {
434528 'removing previous patch from method addEventListener'
435529 ) ;
436530 }
531+ if ( isWrapped ( HTMLElement . prototype . removeEventListener ) ) {
532+ shimmer . unwrap ( HTMLElement . prototype , 'removeEventListener' ) ;
533+ this . _logger . debug (
534+ 'removing previous patch from method removeEventListener'
535+ ) ;
536+ }
437537 shimmer . wrap (
438538 HTMLElement . prototype ,
439539 'addEventListener' ,
440540 this . _patchElement ( )
441541 ) ;
542+ shimmer . wrap (
543+ HTMLElement . prototype ,
544+ 'removeEventListener' ,
545+ this . _patchRemoveEventListener ( )
546+ ) ;
442547 }
443548
444549 this . _patchHistoryApi ( ) ;
0 commit comments