Problem: You have a XPCOM component which handles files of a particular
type (*.xul). You have another component which wants a handler
for foo.xul. How do you get the first component from the
second?
Solution 1: Hard-code the first component's contract identifier ("@foo.mozdev.org/blah/blah/xul;1"); into the second component's source code as a constant. Unfortunately, this is not very scalable (*.js, *.css, *.rdf, *.in, *.xml, *.txt...).
Solution 2: Use the Gecko Category Manager, and define a category for all your file handling components.
Incidentally, this is a real-world scenario for me; I have had to solve this exact problem. The Gecko category manager is a common way for components to look up the contract ID's of other components.
The category manager stores information in a format like this:
"@mozilla.org/xpath-generator;1"
"@mozilla.org/browser/feeds/result-writer;1""service,@mozilla.org/extensions/manager;1"The Roman numerals here indicate category names. Each category can store different properties, indexed by some property name.
Often (but not always, as the Extension Manager example shows) a property value will be a component contract ID. This gives a simple way for one component to list itself where other components can find it.
First you need a category name. In my case, I'll choose "Verbosio document wrappers by file type".
Second, your component's module should register the component's contract
ID in the category manager. This happens in the registerSelf()
call. I've given an example of this near the end of "Exposing
XPCOM Components in JavaScript, part one". In this case, my code would
say:
var Module = {
// nsIModule
registerSelf: function registerSelf(compMgr, fileSpec, location, type) {
// Module registration code goes here.
// Category registration code.
var catman = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Components.interfaces.nsICategoryManager);
catman.addCategoryEntry("Verbosio document wrappers by file type",
"xul",
"@verbosio.mozdev.org/document-wrappers/xul;1",
true, /* Persist this entry */
true); /* Replace existing entry */
},
// ...
}
We already have a category name, and presumably all the relevant components are using it. Let's try to find a match for "myApp.xul".
TestComponent.prototype = {
// sample method to get a component
getComponentContractID: function getComponentForFile(aPath) {
// aPath == "myApp.xul"
var dotIndex = aPath.lastIndexOf(".");
var catEntry = aPath.substr(dotIndex + 1);
const catman = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Components.interfaces.nsICategoryManager);
const catName = "Verbosio document wrappers by file type";
return catman.getCategoryEntry(catName, catEntry);
}
};
This is just sample code (nsISupports is only the foundation interface), but it illustrates the point. By storing the contract ID in the category manager, we can look up the contract ID later for something relevant, and then create a component for the relevant item.
Posted by WeirdAl at February 19, 2007 3:35 PMSolution 3 is to use magical contract IDs. This can be seen in the case of protocol handlers, which have contract IDs of "@mozilla.org/network/protocol;1?name=" followed by the protocol name. Of course, this isn't necessarily better than using the category manager; it's just another option :)
(From Alex: Yes, I left that one out, and it's a valid point. I use that technique myself in Verbosio for a couple things.)
Posted by: Mook at February 19, 2007 10:37 PM