How to do dynamic Javascript imports in XUL chrome
Tutorial March 13th, 2007Having 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.
- An XPCOM interface (isn’t it always?): mozIJSSubScriptLoader
- 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…
April 23rd, 2007 at 10:02 am
Hi,
But what I did found is that code:
I’ve been also trying to write some kind of ‘include’ function but I want to make it work for chrome and for HTML, mainly because I use jsUnit to test components of my extension and it runs from the browser. That’s why I decided to stay with dynamically writing tags into page, which I didn’t test from inside chrome yet
var scripts = document.getElementsByTagName(”script”);
for (var i = 0; i
April 23rd, 2007 at 10:03 am
var scripts = document.getElementsByTagName(”script”);
for (var i = 0; i
April 23rd, 2007 at 10:04 am
var scripts = document.getElementsByTagName(”script”);
for (var i = 0; i < scripts.length; ++i) {
print(scripts[i].getAttribute(”src”));
}
Sorry, ‘<’ sign broke the parser
April 23rd, 2007 at 10:05 am
The code above works in the chrome and shows paths to all the loaded js files, so ’script’ objects aren’t really destroyed.
April 23rd, 2007 at 9:32 pm
Hmm… that’s nicer than the way I do it with the stack I think. For some reason I forgot that I could just search for script tags, instead of looking for one with a particular id.
So far for me, adding a script tag just hasn’t worked in chrome, but if you get it to work, please let me know.
To do one that works in html and chrome, I was just going to say that if it starts with “chrome://” then you can use the js loder and otherwise embed a script tag. Sometimes being specific is much much easier than doing the general case.
August 3rd, 2007 at 9:24 pm
Completely unrelated question … but kinda related to what i am looking for if i had to remove the JS Objects which have been initialized in the SCRIPT object at runtime when i am removing the SCRIPT object after having added it dynamically.