Accelerating Firefox Add-On Development with JavaScript IntelliSense in VS 2008

Some folks on the Visual Studio team have done a phenomenal job of enhancing IntelliSense for JavaScript in Visual Studio 2008 and Visual Studio 2008 SP1. So far, I have found numerous articles and videos that discuss how it significantly improves the development experience in ASP.NET, but I wanted to share the impact it has on what I do—Firefox add-ons.

If you’re not familiar with the architecture of Firefox add-ons, here’s an extremely abridged top-down view:

  • Most add-ons are implemented with a combination of XUL (XML User Interface Language) for the UI and JavaScript to support it.
  • Instead of running the context of a web page, the JavaScript runs in the context of a window.
  • In order to share state across multiple windows, you must use and/or implement XPCOM components.

XPCOM components provide services or objects that JavaScript running in a Firefox web page normally does not have privileges for. For example, there are services for file I/O, preference reading and writing, and writing to Firefox’s JavaScript console. You can also build your own XPCOM components (in JavaScript, C++, among other languages) that enable you to share data across the entire Firefox session or place logic that doesn’t fit well within the context of a single window.

My use of IDEs for add-ons shifted from Notepad++ to Visual Studio in early 2007 because I needed a better way to organize and search my files, but now I am glad I made the switch for other reasons. The hard about using XPCOM is that unless you have memorized your own interfaces as well as the interfaces of all the components you use, you often find yourself referring back to your IDL files or Mozilla’s documentation for the exact name of the attributes and functions you need. This arguably slows your momentum when working with XPCOM. But with the new improvements to Visual Studio’s JavaScript IntelliSense, this problem (almost) fades away completely.

Consider the following XPCOM interface:

[scriptable, uuid(770E43E2-7213-4639-ABEB-DED12A7188A9)]

interface ITrace : nsISupports

{

    /// <summary>

    /// Logs an exception as an error to the JS Console.

    /// </summary>

    /// <param name="e">The exception to log.</param>

    void error(in nsIException e);

 

    /// <summary>

    /// Logs a message to the JS Console and the dump window as an informational message.

    /// </summary>

    /// <param name="message">The message to write.</param>

    void info(in wstring message);

 

    /// <summary>

    /// Logs an exception to the JS Console as a warning.

    /// </summary>

    /// <param name="e">The exception to log.</param>

    void warn(in nsIException e);

 

    /// <summary>

    /// Logs a message to the JS Console and the dump window as a verbose message.

    /// </summary>

    /// <param name="message">The message to write.</param>

    void verbose(in wstring message);

};

Creating an XPCOM component requires an interface specified in XPIDL (shown above) as well as an implementation (not important for this discussion). The interface above should be straightforward to interpret even if you are not familiar with XPIDL. The example shows a simple tracing interface which abstracts away the API pain points of logging data to Firefox’s JavaScript console. It inherits from nsISupports (the XPCOM equivalent of IUnknown), and its methods take very simple parameters. The scriptable attribute specifies that this interface is callable from JavaScript and hence usable in our add-on. Here’s some sample code to show from JavaScript how we can invoke this interface.

try {

    // Do something that can throw

}

catch (e) {

    Components.classes["@example.org/trace;1"].getService(Components.interfaces.ITrace).error(e);

}

The indexer on Components.classes returns the object that follows the given contract ID. The contract ID is defined in the implementation (and we hope the documentation); from there we get the service that corresponds to the ITrace interface (this is like a QueryInterface call) and call the error function with the exception that we caught. Of course, there is zero IntelliSense help because Visual Studio doesn’t know what Components.classes and Components.interfaces are, nor would it have any idea how to find the XPIDL file for a given service to find what functions ITrace actually exposes. We can do much, much better.

Let’s expose a simple function called "error" that we can include from any other JavaScript file that allows us to do the same thing:

// xpcom-interop.js

 

function logError(e) {

    /// <summary>

    /// Logs an exception as an error to the JS Console.

    /// </summary>

    /// <param name="e" type="nsIException">The exception to log.</param>

    Components.classes["@example.org/trace;1"].getService(Components.interfaces.ITrace).error(e);

}

