This is a debugging story of how to determine what event listener is calling event.preventDefault()
or event.stopPropagation()
or some other completely random thing which is not allowing your intended action.
The Problem
We had this form in which when we clicked on the input box it would not focus. So clearly there was something which was listening either on the focus of this input box or was listening on the blur of something else and would try to give focus to some other input box which was not there or hidden on the screen.
Now we have a huge code with lots of spaghetti code tapped up which somehow just works. (We have React and Backbone, we have jQuery and Underscore, we have it all. We are close to finishing our migration) So it was very difficult to find out what was causing this. Event Listeners were added from many places and no track of how many there are.
Using Chrome DevTools to find DOM Modifications
To find out if something is modifying the DOM is very easy in Chrome DevTools, just select the element in the elements pane, right click and then you can select to break on subtree modifications, attribute modification and node removal.
But for our current problem, the DOM is not being modified, so this would not help here.
List all DOM Event Listeners for a particular event
There is a Event Listeners tab just beside Styles in the Elements Panel in Chrome DevTools. Over here there is a list of all Events and the Event Listeners added for that Event over all the ancestors of the current DOM Element selected in the Elements Pane. (Sounds like a Tongue Twister 😂)
Here there are options to remove a listener or you can go to the listener function definition. But if you are using any framework like jQuery or React, it will take you to its internal functions, so this was not going to help.
So we thought, lets remove listeners, we might get lucky. We removed listeners for focus, blur, click etc. seeing one after another if this was the one which was hindering us. But alas we removed all listeners on all events on all the ancestors but we were still not able to focus on our input box.
What does this tell us?
This did tell us one thing, that the listener is not listening on focus or click events but its listening on blur event of some other element which is in a disjoint tree of our element. We did a search for 'blur', "blur" & onBlur in our repository and found more than 25 instances. Crap. This is going to take long. My colleague started adding debugger statements everywhere.
Now we can add an event listener on window for focus in the capture phase and we can find the culprit DOM which steals the focus, but it does not give us the culprit Function which sets its focus. We would need some additional steps to find that.
Can we do better? Enter Timeline.
You can record a flamegraph of everything that happens in the browser from script to rendering in the Timeline Panel. So we started recording, clicked on the input box and stopped and voila we found the event and its listener.
Click on the name takes you the definition in the Sources Panel and there it was:
Looking at the pre-transpiled code, we found the author had left a comment.
parent[0].addEventListener('blur', function(e) {
if (!e.relatedTarget) {
return;
}
// if tab key is pressed, and something out of modal-container is focused, snatch it back.
if (!$(this).find(e.relatedTarget).length) {
return this.focus();
}
}, true);
What??
If something else is focused, snatch it back. What?? What is a piece of code like this doing?
So this code was added in the modal library code, so that if someone clicks on tab or shift + tab and there are other input boxes behind the backdrop of the modal, the cursor might move there, or if you have added a overflow hidden on your body and your input box is at the bottom of the screen, the browser might scroll down and all the user might see is the backdrop.
So this was pretty neat piece of code. Kudos to Pranav Gupta for adding it.
So what did we do wrong?
So as I mentioned above we are migrating our codebase from Backbone to React. What we had done was we had opened a React Modal (Modal written using React) on top of a Backbone Modal. And we had made two different containers, one for all Backbone Modals and one for all React Modals and that is why losing focus out of an active modal caused this issue. Some little conditions were added and we fixed this.
Take Away - Chrome DevTools Timeline
Well this might not be the most difficult bugs of all time, but using the Timeline can be an effective tool in finding what exactly is happening, how slow or fast an operation is. It can be easily used to find a performance degradation. So Start Recording.
Do you think there is a better way to debug this? (Other than the obvious way to write better code 😂) Do mention that in the comments. If you learnt something new, do recommend this story.
Top comments (3)
Nice article, I did not know that Chrome could add breakpoints when an element on the DOM changes.
That is a great tip, thanks!
Would love to see more like this! (how to get feedback in different environments)