---
title: Using Jester from inside an XPCOM Component
teaser:
tags: web,javascript,jester
author: Chad Pytel
published_on: 2007-10-02
---

One of the original reasons we decided to write
[Jester](http://www.thoughtbot.com/projects/jester) was for use in a
[XULRunner](http://developer.mozilla.org/en/docs/XULRunner) Application.  This
would give us the ability to make a desktop client application (written in <abbr
title="Extensible Markup Language">XML</abbr> and Javascript) that would talk to
a Rails back end via ActiveResource.

Using prototype (one of the dependencies of Jester) in XULRunner is something
that has been [documented in other places](http://www.jayomega.net/blog/?p=67),
and once you apply those workaround, using our familiar prototype and Jester
from within a XUL application are straightforward.

However, those of you familiar with XUL applications (is there actually anyone
out there who is? I'm not sure) will be familiar with the fact that you can
write [XPCOM](http://www.mozilla.org/projects/xpcom/) Components in Javascript
as well as C++ (and other languages like Python and Java).

However, once you're inside an XPCOM component you no longer have any access to
Jester, prototype, or even a window object - so you're not going to get very far
when you need to talk to your remote service from within an XPCOM object (such
as if you're using the built-in autocomplete textboxes XULRunner provides.

So, in order to do this you're going to need to 'fool' prototype into thinking
it has everything it needs.  You can probably actually do this with some slick
javascript, but to keep things clear I've expanded everything onto its own line:

    // Useful definitions for easy XPComponent access.
    var Cc = Components.classes;
    var Ci = Components.interfaces;
    var CC = Components.Constructor;
    var XP = {
      newInstance: function(contract_id, nsinterface) {
        return Cc[contract_id].createInstance(nsinterface);
      },
      getService: function(contract_id, nsinterface) {
        return Cc[contract_id].getService(nsinterface);
      },
      getConstructor: CC
    }

    //lookup and grab an actual window object
    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                   .getService(Components.interfaces.nsIWindowMediator);
    var window = wm.getMostRecentWindow("");

    var navigator = window.navigator;
    var document = window.document;
    var Element = window.Element;
    var Object = window.Object;
    var Number = window.Number;
    var String = window.String;
    var Enumerable = window.Enumerable;
    var Array = window.Array;
    var Hash = window.Hash;
    var HTMLElement = window.HTMLElement;
    var Event = window.Event;
    var XPathResult = window.XPathResult;
    var DOMParser = window.DOMParser;

You can see that we need to get top level variables for window and all the
objects that are actually defined on window but that are usually top level
inside a browser.

Believe it or not in this context, we don't even have the XMLHTTPRequest object,
so, we're going to need to define one of those as well.

    function XMLHttpRequest()
    {
      var request = Components.
                    classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
                    createInstance();

      // QI the object to nsIDOMEventTarget to set event handlers on it:
      request.QueryInterface(Components.interfaces.nsIDOMEventTarget);

      // QI it to nsIXMLHttpRequest
      request.QueryInterface(Components.interfaces.nsIXMLHttpRequest);

      return request;
    }

And finally, we can include prototype and Jester (we're going to need to supply
an include method for this as well):

    function include(f)
    {
      var log = false;
      if(typeof(Log) != "undefined") log = new Log();
      if(f.indexOf("chrome://") != 0) f = "chrome://appname/content/" + f;
      try
      {
        Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader).loadSubScript(f);
      }
      catch(e)
      {
        if(log) log.error("Error loading " + f + ": line " + e.lineNumber +
          ". " + e + "\n");
        else dump("Error loading " + f + ": line " + e.lineNumber + ". " + e + "\n");
      }
    }
    include("chrome://appname/content/lib/prototype.js");
    include("chrome://appname/content/lib/jester.js");

Although Jester is probably one of the most useful things you could include in
an XPCOM object, just being able to include prototype in there is pretty handy
as well.

On the rare chance that you're doing RoR and XULRunner development, I hope this
is helpful for you.
