javascriptaddeventlistenerevent-listenerfirefox-developer-tools

How to intercept an eventhandler in javascript?


Let's say I have a simple webpage with only a small js file:

document.addEventListener('click', (event) => {
    console.log("click event fired");
});

document.addEventListener('keypress', (event) => {
    console.log("keypress event fired");
});

How to use the firefox developer console to:

  1. Overwrite the addEventListener function so I can log which type it was listening to?
  2. After logging execute the original function code

Right now I have this:

Object.defineProperty(Document.prototype, "addEventListener", {
    value: function () {
        console.log("addEventListener of type ...? was fired");
        // execute original function
    }
});

Whenever the document is clicked, the output should be:

addEventListener of type click was fired
click event fired

Or when a key is pressed:

addEventListener of type keypress was fired
keypress event fired

edit: Using the debugger would not solve my problem. I would like to know which eventlisteners are present on a site, and automate this for a long list of sites.


Solution

  • Your value is the new value of the addEventListener() function, not the callback function that's provided which gets invoked. In order to log each time the event occurs, you need to modify the callback as well, which can be done by calling the original addEventListener() function and then providing your own callback that performs the logging. Below I'm also updating EventTarget.prototype, which allows you to intercept the addEventListener on all elements that extend EventTarget:

    const proto = EventTarget.prototype;
    const _addEventListener = EventTarget.prototype.addEventListener;
    Object.defineProperty(proto, "addEventListener", {
      value: function (type, fn, ...rest) {       
          _addEventListener.call(this, type, function(...args) {
            console.log(`addEventListener of type ${type} was fired`);
            return fn.apply(this, args);
          }, ...rest);
      }
    });
    
    document.addEventListener("keydown", () => {
      console.log("keydown");
    });
    
    btn.addEventListener("click", () => {
      console.log("clicked");
    });
    <button id="btn">Click me</button>


    Note, by providing your own wrapper around the original callback function, using removeEventListener() won't work as expected, to handle this (and another edge case where the same function reference is passed to addEventListener()), one idea to handle this it to keep a Map of the functions added as event handlers and reuse the custom callback when needed:

    const proto = EventTarget.prototype;
    const _addEventListener = EventTarget.prototype.addEventListener;
    const _removeEventListener = EventTarget.prototype.removeEventListener;
    const callbacks = new WeakMap();
    Object.defineProperty(proto, "addEventListener", {
      value: function(type, fn, ...rest) {
        const cb = callbacks.get(fn) ?? function(...args) {
          console.log(`addEventListener of type ${type} was fired`);
          return fn.apply(this, args);
        }
        callbacks.set(fn, cb);
        _addEventListener.call(this, type, cb, ...rest);
      }
    });
    
    Object.defineProperty(proto, "removeEventListener", {
      value: function(type, fn, ...rest) {
        const cb = callbacks.get(fn);
        _removeEventListener.call(this, type, cb, ...rest);
      }
    });
    
    document.addEventListener("keydown", () => {
      console.log("keydown");
    });
    
    const mouseover = () => console.log("hover");
    para.addEventListener("mouseover", mouseover);
    
    btn.addEventListener("click", () => {
      console.log("clicked, removing event listener");
      para.removeEventListener("mouseover", mouseover);
    });
    p {
      background: yellow;
    }
    <p id="para">Hover me</p>
    <button id="btn">Remove hover event listener</button>