Sharing JavaScript Code in Firefox and IE Add-ons

Recently I ported a Firefox extension to Internet Explorer. The Firefox version has some rather complex application logic implemented in JavaScript, so I wanted to share the code in the IE version rather than reimplement it. I thought the others might be interested in the approach that I used.

My IE add-on is implemented in C++ as a Browser Helper Object, so first of all I needed to find a way to call into JavaScript code. I ended up using a Windows Script Component, which is a COM component implemented in a scripting language. Normally this will be VBScript or JScript, which is Microsoft's implementation of ECMAScript, the standardized version of JavaScript. (After all, why have one name for something when you can have three?) The component gets a UUID and can be instantiated and invoked like any other COM object. In my case, the Script Component (let's call it foo.wsc) looks something like this:

<?xml version="1.0"?>
<component>
<registration
 description="Foo"
 progid="Foo.WSC"
 version="1.00"
 classid="{4d137343-21de-4f5d-9a9e-0bd3bd8e1c9a}"
>
</registration>
<public>
 <method name="myMethod">
 <parameter name="param1" />
 <parameter name="param2" />
 </method>
</public>
<script src="sharedjs/foo.js" />
<script language="JScript">
<![CDATA[
function myMethod(param1, param2)
{
  var foo = new Foo;
  foo.mySharedMethod(param1, param2);
}
]]>
</script>
</component>

The trick is to inject in the actual shared code (foo.js in this case) using a relative path from wherever the .wsc file resides. We can use the same code from the Firefox extension by using the Text Preprocessor. (Note that to use this approach, you have to build your extension using the Mozilla build system as explained in this fascinating, brilliantly well-written article.) Let's say we have an XPCOM component that needs to implement the same functionality. The actual component file becomes a sort of wrapper around the shared JS code:

#filter substitution

const Cc = Components.classes;
const Ci = Components.interfaces;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

#include ../sharedjs/foo.js

Foo.prototype.classDescription = "Foo!";
Foo.prototype.classID = Components.ID("{37025f65-7e5b-4eac-8aa7-bc85b7a190d5}");
Foo.prototype.contractID = "@salsitasoft.com/foo;1";
Foo.prototype.QueryInterface = XPCOMUtils.generateQI(
 [
   Ci.nsIClassInfo,
   Ci.nsIFoo
 ]
);

// nsIClassInfo
Foo.prototype.implementationLanguage = Ci.nsIProgrammingLanguage.JAVASCRIPT;
Foo.prototype.flags = Ci.nsIClassInfo.DOM_OBJECT;
Foo.prototype.getInterfaces = function getInterfaces(aCount) {
 var interfaces = [
   Ci.nsIClassInfo,
   Ci.nsIFoo
 ];
 aCount.value = interfaces.length;
 return interfaces;
}
Foo.prototype.getHelperForLanguage = function getHelperForLanguage(aLanguage) {
 return null;
}

function NSGetModule(compMgr, fileSpec) {
 return XPCOMUtils.generateModule([Foo]);
}

Notice that there is no implementation for mySharedMethod in the component itself since it is implemented directly on the Foo object in the shared JavaScript file. The same approach can be used to access the shared code from a JavaScript module or even a chrome file. In my case, I had some code that couldn't be shared easily between Firefox and IE, so I factored that out into two files (let's call them ie.js and ff.js). The .wsc includes the former (using a <script> tag) and the Firefox components and modules include the latter (using the #include directive). So if I want to e.g. instantiate an XMLHTTPRequest using XPCOM in Firefox and ActiveX in IE, I can implement a method called newXMLHttpRequest with the appropriate implementation in each of the browser-specific script files and then call it from the shared JavaScript code.

Matt

Matt