An In-depth Look At The Future of Javascript Debugging With Firebug

—Friday, May 12 2006

What if you could say goodbye to alert and not-so-useful logger scripts and have a real debugging API to debug Javascript. While it hasn’t made it to the public yet, the next version of Joe Hewitt’s Firebug will finally give us an easy to use, full-featured debugging environment for Javascript. Fortunately, I’ve had the pleasure of being able to test the new version of Firebug and this post will outline some of what you can expect.

Before Joe’s efforts, we’ve used alert, and we’ve even tried to use the cool-but-cumbersome Venkman. This is a painful process to say the least. It shouldn’t be, and in the future it won’t be–Atleast while using Firefox. Hopefully Joe won’t mind if I elaborate a bit on what you’ll soon be able to do with the new Firebug.

Logging to the console

Have you ever written anything like this?

    alert('width: ' + width + ' height: ' + height);

Thats a mess just to annoy you with a dialog box that shows width and height variables that are labeled; Concating strings together is messy, luckily Joe thinks this isn’t ideal as well. We can do the same thing using Firebugs console object.

    console.log('width: %d height: %d', width, height);

Firebug uses printf-like formatting. I’m not exactly sure the language it’s based on so I linked to the Php manual because it has good examples.

The code above would output something like the image below in your console thats hidden snugly away at the bottom of your browser:

Logging

You’ll notice that our numbers print out in red signifying they are digits. We could’ve used %s and they would’ve been black. The log function is pretty robust and can be used in a number of ways. You don’t have to use formatted strings. Here are a few examples:

Printing Arrays

var myArray = ['item1', 'item2', 'item3'];
console.log(myArray);

200605120414

Multiple Strings

var foo = 'This is foo';
console.log('This is 100% not formatted', foo);

Html Elements

    console.log($('mycanvas'));

200605120432

Info, Warnings, and Errors

Firebug also has different logging levels:

    console.debug('I am debug');
    console.info('I am info');
    console.warn('I am a warning');
    console.error('I am an error');

200605120440

Scoped Timers

Trying to speed up your code? How fast does it take to execute line 10-20? These questions can be answered using the time and timeEnd methods.

  importImages: function() {
    console.time('Loading Images');
    this.options.images.collect(function(image) {
      var img = new Image();
      img.src = this.options.filePath + image;
      console.time('event binding');
      Event.observe(img, 'load', this.onImageLoaded.bind(this, img));
      console.timeEnd('event binding');
      return img;
    }.bind(this));
    console.timeEnd('Loading Images');
  },

This will print out the time in milliseconds it took to execute the code between time and timeEnd. Unfortunately it doesn’t print out the name of the scope, so if you have more than a few of these in your files, you’ll need to click on the blue links to the right in order to view the code and see which number comes from which timer. Update: Joe reports this has been fixed

200605120448

Tracing Execution

What was called and where was it called from? Tracing gives us the ability to get this type of information. If you wanted to trace the flow of execution for a method you can use the trace method.

  importImages: function() {
    this.options.images.collect(function(image) {
      var img = new Image();
      img.src = this.options.filePath + image;
      Event.observe(img, 'load', this.onImageLoaded.bind(this, img));
      return img;
    }.bind(this));
    console.trace();
  },

The image below shows us a complete trace of files and line numbers where we can trace this method all the way back to where the object was initialized in our html file.

200605120503

Breakpoints

If your not familiar with breakpoint-style debugging, it’s extremely powerful. Imagine if your Javascript code was your favorite tune. Anytime during that tune, you could pause it on a certain area and find out what lyrics were being sung, what note was being played and at what pitch. You could essentially find out all you need to know about that particular part of the song and move on to other parts of it and do the same thing. Maybe not the best comparison, but it’s the best I have at the moment. ;-)

So essentially we can freeze the state of our scripts and inspect variables and objects that are in play at the moment. I’ve rambled enough, lets look at a couple examples.

The debugger keyword

