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".