Using the Chrome Debugger Tools, part 3: The Sources Tab
A few months ago, I started putting together a series of tutorials on the excellent Chrome debugger tools. Having explored the Elements tab
and the Network tab, I'll continue to work left-to-right and look this month at theSources
tab that functions almost as a full-fledged
in-browser IDE and is especially useful for Javascript developers. I suspect that most users of the Chrome debugging tools started out exploring the Source
tab and discovered the rest of its functionality later.
Source Level Breakpoints
At its simplest, the Sources tab allows you to set breakpoints and evaluate expressions in Javascript whether that Javascript was loaded from a separate file or was part of the page as illustrated in figure 1, below, which is a screen shot of the very simple Example 1.
Figure 1 isn't very interesting because there's nothing going on; the page has already been loaded. You can, of course, view the page source from here, but you can see that in the Elements
tab,
with a nicer, pretty-printed view. The Source tab has an edge over the Elements tab if the page includes any Javascript, though. If you click the "gutter" next to the source listing where the line numbers are, a blue chevron symbol appears, indicating that
you have set a breakpoint. If a breakpoint is set on the page, the browser will pause page rendering when that piece of Javascript is executed. Since the single line of Javascript in figure 1 is "inline" (that is, not contained inside a function), it's executed
as soon as it's parsed. Figure 2 demonstrates the browser view if you set a breakpoint on line 5 and refresh the page.
A few interesting points to observe about figure 2. First, the browser display area is completely blank — the <body>
tag hasn't been encountered at this point, so the
document doesn't have a body yet. The "loading" icon in the upper-left corner of the browser (not shown in figure 2) continues to spin as the page load is paused awaiting instruction. In this case, there's not much interesting to do — but take a look at the
tooltip along the top that says "Paused in debugger". To the right of the text you see a right-pointing triangle and an arrow. The first tells Chrome to finish loading the page (or continue to the next breakpoint if one is encountered) and the second tells
Chrome to run the highlighted line and stop at the next one.
These same two icons are duplicated in the area to the right of the source listing, along with four others I'll discuss more below. For now, just press the resume triangle and let the page finish loading.
The Call Stack
Example 2 is a slightly more interesting page that computes Fibonacci sequences (although
the implementation is just a little bit contrived to show some of the pieces of the debugger). Setting a breakpoint on line 9 and reloading as shown in figure 3 doesn't cause anything to happen immediately, because the function fib
isn't called
until you input a value for n
and click the Compute
button. (If you're following along, make sure to put a number in for n
).
Now, when you input 8
, to compute the 8th Fibonacci number (which is 21) and click Compute
, the browser pauses as shown in figure 4.
Notice the program status area to the right of the source code listing. It consists of 7 headers that can be expanded or collapsed individually: Watch Expressions
, Call
Stack
, Scope Variables
, Breakpoints
, DOM Breakpoints
, XHR Breakpoints
, and Event Listener Breakpoints
. This area is responsible for giving you a view of what's going on in the Javascript
"program" right now — that's why it was mostly empty in figure 3, since the Javascript code wasn't running. In figure 4, it has a lot more to tell you.
Scope variables
header is itself split into
two subsections in figure 4: Local
and Global
.
If you peek back at figure 2, you'll notice that Example 1 showed one Global
variable named Window
,
just as in figure 4. This illustrates a peculiarity of Javascript (at least, as opposed to many other programming languages like Java and C). A Local
variable
is a variable declared with the Javascript keyword var
. But as you may know, var
is
not required in Javascript — you can legally declare a variable such as:
x = 2;This is not a local variable and won't show up under the local variables list; instead, it is appended as a property of the global scope, which is always defined and which there is always exactly one of. Use the
step over
command to step down to line
11 to see this in action — the loop index variable i
is declared without the var
keyword,
so it appears not as a local variable, but as a property of the global variable as shown in figure 5.
In this case, the global scope has a name and that name happens to be Window
. Global is always defined and, in client-side Javascript, always an instance of "Window".
Figure 4 shows an additional local variable n
— parameters passed to functions always become local variables.
Now, click step in
and you'll jump into the fib_elem
function, shown in figure 6. Observe that the Call Stack
grew by one entry; each time a
function is called, the call stack keeps track of where the call was made and keeps a reference to it. Here you can now see that (at this point in the execution), fib_elem
was called by fib
which was initially called by onclick
.
Click on the fib
invocation in the call stack and you can see what n
was when that level of the call stack was active. In addition, the source code listing highlights the line that made the original function call.
That's not where the code actually is, though; if you were to click step over
, you'd return to the current level on the call stack. Before you do, though, you can actually
peek further back up the stack, to the "onclick" call that started the whole thing, as shown in figure 7.
Here you can see that in this context, there were two local variables: event
and this
. Here, though, the Scope Variables
shows that there's
a lot more going on — there are three With
blocks and a Closure
that are inserted by the Javascript runtime (i.e. Chrome itself). If you look at the source for example 2, you see that the button handler is declared as:
<button onclick="getElementById('out').value = fib(getElementById('n').value);">Compute</button>
the function call getElementById
is
not global in Javascript — it's a method of the Document
object. So how does this code actually work? The with
block
creates an implicit reference to #document
that allows functions ondocument
to
be called implicitly. Click the step out
button to return back to the original fib
call.
The button to the right of the step out
disables breakpoints so that the debugger doesn't stop when they're hit, but leaves them
in place so you can easily reactivate them.
Event Listener Breakpoints
So far, this is pretty boilerplate source-code debugger stuff; if you've used any other IDE tool like Eclipse, Visual C++ (or even GDB), this should be old hat to you and even if
you haven't, you can almost definitely see the utility immediately. There are a few extensions, though, that are fairly specific to Javascript execution, particularly in a browser. For instance, I showed you in figure 7 how you can walk up the stacktrace to
the browser-level action that triggered a Javascript handler; but what if I had coded that handler in such a way that it didn't call a function in the source code? What if I wanted to actually pause in the onclick
handler itself? You might think
you could just set a breakpoint on line 21, where the onclick
handler is declared. And while Chrome will dutifully put a breakpoint icon next to line 21 to make you think that it will stop there when that line is executed (i.e. the button is clicked),
the browser just sails past it.
This is where Event Listener Breakpoints
come in handy. If you expand the Event Listener Breakpoints
header in the debugger, you'll see a categorized list
of every action that client-side Javascript can attach handlers to. In this case, the one you're interested in is under Mouse
— specifically, the click
handler. Figure 8 shows the click
handler being invoked for the button
click from example 2. The scope variables
section is the same as it was in figure 7, but this time the call stack only shows one level, since the top-level handler hasn't made any invocations yet. It's important to recognize that the event listener
breakpoints only break if you have a listener on the event in question — for example, the input boxes can have click events set on them, but if your code didn't attach a listener to them, the debugger won't break. It will break whether you used an
HTML on attribute as I did in Example 2, or a more modern:
document.getElementById("n").addEventListener("click", function() { alert("clicked"); });
or even a jQuery event handler (unfortunately, in the latter case, you'll be dropped into the jQuery runtime).
Pause on Exceptions
I've talked about five of the six action buttons at the top of the program status area: pause/play, step over, step in, step out and disable breakpoints. The last one is Pause
on Exceptions
. Example 3 invokes the single line of Javascript:
window.nonexistent.call();
which will cause Chrome to throw a TypeError
since
I tried to call a function on the nonexistent window property nonexistent
. However, if you load the page, you won't see any indication
that anything went wrong. Since Javascript halts execution on an error, though, the code below this line will never be invoked, leaving you to wonder what was wrong with your page. Clicking the pause on exceptions
as
shown in figure 9 at least lets you see what went wrong, although you can't recover from it. The description of the error itself appears a yellow area underneath the control toolbar. Pausing on exceptions is almost always useful, and I leave it active almost
all the time.
You may have noticed that, when you activated Pause on Exceptions
a new checkbox appeared labeled Pause on Caught
. This checkbox doesn't seem to change the
behavior of the page and for example 3, it doesn't. However, in Example 4, I've surrounded the erroneous call with a well-intentioned-seeming try/catch
block:
try {
window.nonexistent.call();
} catch (e) {
console.log("Oops, nonexistent was nonexistent");
}
In some ways, though, this is even worse, because the part outside the try
handler did run,
so you really don't have any indication that anything was wrong unless you happen to look at the console. If you click the Pause on Caught
checkbox,
though, you'll be notified that you had a problem as soon as it happens.
Watch Expressions
Watch Expressions
toward the top, allows you to input an expression that will be evaluated every time the debugger pauses, including after the page has fully rendered.
If the expression is meaningful relative to the Scope Variables
you'll see its value; if not, Chrome will just report that it's <not available>
. You never need watch variables — you can always determine variable values from
the Scope Variables
, but watch variables do come in handy from time to time over the course of a long debugging session.
Breakpoints
So far, I've discussed the buttons along the top, the Watch Expressions
, Call Stack
and Scope Variables
. The remaining four debugger tool headings
— Breakpoints, DOM Breakpoints, XHR Breakpoints and Event Listener Breakpoints
— all show you lists of breakpoints that you have set. The simplest is the first, just labelled Breakpoints
— it shows you where in your source code you
have set a line-level breakpoint. If the Javascript execution engine reaches any of these lines, the engine will pause and the debugger tools will become active as shown in figure 2. You can uncheck the check box next to the breakpoint and that will cause
Chrome to ignore this breakpoint until you re-check it; helpful if you have to set a breakpoint inside a loop for any reason. The Breakpoints
are lists of any breakpoint in any source file that was loaded by this page.
Chrome will also do its best to keep track of breakpoints that you've set and reload them when you re-open the page at a later time. This means that if you have two or more pages that share a Javascript source file, the breakpoints you set in one page will still be active when the other is loaded. However, if you modify the source file outside of Chrome, the breakpoint will stay on the same line that it was one when you created it initially — even if that logical line has moved up or down in the source file. What if you delete the line entirely? In this case, the breakpoint will still remain, unreachable, until you decide to remove it or extend the source code file to include code at that line in the future.
DOM Breakpoints
What about DOM Breakpoints
? If you expand the disclosure triangle, there doesn't appear to be any way to add one. Instead, as shown in figure 10 below, the only way to
create a DOM Breakpoint
is to navigate over to theElements
tab, right click on a DOM element (i.e. one of your HTML tags) and select one of the Break on...
options. As you can see, there aren't very many.
XHR Breakpoints
Below DOM Breakpoints
are XHR Breakpoints
, the last remaining subheading of the debugger tools.
XHR is shorthand for XMLHttpRequest
, which is the workhorse of the development pattern called AJAX
. AJAX
itself is an acronym for "Asynchronous Javascript and XML", and as such, is initiated by an XMLHttpRequest
object.
The "X" both at the end of AJAX as well as in the first word of XMLHttpRequest
are fairly misleading as AJAX does not actually require XML at all. In most cases these days, AJAX is done using the Javascript-friendlier JSON notation instead.
Clicking the "+" icon next to XHR Breakpoints
as shown in figure 11 will allow you to break on any XMLHttpRequest or only one that contains a specified substring in its
URL.
Example 5 is a simple "dynamic" web page that loads its content through an XMLHttpRequest
invocation. If you've enabled Break on any XHR
, the script will pause as shown in figure 12 before the send
call. This is most useful if the page does a lot of dynamic update based on AJAX calls and you need to see what the page looks
like before the call returns.
Async call stack
Example 5 is also useful to demonstrate the Async
checkbox next to the Call Stack
that I glossed over previously. "Async" is short for "Asynchronous" (the
first A
in the acronym AJAX
) and is another term for multi-threaded. Although Javascript doesn't offer a facility for creating or managing multiple threads of execution, the browser can do so on your behalf as in the case
of an XMLHttpRequest send()
call. As demonstrated in figure 13, if theAsync
checkbox is enabled, you'll be notified where, in the calling thread, this invocation originated.
Some other sources of asynchronous calls in Javascript come from setTimeout
and newer HTML5 features like Web Workers.
Script sources
All of the previous examples have been pretty simplistic, to demonstrate a single aspect Chrome's debugger tools. However, most web interfaces are much more complex, and Chrome has
a lot of support for taking this into account. Figure 14 shows the source
list when you load Amazon's home page. Here you see that all of the source files that were loaded are
organized by domain and path. This view can be particularly helpful when trying to diagnose cross-domain scripting and cookie-sharing issues. The sources list is updated dynamically so that when, as is often the case, scripts load other scripts, you can see
that they're there.
Although the sources view shows you from which domain and path the script was loaded, it doesn't tell you which source file was responsible for bringing it into scope. You may be
surprised one day to find, for instance, that EXT-JS has suddenly been brought into your web page, causing strange conflicts. You probably suspect that an advertiser is responsible, but which one? All you can tell from the Source
tab is that it
was loaded from the domain www.sencha.com
. Chrome does offer you a way to trace the script back to its ultimate source — right click on the script under Sources
and select Reveal in Network Panel
. That will take you to
the Network Tab which has a column for the "Initiator" of each web artifact. However, you'll only see the Reveal in network panel
option
if the chrome debugger tools were opened when you loaded the page in the first place; you may have to open the debugger tools and reload the page to see it.
Minification and source maps
Most of the time, though, Javascript isn't deployed like the examples I've shown so far, with embedded <script>
tags — instead, it's separated out into external source
files and usually minified before being deployed into production. Example 6 is a minified version of example 2's Fibonacci computation, after having run the Javascript
through UglifyJS. Although compression is hardly worthwhile for this simple example, it demonstrates how compressors affect more complex pages. If you load the example, you see
that the whole Javascript source has been reduced to a single line as shown in figure 16.
All source files are subject to "pretty-printing" if you click the Format
icon in the lower-left corner. When you do, Chrome attempts to follow a set of readability rules
to make the source more human-friendly as shown in figure 17. Notice that when you click it, the button disappears and the tab reminds you that it's "formatted". If you want to see the original, just close the formatted tab.
However, if you compare the pretty printed Javascript with the original javascript source file, you'll see that there are still quite a few differences; UglifyJS renamed all of the
local variables to nondescript i
and e
. Minifying is done partly to save network bandwidth but also as a form of obfuscation to make the job of a would-be hacker just a little bit harder than it otherwise might be.
And when I say "a little bit harder" I really mean "a little" bit. Don't rely on code obfuscation to secure your site — if your site wouldn't be secure without obfuscation, it's not secure with it, either.
If you have a problem that you can only reproduce on your live site (perish the thought), and you need to troubleshoot it using Chrome's debugging tools, you can always load the live site with its minified code, pretty print it, and try to step through that way. I've found, though, that breakpoints aren't nearly as reliable with pretty-printed code, and it is helpful to see the meaningful variable names that you spent so much time thinking up when you were writing the code in the first place when trying to trace through why something isn't working. In this case, you don't have to install the development source code in production just to pinpoint a problem; Chrome actually allows you to replace the live code "in place" with the original source code by using a source map.
The source map
option only appears on the original unformatted view; close the formatted tab if it's open, right click on the source area (not the name of the file on
the left) and select Add source map
as shown in figure 18.
The pop-up shown in figure 19 will prompt you to input the path to your source map; this can be a public URL but will most likely be a file artifact on your local system — if so,
you'll have to provide a full file URL such asfile:///Users/jdavies/Workspace/site/art05ex9_src.js
.
Once you do, the original will appear, and you can interact with it just as if you had loaded the page from your local system — in many cases, when you use AJAX, this can end up being the only way to track down an issue.
So how did I get this source map? There's a published
standard, but almost all minifiers will create one for you if you ask. The minified code in example 8 was generated from the source file art035ex8.js:
function fib_elem(arr, i) {
return arr[i-2] + arr[i-1];
}
function fib(n) {
var arr = [1,1];
for (i = 2; i < n; i++) {
arr.push(fib_elem(arr, i));
}
return arr[n-1];
}
via:
$ uglifyjs -m -c --source-map art035ex8_src.js art035ex8.js > art035ex8_min.jsWhich produced the "uglified" art035ex8_min.js:
function fib_elem(i,e){return i[e-2]+i[e-1]}function fib(e){var f=[1,1];for(i=2;i<e;i++)f.push(fib_elem(f,i));return f[e-1]}The generated source map looks like:
{ "version":3, "sources": [ "art035ex8.js" ], "names": ["fib_elem","arr","i","fib","n","push"], "mappings":"AAAA,QAASA,UAASC,EAAKC,GACtB,MAAOD,GAAIC, EAAE,GAAKD,EAAIC,EAAE,GAGzB,QAASC,KAAIC,GACZ,GAAIH,IAAO, EAAE,EAEb,KAAKC,EAAI,EAAGA,EAAIE,EAAGF,IAClBD,EAAII,KAAKL, SAASC,EAAKC,GAGxB,OAAOD,GAAIG,EAAE" }As you can see, the source map is just a standard JSON file. All of its elements are pretty straightforward, until we get down to the
mapping
section.
The mappings section is a list of where in the minified/"uglified" source the corresponding invocation can be found in the original source. In the source map above, you can see that there are 33 comma-separated values, which represent a compacted mapping of
the identifiers in the generated code back to the original code so that events that occur in the generated/minified code can be traced back to the original and debugged accordingly.
In fact, Chrome is so source-map aware that if the minified code includes a comment in the form:
//# sourceMappingURL=art035ex6_src.js
Chrome will process the comment as an execution instruction and load the source map automatically from the given URL — in this case a local
file. UglifyJS will produce such a comment for you automatically (I deleted in manually
from example 6), but unless you override it, it will be the direct path on your local file system to the generated source map. You can override it with a networked URL where you plan to install the source map; I don't find this to be very useful since I then
have to keep the installed source map up-to-date with each new build, but if this sounds useful to you, have a look at the uglifyJS documentation.
Part 4: The Timeline Tab