Having spend the last couple of hours trying, I’d almost concluded that this was impossible. And it was! The way I was trying to do it.

The dynamic injection of script was stupid. It won’t work in XUL (but it will work in HTML).

The secret is found in two things.

  1. An XPCOM interface (isn’t it always?): mozIJSSubScriptLoader
  2. A JS hook into an XPCOM interface: http://www.mozilla.org/scriptable/components_object.html#_stack

The first one lets you load Javascript dynamically within XUL apps. You call it as follows…


var jsLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
    .getService(Components.interfaces.mozIJSSubScriptLoader);
jsLoader.loadSubScript("chrome://booksmarts/content/overlay.js");

The cool thing is that you can also pass it in a scope object, so all of the members of the JS file you load will be attached to it. If overlay had a function called jon(), then the code above would’ve loaded it up into the global scope and I could just call jon() to access it.

But if I’d called loadSubScript(chrome://booksmarts/content/overlay.js, anObject) then I would call anObject.jon() to access it. Brilliant!

So we can load Javascript. But I said I wanted to do it with one file. Now this is where it gets hacky. :) In HTML, my one-file plan involved navigating the DOM to find my script element and then getting the src value from it. So the script tag would look like <script src="include.js?main=another.js">.

Well it turns out that doesn’t work in XUL. As far as I can tell, the script tags are destroyed after they’re loaded in an overlay (document.getElementById can’t find it, so it must be lost). So instead, I discovered (accidentally while I was trying to find out how to load scripts) that you can get a Javascript stack dump from XPConnect by calling Components.stack. This yields something like:


JS frame :: chrome://booksmarts/content/include.js?main=chrome://booksmarts/content/overlay.js :: anonymous :: line 56

Notice that Firefox loaded the script perfectly, despite the URL params AND it preserved them into the stack trace. AWESOME!

If you assign that to a string and split it with split(" :: ") you get an array, the second value of which is your url chrome://booksmarts/content/include.js?main=chrome://booksmarts/content/overlay.js.

From that, a handy bit of URL parsing courtesy of a helpful person on the internet and you get your parameter. Brilliant.

That’s basically all of the pieces you need to do dynamic imports in Javascript. Which I’ll continue next time…