A detailed guide to debugging JavaScript code in Chrome Devtools. Stopping when DOM changes
Imagine that you are working on this incredible new web application and your testers have asked you to fix the following bugs:
- In Firefox, you must make sure that you have installed Firebug extension. Select “Tools > Firebug > Open Firebug”.
- In Opera 9.5+, select “Tools > Advanced > Development Tools.”
- In IE beta, go to “Tools > Panels > Explorer Bars > IE Developer Toolbar.”
- In Safari or WebKit, first enable the debug menu (1), then select “Develop > Show Web Inspector”
rice. 1: Initial view of our JavaScript application in Dragonfly and Firebug, respectively.
When you look at the source code in the debugger, notice the clearLoadingMessage() function at the very beginning of the code. This is a good place for a checkpoint.
How to install it:
When the page is reloaded, the script will stop running and you will see what is shown in Figure two.
rice. 2: debuggers stopped at control point inside clearLoadingMessage.
Let's take a look at the function code. As you can easily see, it updates two DOM elements, and line 31 mentions the word statusbar. It looks like getElements("p", ("class":"statusbar")).innerHTML looks for the statusbar element in the DOM tree. How can we quickly test our assumption?
Paste this statement into the command line to test. Figure three shows three screenshots (Dragonfly, Firebug and IE8) after reading innerHTML or outer HTML element, returned by the command you are researching.
To check, do the following:
* In Firebug, switch to the “Console” tab.
* In Dragonfly, look below the JavaScript code panel.
* In IE8, find the "Console" tab on the right.
rice. 3: Output the command result in Dragonfly, Firebug, and IE8, respectively.
The command line is very useful tool, which allows you to quickly test small pieces of code. The Firebug console integration is very useful - if your command outputs an object, you get a very intelligent view. For example, if it is a DOM object - you will see the marked up result.
You can use the console to do more in-depth research. JavaScript string, which we are studying, does the following three things:
Surprise, at the end... nothing. Thus, the expression getElements("p",("class:"statusbar"")).firstChild points to some object in the DOM that does not contain any text, or does not have an innerText property.
Then, the next question is: what actually comes first? child element at the paragraph? Let's ask this question at the command line. (See fourth picture).
rice. 4: command line StDragonfly debugger, output [Text object].
The Dragonfly's debugger output - [Text object] shows that this is a DOM text node. Thus we found the cause of the first problem. A text node does not have an innerText property, hence setting p.firstChild.innerText to a value does nothing. This error can easily be fixed by replacing innerText with nodeValue, which is a property defined by the W3C standard for text nodes.
Now that we've dealt with the first error:
To find where the localization problem is likely occurring, do the following:
rice. 5: Search in Dragonfly and WebInspector.
To check what this function does:
var str1 = navigator.userAgent.match(/\((.*)\)/);
var ar1 = str1.split(/\s*;\s*/), lang;
for (var i = 0; i< ar1.length; i++){
if (ar1[i].match(/^(.(2))$/))(
lang = ar1[i];
}
}
As you step through the code, you can use the Local Variables Viewer. Figure 6 shows how it looks in Firebug and IE8 DT; we expanded the ar1 array to see its elements.
rice. 6: Pane for viewing local variables of the getLanguage function in Firebug IE8’s
The expression ar1[i].match(/^(.(2))$/) simply searches for a string consisting of two characters, such as “no”, “en”. However, as you can see in the screenshot in Firefox, information about the language is presented in the form “nn-NO” (2). IE does not put language information into the user agent at all.
Thus, we found the second error: the language was determined by searching for a two-letter code in the user agent line, but Firefox has a five-character language designation, and IE does not have it at all. Such code must be rewritten and replaced with language detection either on the server side using the Accept-Language HTTP header, or by retrieving it from navigator.language (navigator.userLanguage for IE). Here is an example of what such a function could be
function getLanguage() (var lang;
if (navigator.language) (
lang = navigator.language;
) else if (navigator.userLanguage) (
lang = navigator.userLanguage;
}
if (lang && lang.length > 2) (
lang = lang.substring(0, 2);
}
return lang;
}
Mistake Three: The Mysterious “prop” Variable
rice. 7: In the Firebug and Dragonfly variable view panel, the global prop variable is visible
In Figure 7 you can clearly see the “prop” variable. In well-written applications, the number of global variables should be kept to a minimum, since they can cause problems when, for example, two parts of the application want to use the same variable. Let's assume that tomorrow another team will add new features to our application and also declare the "prop" variable. We'll end up with two different pieces of application code using the same name for different things. This situation often leads to conflicts and mistakes. You can try to find this variable and declare it local. To do this, you can use the search, as we did in the previous case, but there is a smarter way...
Debuggers for many other programming languages have the concept of a “watch,” which enters debugging mode when a specified variable changes. Neither Firebug nor Dragonfly support "observers" currently, but we can easily emulate similar behavior by adding next line to the beginning of the code under study:
__defineSetter__("prop" , function () ( debugger; ));Do the following:
When you reload the page, code execution will immediately stop where the "prop" variable is defined. The actual stop will occur at the point where you added the above line. One click on the “step out” button will take you to the location where the variable is set.
for (prop in attributes) (if (el.getAttribute(prop) != attributes) includeThisElement = false ;
It's not hard to notice for loop in which the prop variable is declared without keyword var, i.e. global. Fixing this is not difficult, just add var and fix the error. Error four: the “clone” attribute, which should not be there. The fourth error was apparently discovered by an advanced tester using the DOM inspector, since its existence does not appear in any way in user interface applications. If we open the DOM inspector (in Firebug this is the “HTML” tab, in Dragonfly it is called “DOM”), we will see that many elements have clone attribute, which shouldn't exist.
rice. 8: Dragonfly’s DOM inspector shows problematic code.
Since this does not affect the application's users in any way, this bug may not be considered serious, but do not forget that it can significantly affect performance, since the script sets the attribute on hundreds and thousands of elements.
Most quick way The solution to this problem is to set a breakpoint that fires when an attribute called clone is set on some HTML element. Can debuggers do this?
JavaScript is a very flexible language, and one of its strengths(or weak ones, depending on your point of view) is what you can replace basic functions language with your own. Add this piece of code to the page, it will override system method setAttribute, causing the code to stop when the "clone" property is set:
Element.prototype.setAttribute = function (name, value) (
if (name == "clone" ) (
debugger; /* stop the script */
}
funcSetAttr.call(this ,name,value); /* call a previously saved system method so that normal properties are set correctly */
};
So, we do the following:
When execution stops at a breakpoint, you'll want to know where the setAttribute() call occurred, meaning you'll need to go back up in the function call chain and see what's happening there. You can use a call stack for this.
rice. 9: Call stack in Dragonfly and IE8.
Figure 10 shows the stack in Firebug. In line " setAttribute