Hack 83. Add a New Command-Line Option
Customize the startup process and the process of starting windows by adding support for new command-line arguments. The set of Firefox command-line arguments isn't fixed. You can add more options if you want. In the complex case, new command-line options can be compiled up into dynamic link libraries using C/C++. It's easier to use a simple JavaScript script, though. This hack shows how to implement the simple case. We'll make a --my or /my option that displays a page stored in the install area on startup. 7.10.1. PreparationThe straightforward thing to do is to add an option that starts up a window of your own design. In order to do that quickly, you have to sidestep the Extensions system by hacking on the install area directly. You have to create a specific set of objects to make everything hang together properly. As in [Hack #82], start by shutting down all Mozilla programs in preparation for new component registration. Find the file named compatibility.ini in your Firefox profile. Edit it and decrement the Build ID date by one day. Save the file. The compreg.dat component registry is in the same directory if you want to inspect it while you're there.
Next, move from the profile directory to the Firefox install directory. In there, you'll find a components directory. Go to that directory. You'll see there's a number of DLL (or .so) dynamic link libraries, with their accompanying .xpt type libraries. Ignore those. Instead, note the existing .js files. You need to make one of those. The jsconsole-clhander.js file is a good example of a command-line option implemented in JavaScript. Some of the other JavaScript files in that directory do things unrelated to command lines, so beware. 7.10.2. Making the Script OutlineHere's an outline of the script you need to create: // Tell mozilla there's a new module of components function NSGetModule( ) {} // Make a module to hold the sole component var myCommandModule = { registerSelf : function ( ) {}, getClassObject : function ( ) {}, unregisterSelf : function ( ) {}, canUnload : function ( ) {} }; // The module creates the component using a factory object var myCommandComponentFactory = { createInstance : function ( ) {}, lockFactory : function ( ) {} }; // The component itself is just a simple record var myCommandComponent = { commandLineArgument : "", prefNameForStartup : "", chromeUrlForTask : "", helpText : "", handlesArgs : true, defaultArgs : "", openWindowWithArgs : true } As in [Hack #82], the NSGetModule() function is included to be detected by Firefox at startup time. It's the hook that will install all the rest of the claptrap. After that, you have three objects to create:
In this skeleton code, we've been a bit naughty and specified the component as a singleton object rather than as a full JavaScript object constructor. The factory object would really like to construct a whole new handler object each time. It works as is, though, and we're unlikely to use the same command-line option twice anyway. We won't ever call or use any of these objects from other scripts. An object embedded in the Firefox browser called the command-line manager will call them for us whenever that's necessary. NSGetModule() is called by the module registration system, as before. This hack is a good example of the extensive delegation programming that's common across Firefox's design. One object defers to another in a handover style of processing. 7.10.3. Filling in the ScriptFilling in the details of this script outline can be done quickly. We haven't bothered here with all the fancy error handling; you can add that if you want. Just copy it from one of the other command handlers. Put the following completed script in myoption.js in the components directory: // give the new component an identity
const HandlerPrefix = "@mozilla.org/commandlinehandler/general-startup;1?type=";
const MyContractID = HandlerPrefix + "my";
const MyClassID = Components.ID('{75034172-c6bd-4f3d-bbb0-0c572428e3c1}');
// Tell mozilla there's a new module of components
function NSGetModule( ) { return myCommandModule; }
// Make a module to hold the sole component
var myCommandModule = {
registerSelf : function (compMgr, file, location, type) {
var reg = compMgr.QueryInterface(
Components.interfaces.nsIComponentRegistrar
);
reg.registerFactoryLocation(
MyClassID, "My Service", MyContractID, file, location, type
);
},
getClassObject : function (compMgr, cid, iid) {
return myCommandComponentFactory;
},
unregisterSelf : function ( ) { },
canUnload : function ( ) { return false; }
};
// The module creates the component using a factory object
var myCommandComponentFactory = {
createInstance : function (outer,iid) { return myCommandComponent;},
lockFactory : function ( ) { }
}
// The component itself is just a simple record
var myCommandComponent = {
commandLineArgument : "-my",
prefNameForStartup : "general.startup.my",
chromeUrlForTask : "resource:/res/mypage.html",
helpText : "My Help",
handlesArgs : true,
defaultArgs : "default",
openWindowWithArgs : true
} Let's run through the code briefly. We're creating a -my command-line option. --my will also work, and we get /my on Windows as an extra bonus. The only part of the contract ID that can vary is the very last bit, as shown in the bolded line. The rest must be stated to match what the command-line manager expects. To get a class ID, just run uuidgen or guidgen from the command line, and cut and paste the output. NSGetModule() passes back our module object, a trivial task. The module object is able to register the sole component type we've created. It's too dumb to be able to unregister or do other fancy management. It can also pass back our factory object if it's asked to. Finally, the factory object can extrude our singleton handler instance. You can see how a request for a command-line handler is delegated first from the module level down to the factory level, and then finally down to a component instance. Who uses the final component instance? The command-line manager embedded inside Firefox does. Here are the interesting bitsfirst, the handler itself. The only things worth changing in the handler record are commandLineArgument and chromeUrlForTask. The former is the option we're creating. The latter, despite the name, can be any URL. We've chosen an HTML page that we'll put inside the Firefox install area. The resource: URI scheme points to the top of that area, so we'll be able to put mypage.html into the res subdirectory under there. That's a safe place for a startup file to live. The second interesting bit is debugging. The first time you run Firefox, your module is registered. Unless you have fundamental syntax problems, that much should work. You can confirm it by finding the filename in compreg.dat in your profile. After that, you get no feedback at all unless you've got a debug build of Firefox. In that case, you can use dump(). For the normal case, you'll have to check your JavaScript very carefully. Use a syntax-highlighting editor and examine constant strings and keywords carefully for typos. To see your new option work successfully, just run: firefox -my If nothing appears to work, try -jsconsole to prove to yourself that everything is still sane. Don't forget to fully shut down Firefox if you suspect it's become confused. This command-line stuff is really designed for XUL-based windows, not HTML ones. It will work for HTML windows if you're careful. Some weird results are possible if your HTML page isn't coded tightly, though. Start with a trivial page before you try anything fancy. Either way, the displayed document is free to do its own scripting. That code can pick up where the command-line code left off. Hack the createInstance() method in the script if you really want to do extra processing before the window opens. |