132

I have already looked at these questions:

however none of them answers how to get a list of event listeners attached to a node using addEventListener, without modifying the addEventListener prototype before the event listeners are created.

VisualEvent doesn't display all event listener (iphone specific ones) and I want to do this (somewhat) programmatically.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Tyilo
  • 28,998
  • 40
  • 113
  • 198
  • 1
    Possible duplicate of [How to find event listeners on a DOM node when debugging or from the JS code?](http://stackoverflow.com/questions/446892/how-to-find-event-listeners-on-a-dom-node-when-debugging-or-from-the-js-code) – Nickolay Feb 13 '16 at 13:36
  • 2
    "somewhat programmatically" and the fact that [accepted answer](http://stackoverflow.com/a/15666321/1026) for this question is a devtools feature makes this a duplicate of the listed question. For those looking for a JS solution, [the answer is "there isn't one"](http://stackoverflow.com/a/10030771/1026) – Nickolay Feb 13 '16 at 13:39

5 Answers5

160

Chrome DevTools, Safari Inspector and Firebug support getEventListeners(node).

getEventListeners(document)

adriaan
  • 1,088
  • 1
  • 12
  • 29
NVI
  • 14,907
  • 16
  • 65
  • 104
77

You can't.

The only way to get a list of all event listeners attached to a node is to intercept the listener attachment call.

DOM4 addEventListener

Says

Append an event listener to the associated list of event listeners with type set to type, listener set to listener, and capture set to capture, unless there already is an event listener in that list with the same type, listener, and capture.

Meaning that an event listener is added to the "list of event listeners". That's all. There is no notion of what this list should be nor how you should access it.

Tobias Feil
  • 2,399
  • 3
  • 25
  • 41
Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 19
    Any chance of supplying some justification or reasoning for why it must work this way? Clearly the browser knows what all of the listeners are. – Darth Egregious Apr 05 '12 at 19:23
  • 4
    @user973810: How do you want him to justify this? The DOM API provides no way to do it and there are no non-standard ways to do it in current browsers. As to why this is the case, I don't really know. It seems a reasonable thing to want to do. – Tim Down Apr 05 '12 at 22:38
  • I've seen a few threads lying around about adding an API to the DOM for this. – Raynos Apr 05 '12 at 22:41
  • @TimDown the edit helps. Seeing that there is no specification for something like "getEventListeners" justifies why there isn't such a thing. – Darth Egregious Apr 05 '12 at 23:16
  • I find it useful for things like Electron apps or Chrome extensions. Some event listeners may be injected to the page, but the remote page has no clue - and that's good. – Maciej Krawczyk Dec 17 '21 at 14:39
27

Since there is no native way to do this ,Here is the less intrusive solution i found (dont add any 'old' prototype methods):

var ListenerTracker=new function(){
    var targets=[];
    // listener tracking datas
    var _elements_  =[];
    var _listeners_ =[];
    this.init=function(){
        this.listen(Element,window);
    };
    this.listen=function(){
        for(var i=0;i<arguments.length;i++){
            if(targets.indexOf(arguments[i])===-1){
                targets.push(arguments[i]);//avoid duplicate call
                intercep_events_listeners(arguments[i]);
            }
        }
    };
    // register individual element an returns its corresponding listeners
    var register_element=function(element){
        if(_elements_.indexOf(element)==-1){
            // NB : split by useCapture to make listener easier to find when removing
            var elt_listeners=[{/*useCapture=false*/},{/*useCapture=true*/}];
            _elements_.push(element);
            _listeners_.push(elt_listeners);
        }
        return _listeners_[_elements_.indexOf(element)];
    };
    var intercep_events_listeners = function(target){
        var _target=target;
        if(target.prototype)_target=target.prototype;
        if(_target.getEventListeners)return;
        if(typeof(_target.addEventListener)!=='function'||typeof(_target.removeEventListener)!=='function'){
            console.log('target=',target);
            throw('\nListenerTracker Error:\nUnwrappable target.');
        }
        // backup overrided methods
        var _super_={
            "addEventListener"      : _target.addEventListener,
            "removeEventListener"   : _target.removeEventListener
        };

        _target["addEventListener"]=function(type, listener, useCapture){
            var listeners=register_element(this);
            // add event before to avoid registering if an error is thrown
            _super_["addEventListener"].apply(this,arguments);
            // adapt to 'elt_listeners' index
            var uc=(typeof(useCapture)==='object'?useCapture.useCapture:useCapture)?1:0;
            if(!listeners[uc][type])listeners[uc][type]=[];
            listeners[uc][type].push({cb:listener,args:arguments});
        };
        _target["removeEventListener"]=function(type, listener, useCapture){
            var listeners=register_element(this);
            // add event before to avoid registering if an error is thrown
            _super_["removeEventListener"].apply(this,arguments);
            // adapt to 'elt_listeners' index
            useCapture=(typeof(useCapture)==='object'?useCapture.useCapture:useCapture)?1:0;
            if(!listeners[useCapture][type])return;
            var lid = listeners[useCapture][type].findIndex(obj=>obj.cb===listener);
            if(lid>-1)listeners[useCapture][type].splice(lid,1);
        };
        _target["getEventListeners"]=function(type){
            var listeners=register_element(this);
            // convert to listener datas list
            var result=[];
            for(var useCapture=0,list;list=listeners[useCapture];useCapture++){
                if(typeof(type)=="string"){// filtered by type
                    if(list[type]){
                        for(var id in list[type]){
                            result.push({
                                "type":type,
                                "listener":list[type][id].cb,
                                "args":list[type][id].args,
                                "useCapture":!!useCapture
                            });
                        }
                    }
                }else{// all
                    for(var _type in list){
                        for(var id in list[_type]){
                            result.push({
                                "type":_type,
                                "listener":list[_type][id].cb,
                                "args":list[_type][id].args,
                                "useCapture":!!useCapture
                            });
                        }
                    }
                }
            }
            return result;
        };
    };

}();


ListenerTracker.init();

EDIT

Suggestion from @mplungjan: modified to listen to wrappable targets (singleton|constructor). 'init' tracks Element and window .

exemple with other wrappable target:

ListenerTracker.listen(XMLHttpRequest);

Suggestion from @kodfire : You may get optionals arguments with the args property.

yorg
  • 600
  • 5
  • 7
  • 1
    You should also make it intercept window event listeners. Other than that, this works great! –  May 03 '17 at 15:57
  • Can we use a similar script to intercept window.addEventListener? – mplungjan Nov 23 '20 at 09:08
  • The **window** object is a singleton and doesn't inherit from **Element**. If you want to intercept **window**.addEventListener, you will have to copy this code and replace **Element.prototype** and **HTMLElement.prototype** by **window**. – yorg Nov 24 '20 at 18:04
  • Awesome :)) Is it possible to call the `getEventListeners` in javascript and not in devtools? – kodfire Nov 02 '21 at 06:54
  • @kodfire Thank you. The tracker overloads HTMLElement event methods globally. So once you called `ListenerTracker.init();` every new event will be stacked and you can call `element.getEventListeners();` from any script. – yorg Nov 05 '21 at 23:50
  • @yorg Thank you back :). It just has the `useCapture`. How to add the value of `passive` too? – kodfire Nov 06 '21 at 06:50
  • @mplungjan Thank you for your suggestion. I edited the code accordingly. On the post where i suggested to copy/paste, i didnt have this pattern in mind. – yorg Nov 26 '21 at 22:44
  • @kodfire Thank you for your suggestion. Since the edit, If for exemple you use options as 3rd argument, you can get **passive** with **obj.args[2].passive** . – yorg Nov 26 '21 at 22:49
  • Thank you very much @yorg for your update. Now I want to get the caller location in order to add `{passive: true}` to them. How can I find them as they might be called from different places. I used `var caller_line = (new Error).stack.split("\n")[4];` inside `_target["addEventListener"]` function but the value is `undefined`. – kodfire Dec 19 '21 at 06:43
3

I can't find a way to do this with code, but in stock Firefox 64, events are listed next to each HTML entity in the Developer Tools Inspector as noted on MDN's Examine Event Listeners page and as demonstrated in this image:

screen shot of FF Inspector

Adam Katz
  • 14,455
  • 5
  • 68
  • 83
1

You can obtain all jQuery events using $._data($('[selector]')[0],'events'); change [selector] to what you need.

There is a plugin that gather all events attached by jQuery called eventsReport.

Also i write my own plugin that do this with better formatting.

But anyway it seems we can't gather events added by addEventListener method. May be we can wrap addEventListener call to store events added after our wrap call.

It seems the best way to see events added to an element with dev tools.

But you will not see delegated events there. So there we need jQuery eventsReport.

UPDATE: NOW We CAN see events added by addEventListener method SEE RIGHT ANSWER TO THIS QUESTION.

Rantiev
  • 2,121
  • 2
  • 32
  • 56