Molybdenum has its own implementation of javascript modules in contrast to the Firefox 3 Components.utils.import.
The Molybdenum implementation works in Firefox 2 as well.
Goals
- Move javascript file dependencies out of the XUL file into the javascript file itself
- Allow javascript object to behave like singletons
- Enforce a clean codestyle where a file is containing one function only
Implementation
The implementation is in an XPCom service with name @molyb.org/molybase;1.
The service has following methods:
- inject(modulePath, scopeObject)
- clearCache()
Usage
- Put your modules into the directory content/modules. Subdirectories are allowed. Modules must have the extension .mmod (Molybdenum Modules).
- The *.mmod file has to hold exactly one function with the same name as the file itself.
- Lookup the MolyBase service
- call inject
Singleton Example
function TestSingleton() {};
TestSingleton.dump = function() {
return "object Test";
}
var MolyBase = Components.classes['@molyb.org/molybase;1'].getService().wrappedJSObject;
try {
MolyBase.inject("test/TestSingleton", this);
alert(this.TestSingleton.dump());
} catch(e) {
alert(e.message);
}
The code above adds the loaded function as property to "this" (the object requiring the collaborator). This means, the object instance has already been created before the collaborator gets injected.
You can inject outside of the constructor function as well:
MyComp.MolyBase = Components.classes['@molyb.org/molybase;1'].getService().wrappedJSObject;
MyComp.MolyBase.inject("test/TestSingleton", MyComp);
function MyComp() {
alert( MyComp.TestSingleton.dump() );
}
Non-Singleton Example
When injecting prototypical objects, you need to instantiate the injected object.
Consider splitting the injection in the "load and import"- part and the instantiation part:
function Test() {};
Test.prototype.dump = function() {
return "object Test";
}
MyComp.MolyBase = Components.classes['@molyb.org/molybase;1'].getService().wrappedJSObject;
MyComp.MolyBase.inject("test/Test", MyComp);
MyComp.prototype.test = new MyComp.Test();
function MyComp() {
alert( this.test.dump() );
}
Details
MolyBase.inject has to be called with the path of the module to be loaded and a scope object the function should be applied to.
The path must be provided without prefix content/modules and without extension .mmod.
The inject function will get the module from the cache or, if not found in the cache, loads the module via subscript loader.
After successful load, the function with the name of the module, the last component of the module path, will be assigned to a property of the scopeObject with the same name.
If something goes wrong, an exception is thrown.
Not shown in the example, clearCache can be called to clear the module cache and enforce a reload of the modules from source. This is needed, because otherwise modules will not reflect changes on the filesystem and inplace editing without Firefox restart is impossible.
Each object injected via MolyBase will have a property MolyBase. This way, you have to lookup MolyBase via Components only for the very first object in a dependency chain.
Caveats
Lifecycle
Normally, your javascript objects are initialized with the load of the window they are attached to. Scripts loaded with MolyBase are not attached to a window. Instaed of this, they are attached to the application lifecycle (in our case Firefox). This means, object are living as long as Firefox is stopped.
Even closing the Molybdenum window is not destroying the Javascript objects instanciated via MolyBase. This means: If you migrate your scripts to MolyBase, check object initialization mechanisms twice!
Singletons vs Prototypes
Dependency injection in javascript injects object into objects, no matter whether any of it is prototypical or not. However, MolyBase does not create a new instance of the loaded constructor function using "new". As such it provides very simple Singleton behaviour.
If you need to create a new instance of an injected prototypical object, you need to do this yourself. This is great for using MolyBase.inject(..) for importing other objects and then instantiating them manually (like MolyLogger instantiated and constructor-intialized with log category) each and every time.
However, for Singletons, you don't want MolyBase to construct new instances. Instead the injecting code will use the collaborator directly. Therefore, it does not make sense for a Singleton to use object prototyping.
Next steps
- Move code from js directory to modules following the guidelines.
- remove the corresponding <script> tags from molybdenum.xul.
- Adapt unit tests by removing subscriptloader calls because transitive deps will be loaded automatically
- use the singleton feature of modules, first thing could be the MolyLogger to avoid permanent instantiation