How to intercept an eventhandler in javascript?

220 Views Asked by At

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.

3

There are 3 best solutions below

4
Nick Parsons On BEST ANSWER

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>

0
BugHunter On

You can simply use the Document.prototype.addEventListener to achieve your goal.

example:

const originalAddEventListener = Document.prototype.addEventListener;

Document.prototype.addEventListener = function (type, listener, options) {
    console.log(`addEventListener of type ${type} was fired`);
    originalAddEventListener.call(this, type, listener, options);
};

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

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

The DevTools provide a way to log events without having to change the code.

To do that go to the Debugger. In there, there is an Event Listener Breakpoints pane, which let's you set breakpoints for many different kinds of events. To only log the events instead of stopping the script execution at the line of the event listener function, you have to check the Log option in the header of the pane.

Event Listener Breakpoints pane in Firefox DevTools' Debugger listening for the 'click' event and having the Log option enabled to log the events to the Console

When an event listener is hit, the event is then logged to the Console including a link to the source code, which looks like this:

'click' events being logged to the Console

What you cannot do at the moment (as of Firefox 118) is setting a condition for the events you are listening. Therefore, I've created a feature request to add conditional event breakpoints.