I have a use case where I'd like to monitor event listeners being added. I found a method to do this that worked for me here: https://stackoverflow.com/a/6434924
This code seems to work in many situations, but, it would seem it will throw a InternalError: too much recursion
exception when used on a page containing a react app.
Simplest reproducer looks like this:
Node.prototype.realAddEventListener = Node.prototype.addEventListener;
Node.prototype.addEventListener = function(a,b,c){
this.realAddEventListener(a,b,c);
}
Printing this
, a
, and b
just before the error happens yields:
this = [object HTMLUnknownElement]
a = react-invokeguardedcallback
b = function callCallback() {
// We immediately remove the callback from event listeners so that
// nested `invokeGuardedCallback` calls do not clash. Otherwise, a
// nested call would trigger the fake event handlers of any call higher
// in the stack.
fakeNode.removeEventListener(evtType, callCallback, false); // We check for window.hasOwnProperty('event') to prevent the
// window.event assignment in both IE <= 10 as they throw an error
// "Member not found" in strict mode, and in Firefox which does not
// support window.event.
if (typeof window.event !== 'undefined' && window.hasOwnProperty('event')) {
window.event = windowEvent;
}
func.apply(context, funcArgs);
didError = false;
}
What is it about adding a listener to an HTMLUnknownElement on the react-invokeguardedcallback
with the function handler above that causes my hijacked addEventListener
to recurse?
Thanks @Bergi and @Ivan Castellanos for the reality check.
This behavior was caused by the development environment. The original code in the post works fine under normal circumstances.
However, if the source files are being watched and hot-reloaded on changes, and said source files are also injected into the page by a web extension content script (using Method 1 described here: https://stackoverflow.com/a/9517879/2041427), the InternalError: too much recursion
will occur.
This happens because the addEventListener
function would have already been swapped in the page script context when the old version of the file was injected into the page. Upon hot-reload addEventListener
equals
function(a,b,c){
this.realAddEventListener(a,b,c)
}
Which is then set to Node.prototype.realAddEventListener
causing an infinite recursive loop.
The old ''fix'' below ''worked'' because, on hot-reload+re-injection the error: Uncaught SyntaxError: redeclaration of const handlerMagic
would prevent the code from running again.
The actual fix is to only assign Node.prototype.realAddEventListener
if Node.prototype.realAddEventListener
is undefined, like so:
if(!Node.prototype.realAddEventListener){
Node.prototype.realAddEventListener = Node.prototype.addEventListener
}
Node.prototype.addEventListener = function(a,b,c){
this.realAddEventListener
}
Turns out realAddEventListener
was not in getting assigned to the original addEventListener
implementation. Instead, my re-assignment of addEventListener happened first, and then realAddEventListener
was assigned to my hijacked function causing infinite recursion.
The fix was to ensure the order of events with a Promise:
const handlerMagic = new Promise((resolve)=>{
Node.prototype.realAddEventListener = Node.prototype.addEventListener;
resolve()
}).then(_=>{
Node.prototype.addEventListener = function(a,b,c){
this.realAddEventListener(a,b,c)
}
})
Works fine now.