Great! Now we have a utility function, we can replace our ugly code before:

try {

    // Do something that can throw

}

catch (e) {

    logError(e);

}

Still, though, no love from IntelliSense:

No JS IntelliSense

Here we can use the the reference tag in our JS file to enable Intellisense, like this:

JS Function IntelliSense

JS Parameter IntelliSense

Very nice! If we flesh out this xpcom-interop file more, our development time can really improve.

Something you need to be aware of is that sometimes the XPCOM interop file can become too complicated for Visual Studio to parse correctly because it references objects that it might not know exist (e.g. Components.classes, Components.interfaces, Components.results). If this is the case, add an xxx.debug.js file in the same directory as the xxx.js file and write all of your documentation there. For example, I could keep xpcom-interop.js like this:

// xpcom-interop.js

 

function logError(e) {

    /// <summary>

    /// Logs an exception as an error to the JS Console.

    /// </summary>

    /// <param name="e" type="nsIException">The exception to log.</param>

    Components.classes["@example.org/trace;1"].getService(Components.interfaces.ITrace).error(e);

}

 

var prefService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);

(Note that because we reference Components.classes outside of the logError function we see this when updating IntelliSense (Ctrl+Shift+J).)

IntelliSense Update Error

And I could also create a new xpcom-interop.debug.js file for documentation purposes:

// xpcom-interop.debug.js

 

function logError(e) {

    /// <summary>

    /// Logs an exception as an error to the JS Console.

    /// </summary>

    /// <param name="e" type="nsIException">The exception to log.</param>

}

 

var prefService = {};

Notice that I don’t include any code for the logError function this time. This file is intended for documentation purposes only; it will never be included with the final Firefox add-on because it doesn’t do anything. If you go back to the default.js file and refresh IntelliSense (Ctrl+Shift+J), IntelliSense will display the same help for the logError function in addition to displaying the prefService variable. When you place the reference tag at the top of a file, Visual Studio will look for filename.debug.js before checking the actual file that the tag references, which is very useful in these situations.

prefService shown in IntelliSense 

Ctrl+Shift+J Is Your Friend!

Fiddling with JS IntelliSense can be frustrating if you’re not aware of how to get it to refresh itself. Ctrl+Shift+J should work. If not, you can find it in the Edit menu under the IntelliSense menu item. Also, you can bind a shortcut to the command "Edit.UpdateJScriptIntelliSense".

Resources

  1. Format for JavaScript Doc Comments
  2. Hotfix to Enable -vsdoc JS files

Entity Framework Tutorial Excerpt

Joydip Kanjilal has recently released a book all about the ADO.NET Entity Framework entitled Entity Framework Tutorial. In this book, Kanjilal explains and demonstrates the various features of EF, including how to create an Entity Data Model (EDM), using E-SQL and LINQ to Entities to perform queries, and even an introduction to ADO.NET Data Services (Astoria). Packt Publishing has provided me with an excerpt from this book where Kanjilal goes through building an EDM, which you may find below.

You may also be interested in a review that John Kilgo has written about Entity Framework Tutorial located here. Finally, scripts and code downloads for this book are available here. (You will have to provide your email address.)

Continue reading

UCMA 2.0 at Metro

Tomorrow, I’m headed to Chicago with my coworker Mark Stafford to attend the Metro program to get down and dirty with the new UCMA 2.0 beta bits. Having at least played with the majority of the UC APIs this year throughout my company’s OCS deployment, I have seen my fair share of hacks to implement the scenarios that we needed to get working, the most notable of which is our current recording solution. It’s based on the Speech Server APIs, and without going too much into it, it’s safe for me to say that it’s not pretty. So you can only imagine how excited I am to hear that this scenario is one that UCMA 2.0 will support.

In the meantime, I’ve been reviewing the pieces of UCMA by reading Joe Calev’s blog. If you are new to UCMA I would recommend checking it out; Joe has a lot of detailed posts on UCMA 1.0 and a new series on UCMA 2.0 that is underway.

I hope to post updates with the material I learn over the next two days as quickly as I can, so stay tuned!