XUL:Command Line Handling
Command-Line Handling in the xulrunner
XUL apps intended to be run by the xulrunner need to be able to flexibly handle command-line arguments. The current system does not allow arbitrary processing by command-line handlers, it is limited to opening XUL windows. Apps (especially utility apps) may wish to exectute arbitrary code and then exit, without ever opening a window.
Command-line handling is currently done through the nsICmdLineService and nsICommandLineHandler interfaces. As you can see, the nsICommandLineHandler interface is so weird that we use a C++ macro to implement it for most cases.
Instead, the command-line handling should be handled through a callback-like interface. Posit a set of interfaces:
[scriptable...]
interface nsICommandLine : nsISupports
{
/**
* Number of arguments in the command-line.
*/
readonly attribute signed short length;
/**
* Get an argument from the array of command-line arguments.
*
* @param aIndex The argument to retrieve. This index is 0-based, and does not include
* the application name.
* @return The indexth argument.
*/
AUTF8String getArgument(in signed short aIndex);
/**
* Find a command-line flag. Flags can begin with a hyphen or double-hyphen,
* and on Windows/OS2 with a forward-slash.
* @param aFlag The flag to locate.
* @param aCaseSensitive If true, the case of the flag is significant.
* @return The position of that flag in the command-line, or -1 if not found.
*/
signed short findFlag(in AUTF8String aFlag, in boolean aCaseSensitive);
/**
* Remove arguments from the command-line. This is normally done when a handler has
* processed the arguments.
* @param aStart Index to begin removing arguments.
* @param aEnd Index to stop removing arguments.
*/
void removeArguments(in signed short aStart, in signed short aEnd);
/**
* The type of command-line being handled:
* STATE_INITIAL_LAUNCH is the first launch of the app.
* STATE_REMOTE_AUTO is a remote command-line automatically redirected to this instance.
* STATE_REMOTE_EXPLICIT is a remote command-line explicitly requested using winDDE/xremote.
*/
readonly attribute unsigned short state;
const unsigned short STATE_INITIAL_LAUNCH = 0;
const unsigned short STATE_REMOTE_AUTO = 1;
const unsigned short STATE_REMOTE_EXPLICIT = 2;
/**
* The "working directory" for the command-line. A remote command-line may have a different
* working directory than the current process, so we make sure we remote that also.
*/
readonly attribute nsIFile workingDirectory;
/**
* Resolve a file-path argument into an nsIFile.
*/
nsIFile resolveFile(in AUTF8String aArgument);
/**
* Resolve a URI argument.
*/
nsIURI resolveURI(in AUTF8String aArgument);
};
interface nsICommandLineHandler : nsISupports
{
/**
* Handle the command-line. If the handler finds arguments that it understands, it
* should perform the appropriate actions (such as opening a window) and remove
* those arguments from the command-line array.
*
* @throws NS_ERROR_ABORT to immediately cease command-line handling
* (if this is STATE_INITIAL_LAUNCH, quits the app);
* NS_SUCCESS_COMMANDLINE_RESTART_XPCOM to restart the app;
* All other exceptions are silently ignored.
*/
void handle(in nsICommandLine aCommandLine);
/**
* When the app is launched with the -help argument, this attribute
* is queried and displayed to the user (on stdout).
*/
readonly attribute AUTF8String helpText;
};
Instead of treating the command-line handler as static information about which XUL window to open, it is a dynamic type that can respond to the "event" of starting the application. Arguments can start with -arg, --arg on *nix, /arg on windows; they will be normalized to -arg before handlers are called. If a unix arg is the form --arg=param, it will be split into two arguments -arg <param>. Handlers are registered with the category manager (currently, you have to register with a contractID prefix and a category, which is kinda silly) and would be run in alpha-order thusly:
| category | entry | value | (description) |
|---|---|---|---|
| command-line-handler | b-jsdebug | @mozilla.org/venkman/clh;1 | Handles -venkman and -venkman-sync flags to debug JS, even during startup |
| c-extensions | @mozilla.org/extension-manager/clh;1 | Handles -install-toolkit-extension -install-app-extension -install-profile-extension flags | |
| m-edit | @mozilla.org/composer/clh;1 | Handles -edit <URLOrPath> | |
| m-irc | @mozilla.org/chatzilla/clh;1 | Handles -irc or -chat flags | |
| y-final | @mozilla.org/app-startup/clh;1 | If there is a bare URL/file on the command-line, this opens a browser window with that URI/file. If there are *no* windows open at this point, it opens a default browser window. |
The important thing is that the same command-line handlers would be used for a remote invocation (DDE or xremote) as an initial startup. This means that we can get rid of all the app-specific code in the xremote service. Firefox can keep a backwards-compatibility shim to handle the -remote flag. This would give apps equal-opportunity and silent access to argument handling. This is very similar to the way win32 DDEremoting works now, but somewhat different from the xremote model.
We should also document a convention for the alphabetic letter prefixes, such that important early handlers such as venkman can reliably precede handlers that might need to be debugged.
The command-line-service should not be a service, but rather a per-startup passed to the handlers. You would have a separate "command-line-service" for remote invocation of an app. The service would QI to nsIArray/MutableArray, allowing handlers to manipulate the argument array. This may cause some issues with GTK/X startup, which needs access to the original argv/argc at startup. This is already a thorn in the side of embeddors who have already initialized GTK/X and don't want our widget code trying to do it again.
A sample command-line handler might look like this (with a bit more error-checking, hopefully):
function handle(commandLine) {
var ww = C[...].getService(I.nsIWindowWatcher);
var found;
while ((found = commandLine.findFlag("edit", false)) >= 0) {
var uriarg = commandLine.getArgument(found + 1);
var theuri = commandLine.resolveURI(uriarg);
ww.openWindow(null, "chrome://editor/content/", null, "chrome", null, theuri);
commandLine.removeArguments(found, found + 1);
}
found = commandLine.findFlag("editor", false);
if (found >= 0) {
ww.openWindow(null, "chrome://editor/content/", null, "chrome", null, null);
}
}
Comments XUL:Axel Hecht
I'm not so happy about the numbers, like the
commandLine.getArgument(found + 1);
and
commandLine.removeArguments(found, found + 1);
I'd prefer to see just found there instead of found + 1.
bsmedberg says: Why, and how would it work? getArgument(found) returns "-arg" and getArgument(found + 1) return "param".
Could the gtk startup just be an additional command-line handler, disabled in embedding?
bsmedberg says: no. The startup sequencing does not allow for that.
Callek says: I agree with Axel on the ugliness of the look of that. Could we perhaps add a method similar to getArgumentValue(found) which would do what found + 1 does now in this case?
Neil: maybe a function void extractArguments(in string flag, in unsigned long max, out unsigned long count, [retval, array, size_is(count)] out string args); which would remove the extracted args or return null on failure.
Neil: Is there need for a case where a handler doesn't want to open a window, doesn't want to suppress other handlers but may or may not want to suppress default window opening? Currently some of the handlers have hacks that say "don't open a default window if we are remoting".