var CoverFlow = Class.create();
CoverFlow.prototype = {
  initialize: function(canvas, options) {
    this.canvas = $(canvas);
    this.context = this.canvas.getContext('2d');
    this.currentX = 10;
    this.options = Object.extend({
      filePath: 'images/',
      images: ['as-i-lay-dying.png', '36crazyfists.png', 'in-flames.png']
    }, options || {});
    this.importImages();
  },
  
  importImages: function() {
    this.options.images.collect(function(image) {
      var img = new Image();
      debugger;
      img.src = this.options.filePath + image;
      Event.observe(img, 'load', this.onImageLoaded.bind(this, img));
      return img;
    }.bind(this));
  },
  
  onImageLoaded: function(img, event) {
    var width = '10px';
    var height = '20px';
    this.context.drawImage(img, this.currentX, 10);
    this.currentX += img.width + 10;
  }
}

The above Prototype class is the entirety of the code we’ve been working with. All it really does is load up some album art and spit it out to a canvas element. The script itself really isn’t all that useful, but it’s more than enough to allow us to explore breakpoints.

Take special note of the debugger keyword in the importImages method. This will tell Firebug it should pause my script here and tell me a little bit about it. So now to refresh the page and see what happens.

Firebug encounters the debug keyword and instantly pops up the debug console, highlights the line in which the breakpoint was encountered, shows me the call stack, gives me an overview of local variables and my object, and finally pauses my script (in no particular order).

Firebug-Debug-Console-1

I can now get all kinds of information about my object (this) and inspect the img element that has been created and also see the local image variable thats available inside the loop.

Inspector

As you can see from the image above, I can even go deeper into inspecting objects. If I were to expand the img object, we would see that the src attribute hasn’t been set yet. Also, we can use the Firebug command line to inspect the variables and objects in context now. If you look at the inspection pallet, you can see the local variable image is set to the string “as-i-lay-dying.png”. Lets hope over to the command line (in the console tab) now and witness some awesomeness:

200605120612

So we’ve frozen our script at a certain point and we can inspect to our hearts content. To continue the script execution until the next breakpoint press the play button in the debug console. If there isn’t another debugger statement in your code, your script should finish fully loading, otherwise it will pause again.

Stepping Over

Remember our img element from earlier whose src attribute has yet to be set. If you look at the next line of code in our class, you can see this is where it gets set. We don’t want to have to unpause our script and move the debugger statement lower, fortunately we don’t have too.

Stepping over allows us to single step into the next line of the current function. So if we wanted to keep moving along until our image’s src attribute was set, we’d press the “Step Over” button a couple times until it has passed the line responsible for setting this.

When we go back to our console to inspect the image, here is what we get:

>>> img
<img src="images/as-i-lay-dying.png">
>>> img.src
"file:///Users/Caged/Sites/prototype/images/as-i-lay-dying.png"

Stepping Through

To single step into entering a function, we use the “Step Into” button. So if we keep pressing this, you’ll see the script enter into the bind method of the Function object in Prototype, then from there it goes into the $A or Array.from method, so on, and so on.

Stepping Out

Stepping out allows our script to continue executing until our current function returns or another debugger statement is encountered.

The Bad News

I have nothing but good things to say about Firebug. It’s truly a blessing for Javascript developers, especially with the increase in the number of developers getting involved with Javascript. With that said, there are some negative side effects of Firebug that doesn’t really have much to do with Firebug itself.

Safari will crash

You may or may not know, but Safari also has a console.log command. It works pretty well until it encounters a printf style string. It will simply lock up and crash.

Not A Standard

I really wouldn’t care much about this point if it didn’t cause problems in other browsers.

Because the console object isn’t available to all browsers, anytime it or the debugger statement is used, it will result in a syntax error or something similar in anything but Firefox (excluding console.log in Safari).

This makes browser switching harder. It’s not a quick jump between Firefox and Safari to see if your script works in both browsers. You need to comment the Firebug specific keywords in order to view your script in another browser.

You could potentially create a wrapper around the console object to provide a fail-safe for other browsers.

Bittersweet

It would be great if we had a Firebug for all browsers. Unfortunately I don’t see this happening anytime soon. I found a post last week (which I can no longer find) of someone who was doing something like this for Safari, but I think it has since been abandoned.

Summing Up

There is much more to come in Firebug than what I’ve covered here. If you want to keep your eyes pealed for the new release along with some useful tips on Firebug, check out Joe’s blog.

Also, the mozilla site has a better explination of the step over, step into, step out stuff that you might be interested in.

Until next time…