Using Jester from inside an XPCOM Component

Chad Pytel

One of the original reasons we decided to write Jester was for use in a XULRunner Application. This would give us the ability to make a desktop client application (written in XML 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, 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 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.