In this third part of our Chrome Developer Tools series, we’ll review how to modify and debug JavaScript. Optimization is an important part of the development process, especially for performance-critical applications. We’ll also discuss techniques for identifying potential bottlenecks in our code.
As in the previous two articles, I’ll be focusing on the features found in the Chrome Canary build (version 26.0, as of this writing). I’ll cover the Sources and Timeline panels.
Sources Panel
The Sources Panel is the go-to place for JavaScript debugging. This panel, coupled with the Console Panel, makes for an extremely powerful tool! It’s a point-and-click interface that lets you pause JavaScript execution and inspect all the variables and objects in the current scope.
- The Sources Panel. If you do not see as many scripts as you expect, refresh the page with the Sources panel open.
- This pane can be either hidden, auto-hidden or fixed. Click on the small icon to the right of ‘Content scripts’ to toggle between these states. This pane can be resized.
- The Sources tab within the left-hand side pane; you’ll likely have this tab open most of the time. The resources it lists are separated by sub-domain, and you can expect to see CSS, JavaScript and HTML within the tab.
- The Content Scripts tab (not active in the screenshot) may at first display many oddly named scripts. These are in fact Chrome extensions that loaded on the page. This is useful for debugging actual extensions. Otherwise, you can avoid seeing them by opening your page in an incognito window; most extensions are disabled by default in incognito mode.
- The main content pane displays the contents of the selected script. Selecting multiple scripts creates a tabbed interface similar to an IDE.
- This pane contains sub-panels that provide useful JavaScript debugging utilities. At the top of the pane are the icons to step through your code.
- Watch Expressions does exactly that, it ‘watches’ expressions you have typed in. If you find yourself wanting to know the value of the
this
keyword at the various stages of a JavaScript program, you can watch thethis
keyword to see its different values over time. Click the add button to add an expression, and if an expression doesn’t update, hit the small refresh button next to the add button. - XHR Breakpoints enables us to halt JavaScript code execution when making an Ajax request. We get even more control over this behavior by supplying a value in the ‘Break when URL contains’ field, which pops up when you hit the add button. Providing no value causes the debugger to break uponany XHR request.
- Event Listener breakpoints allow you to set break points for specific events. The screenshot only lists the top level categories. For example, ‘Timer’ has the following individual event listener breakpoints: ‘Set Timer’, ‘Clear Timer’ and ‘Timer Fired’.
- If you encounter minified code, selecting ‘Pretty Print’ acts as a JavaScript beautifier.
You may find it useful to edit your CSS and JavaScript in the Developer Tools.
Sources Tab
The sources tab lists resources grouped by the sub-domain they are served from. Each resource has a context menu (revealed by right-clicking on the resource) with a set of common options. One option, however, is very interesting: Local Modifications, which we’ll look at later.
Note: You can view the source file list as a flat list (i.e. not contained within folders grouped by sub-domain) by unchecking ‘Show folders’ in Settings > General.
Clicking on a resource displays it in the main content pane. Don’t forget to enable pretty print mode for minified resources, as some minifiers rename variables to can make the code harder to understand. Hopefully, more developers will generate source maps in the future, making it easier to work with minified code.
You can edit most files in the main content pane, and those changes are immediately reflected in the browser. After making changes to a resource, the context menu presents you with the ability to Save (albeit not permanently) or Save as (saves a new version locally). When working with your own local sites, you may find it useful to edit your CSS and JavaScript in the Developer Tools instead of your IDE. Saving modifications, in this case, modifies the actual source file. Tools such as Tincr or chrome-devtools-autosavecan help with automating this workflow.
The resource’s context menu also provides the option to reveal the resource in the network panel.
Revisions
A revision is a new point within a resource’s lifetime, upon which it has been modified. Editing and saving code from the sources panel creates a new revision, and style changes made within the Elements panel actually triggers a new revision!
After you’ve made a few changes, you can right-click on the resource and select Local Modifications. This opens a new Local Modifications pane which contains a diff of the original and edited files. Within the Local Modifications pane, we can revert a modified source file in its entirety (useful for when you’ve made a large number of unwanted changes) by clicking ‘revert’ next to the filename.
Main Content Pane
Developer Tools is kind enough to alert us to potential optimizations.
The main content pane has many of the features you’d find in basic code editors: line numbers, syntax highlighting, the ability to create tabs and ‘undo’ functionality. While it doesn’t match a fully-fledged IDE, it’s not bad for quick editing.
Breakpoints
Breakpoints lets us halt JavaScript code execution and inspect the current environment. For example: assume we have a simple
for
loop which pushes items onto an array. Our goal is to precisely understand the inner workings within each iteration of the loop. We could very easily use console.log
to log the variables we want to inspect. While this technique would give you your desired results, it is certainly not the most efficient debugging technique. By using breakpoints, we can pause code execution while inside the for
loop and inspect all variables within the context’s scope.
1
2
3
4
5
| var arr = []; for ( var i = 0; i < 3; i++) { var num = i; arr.push(num); } |
To add a breakpoint, click on the line number; you can also right-click on the line number and choose the “Add Breakpoint” option. After setting your breakpoint, refresh the page and notice that breakpoints persist between page loads. If the code has yet to execute (for example, the breakpoint was set inside a click event handler), then you can initiate the code execution without a page refresh.
You can hard code a breakpoint in your code by using the
debugger;
statement in your code. Developer Tools (and most JavaScript debuggers) recognizes this as a breakpoint.
When reaching a breakpoint, the page tints grey and the line of code highlights blue. At this point, we can hit the escape key to display the console panel. What’s interesting is that the code written and executed within the console (while paused on a breakpoint) actually executes in the currently paused context! Typically, the
this
keyword refers to the global window
object; whereas viewing this
in an click
event handler shows the value as the event target (an element).
In the screenshot above, the grey portion is the document itself, and Developer Tools highlighted the current line of JavaScript. In the console, you see the results of inspecting a few variables. And to the right of the content pane is the “Scope Variables” pane, where you can inspect all the variables and objects in the current scope.
Conditional Breakpoints
Conditional breakpoints let you break when a certain condition is true.
In the right-hand side pane of the Sources panel, you’ll notice the XHR Breakpoints tab. Click the ‘Add XHR Breakpoint’ on your favorite Ajax-enabled site. Leaving the ‘URL Contains’ field empty breaks on any XHRrequest.
This presents developers with new and powerful opportunities. We can navigate to a site we haven’t built, nor had any involvement with, and simply start debugging code based on certain events and criteria. So pausing on Ajax requests is nice, but what other events can we break on?
Event Listener Breakpoints
The sources panel has a point-and-click interface for setting breakpoints which match certain event listeners. Note that breaking on a particular event only works when the page in question is listening for that event. If we break on the Control > Resize event, having code like the following ensures the code breaks when the event fires:
1
2
3
| window.onresize = function (e) { console.log(e); }; |
So, when is breaking on particular events useful?
Breakpoints persist between page loads.
- When playing that new HTML5 game, and you want to know what’s going on in its main game loop. Try setting the Request Animation Frame and Timer event listeners and check out what’s going in each event.
- That new responsive JavaScript plugin that nicely lays out the page upon a window resize seems pretty cool. But as developers, we want to know what the code does when we resize the window. Try setting the Control > Resize event listener breakpoint, and you can do just that. Note: You will more than likely have to step through a lot of library code. Try it on the jQuery version of the Masonry plugin, and note how stepping through the code upon a resize breakpoint ends up taking you through a lot of the jQuery internals.
- Many websites, including GMail, allows the user to paste content. The Clipboard > Paste breakpoint would be useful in this case.
- Other useful breakpoint options include: form submission, copying data, DOM mutation events, device orientation, key presses, Ajax requests, mouse events (hovering, moving, clicking etc), setInterval, touchstarts and more.
DOM Breakpoints
The DOM Breakpoints tab displays breakpoints for the DOM. A simple way to see this in action is to find an element which, for example, when clicked has its
className
property changed. Locate the element in the Elements panel, right-click it and go to Break On > Attributes Modifications. The code will will now break when that element’s attributes’ values change.
1
2
3
| document.querySelector( '#button' ).onclick = function () { this .setAttribute( 'some' , 'thing' ); }; |
The
onclick
event handler above counts as an attribute modification, which presents something similar to this:
Other options include:
- Subtree modifications occurr when any node in the tree is inserted or removed.
- Attributes Modifications occurr when changing an element’s attribute.
- Node Removal fires when removing an element; for example:
element.remove()
.
Note: Chrome seems to have implemented the remove() method. Some browsers don’t support this method; so, removeChild() will need to be used instead.
Timeline Panel
The Timeline panel is where you investigate your web app’s performance issues. The Timeline panel’s primary purpose is (at the time of writing) for viewing information, as opposed to the other panels that allow you to perform destructive actions on the page.
Times you may want to use the Timeline panel include:
- Investigating the scrolling performance of your page.
- Trying to improve your animation’s FPS.
- Building mobile web pages that are likely viewed on a variety of old and new devices.
- Making the web jank-free!
- These three items (Events, Frames and Memory) present different views, each illustrating various pieces of performance-related information.
- In this frames view, each bar represents a frame rendered by the browser. The more ‘full’ each vertical bar is, the worse the performance, and the colored portions in the bar are explained in the legend at the bottom of the Timeline panel.
- A popover for individual records, detailing how long arecord took to execute. The popover will, in some cases, link to the line of code that caused the record to execute (this is more likely to happen with script based records).
- The list of records. Some records are triggered by our code; other records may be caused by the user’s actions. For example, scrolling the page causes a ‘Paint’ event.
- Each record has a corresponding row that illustrates the length of time it took to complete. As shown in the screenshot, you can open some bars by clicking the dropdown arrow.
- Filtering options that dictate what records to display. All records are shown by default. If you’re investigating a particular type of performance issue, you could clean up the recorded records by using the filtering options.
- By default, all records are shown regardless of how long they took to complete. If you want to fish out those unusually long records, change from ‘All’ to one of the pre-defined options (such as 15ms).
- This starts recording (just like in the Network panel). Be wary of letting it record for a long period of time; you’ll be bombarded with data! When looking at scrolling issues, I hit record, scroll the page for 2 seconds and then stop recording.
You can hard code a breakpoint in your code by using thedebugger;
statement in your code.
Recording
In Part 2, you may recall how we started recording network information before the page loaded in order to capture as many network requests as possible. We don’t do that in the Timeline panel; we’re better off recording short and specific events. We don’t necessarily have to *do* anything. We could leave it recording to see what happens when the user is idle, or if any timers might be running in the background.
The Events, Frames, and Memory tabs are populated while recording; so, be sure to look through them to find the data you require. The Memory section can help you to catch potential memory leaks.
Records in the Frames Tab
Records are displayed during and after recordings. A fair amount of data is captured within the records, as shown in the following image.
This screenshot shows some network requests (blue), and a few ‘recalculate styles’ (purple). Style recalculations could happen for a number of reasons. The yellow records are scripts, and the green represents page rendering.
Clicking on or hovering over a record displays more information. For example, hovering over a ‘paint’ record may show you the part of the page that was painted.
Developer tools will sometimes link a record to another panel. For example, an image’s link directs you to the Resources panel with that image in focus, and an XHR record might link to the corresponding entry in the Network panel.
Developer Tools is kind enough to alert us to potential optimizations. Notice the small notification icon to the right of some of the records in the following image. If it’s faded out, you’ll have to drill down to locate the actual record which contains the useful information.
Popovers sometimes contain a link to the line of code which caused the record to be displayed. But a word of warning: pretty printing won’t always help–especially when looking at third party code. One option is to set a breakpoint on the line you are taken to. Once the debugger has paused, you can view the variable’s contents to better understand the code’s intent.
Filtering
If you’re like me, you always end up capturing more data then you need, but you can form a selection across the vertical bars (frames) which displays only the records corresponding to the selected portion.
If you only care about lengthy records, then set a filter to only display those long records. You can do this at the bottom of the timeline panel.
If scrolling doesn’t feel as smooth as it should, consider excluding ‘Loading’ (e.g. network records). That being said, if you know that network requests are used to load data in a page that users infinite scrolling, you’ll want to keep ‘Loading’ checked.
Don’t apply filtering just because the data appears too intense at first. Take the time to understand and investigate what the dev tools are showing you. You’ll want to use the timeline to confirm where the slow-downs occurr and aim to get those vertical bars as empty as possible (white space/idle time).
Chrome Dev Tools: JavaScript and Performance
Reviewed by JohnBlogger
on
2:27 PM
Rating:
No comments: