Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Debug Workshop

General tips

❉ Create a new user in Chrome just for use with debugging, ensures no plugins or extensions are adding JS to the page and confusing the issue. Extension code and iframes will show up when you're trying to profile code and get in the way. You're aiming for as close to a totally vanilla browser tab as possible.

❉ Be aware Chrome dev tools add a fair bit of CPU/memory overhead to the page

❉ "Art" of debugging: replicate, isolate, eliminate. Replication is the hardest part. "Works for me" not really an acceptable answer.

❉ When working out which browsers to support, latest version minus 1 for the big 4 vendors. Only go past that if supported by hard data about actual user demographics and charge extra.

❉ If debugging the DOM for rendering issues then use the stable browser version. If debugging JS, or going deep into the devtools, no reason not to use Chrome Canary so you get the benefit of the latest version of the tools. V8 JS runtime is stable, and unlikely to cause bugs between browser versions. Can run stable Chrome and Canary side by side.

❉ Chome dev tools can debug a Android 4 mobile device over USB by live streaming the device's screen to your fully working laptop devtools. Control device via its touchscreen, or from the laptop: this is the best mobile debugging experience available at the moment. In this mode you can enable port forwarding, i.e. map a host:port from your laptop to a localhost:port address on the device.

❉ Check out the dev tools Emulation tab, useful for a quick, dirty preview of how something might look on a device. Mainly useful for responsive layout checks etc.

Chrome dev tools as IDE

  • Navigate the source
  • Edit the source
  • Setup workspaces to save changes back to disk
  • Debugging/breakpoints
  • Snippets (set cookie, make xhr)

Source editing

(Demo with the Marbles game).

Video: http://remysharp.com/2012/12/21/my-workflow-never-having-to-leave-devtools

❉ Sources tab lets you view active source files for page. cmd-o gives all files available to the page. cmd-shift-o, jumps to a definition in the current file.

❉ You can edit this code and update in-memory code live on the page without a page refresh

❉ Can map files in dev tools to files on file system, then can save to disk from dev tools. Go to settings cog > workspaces > add folder. Then right click a single file in sources list to complete the mapping. Will map entire directory structure of the site to your local filesystem. (Workspaces mapped via domain:port combo, so if you have two projects that both run on localhost:8000 you need to add/remove workspace folders when switching between them).

❉ Once you have this mapping setup can also save css tweaks in the Elements view to disk (but creating new selectors won't work).

❉ Can edit LESS/SASS files too, especially if a watch task is auto compiling to CSS

❉ Source editing can be frustrated by elaborate build processes ... try and make it so this isn't the case. Heavy building, processing and file manipulation should only happen just before prod release. Have a lightweight dev mode build that might just do things like generate sets of <script> tags for all your JS in index.html, rather than full uglification/concat etc. ... means you're close to the real files in dev tools, and can use IDE-like features.

❉ Right click on a file in sources tab, click "Local modifications" to revert to a previous version. ctrl-z, ctrl-shift-z work to undo and redo too.

❉ Cannot edit page inline JS, only JS from external files.

Debugging

debugger; statement in code tells JS engine to pause execution and allow inspections. Or you can click on line number to add a breakpoint.

What's the diff between adding breakpoint in dev tools and the debugger; statement? debugger; statement is useful when you need code to break at a point (maybe in processed code or a dynamically generated function). Use dev tools source breakpoints (or conditional breakpoint) when you're working in unmolested source code in dev tools. Otherwise effect is the same.

When code is paused you have access to following utilities:

  • Call stack Sequence of function calls for how we got to this point in time (async checkbox in Canary can make call stacks more descriptive when working with async code like XHRs, promises etc.)
  • Scope vars Contents of vars in current scope (possibly undefined if hoisted but not set to a value yet), closure and globals.
  • Step in go to next line, and if the line invokes a function go into it.
  • Step over go to next line, but not into a function call
  • Pause button Pause code when caught and/or uncaught exceptions occur
  • Right click on line number Add breakpoints, or "continue to here" (advances execution point to that line)
  • Can hover over symbols in source to see values. Be careful, hovering over a function call in dev tools will show its return value, i.e. function will be invoked in order to do this! If the function makes a XHR, or has side effects, they will occur.
  • Watch expressions: add expressions, or function calls, and watch when their value changes. Values only update once code is paused again. For example, can tweak return value from a function while at a breakpoint, and see how the value changes live in the watch expression (http://remysharp.com/2013/11/27/using-watches-in-my-devtools-workflow/)

Additional breakpoints

  • Dom breakpoints. From DOM inspector can break on DOM tree modification, removal, attribute changes etc. Handy for when you're not sure where the code you want to debug is happening, but know its triggered when something happens in the DOM, i.e. pause when DOM mod happens then step through until you start to see relevant code.
  • Event listeners breakpoints, break when a event type is encountered anywhere on page, e.g. "click"
  • XHR breakpoints, break when a pattern is encountered in a XHR call URI

Exclude framework code from debugger

Situation: you've paused execution (i.e. on click) and find yourself in jQuery source instead of your app code, bad place to be, not useful.

Exclude jquery source from debugging (or other framework). Go to chrome://flags, and enable dev tools experiments. Settings cog > Experiments > Enable framework debugging support (may need to restart Chrome). Then go to Settings cog > add exclude regex pattern in sources options to skip certain source files, e.g. "jquery"

Snippets

Can add handy utility functions to dev tools. i.e. set a cookie, make a XHR. Snippets are in sources view, click on the play button to run them. Can inject functions into the global scope etc.

A webpage with some snippets: http://bgrins.github.io/devtools-snippets/

Console

Console API: https://developers.google.com/chrome-developer-tools/docs/console-api

console.trace(arg) logs out a stack trace for the current position or object. Can click on the filename:line link in the trace to jump to it that position.

❉ Use %s and %o for C++ printf style formatting, e.g. console.log('hello %s, %o,'jed',user.obj);. %o also works for DOM nodes.

console.time,console.timeEnd to time code execution.

console.group(),console.groupCollapsed(),console.groupEnd() to create collapsable/expandable groups in the log results.

console.table() log out details about objects in tabular form.

$0 global value in console is the last inspected DOM node. $1 the one before that, and so on.

$$('query selector') works even on pages without jquery to find DOM nodes.

$_ is the last returned value from a function.

console.clear or ctrl-l (like Terminal) clears the console.

console.count(label) count something without having to create a counter variable.

copy(obj) in the console will copy an obj to the clipboard. Handy for passing around JSON responses.

monitorEvents(node,event) will print to the console when an event happens. unmonitorEvents(node) will stop.

getEventListeners(node) array of listeners for the node (also available from elements inspection part of dev tools).

console.profile(label),console.profileEnd(label) programatically start/end a profiling sessions.

<top frame> button on console lets you switch between consoles for different iframes that may be on the page.

Memory leaks

Caused by references to objects hanging around so objects can't be GCed. Most common with detached DOM nodes hanging around in frontend dev, JS holds a ref to a node so even when it's removed from the page it hangs around in memory.

Good video about memory in Chrome: youtube.com/watch?v=L3ugr9BJqls

Typical approach for hunting a leak:

  1. Start recording on Timeline tab, make sure "memory" view is selected, also keep an eye on object "counters"
  2. Perform actions suspected to cause a leak, or just use the site normally
  3. Watch for a "staircase effect", i.e. memory use is hiking up
  4. Force the GC to do full sweep, does the memory use reduce to a baseline or has the baseline increased?

Example with Marbles game: refresh, hit new game x 3, force GC. Repeat seq a few times. Look at memory steps. Notice each time after GC the memory usage baseline increases.

Once memory leak identified, need to find out cause. Do this by comparing heap snapshots.

  1. Go to Profiles tab
  2. Get site into normal refreshed state
  3. Take heap snapshot (cmd-e).
  4. Perform leaky actions
  5. Can perform actions repeatedly to make leak more pronounced.
  6. Possibly force GC
  7. Take another snapshot

If a leak, 2nd snapshot maybe larger than the first, but to analyse properly their delta must be examined. Select the 2nd snapshot, then use the comparison dropdown to compare with the 1st snapshot to see the delta. Ignore objects in brackets (they're system/OS things, confusing and unknowable), concentrate on the objects that come from your app code. Are there any +ve deltas? Which objects are increasing in number? Should they be? This is the detective work of hunting memory leaks, realistically requires knowledge of the source code.

Hint: in the Marbles game example, the comparison should show 'detached dom nodes' in the delta.

Click on an object in the snapshot to show more details.

Red highlights mean objects that probably can't be GCed, so good indication that they are leaked. Yellow means normal active objects that are referenced, nothing to worry about. Note: seems slightly broken on latest Canary, try with normal Chrome if no red highlights.

In Marbles example, around line 209, we remove old tile nodes, but don't remove event listeners and xui framework keeps an internal cache that is clobbering GC. This is the leak. Call the xui un function for click and touchStart events to fix it.

Other demo: timeline/leak.html: is a contrived example to play with. In this example, leaks are related to adding events to DOM nodes, but not removing the listener when removing the node (just setting innerHtml to the empty string). FYI: calling jQuery.remove(node) will auto remove any attached listeners.

Rendering performance

Apparently official word for non-smooth, something "wrong" jerky animation and scrolling on a website is "janky". Jankiness can effect not just very visual sites, but also just scrolling down a page with rendering issues on it.

Should be aiming for 60fps for buttery smooth feel on a website. i.e. a repaint every 16ms.

Try out: http://2013.ampersandconf.com/ ... scrolling is janky. Go to Timeline view, hit record, scroll up and down (just for a few secs), stop recording. Look at "frames" view. Fiddle with options, adjust the display, frames >16ms will be adversely affecting rendering performance. Especially look at long paints. For example, position fixed elements will cause whole page repaint on scroll (In ampersandconf.com the background is fixed, so has to be moved relative to the content on scroll to composite the layers). Rendering > show paint rectangles can illustrate this. Remove bg fixed CSS to illustrate.

translate-z CSS hack can bump elements into new rendering layer, and improve performance in scrolling/parallax. Only way to create a new layer at the moment, until browsers give us control. Can enable "layers" tab in dev tools in experiments for 3d visualisation. Try it on ampersandconf.

Careful about running code on window scroll. Debounce it so it isn't run every scroll event. Maybe something like:

window.onscroll = function () {
	requestAnimationFrame(doStuff());
}

Fin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.