Providing Command-Line Options

  • Revision slug: Chrome/Command_Line
  • Revision title: Providing Command-Line Options
  • Revision id: 50387
  • Created:
  • Creator: OwjS26
  • Is current revision? No
  • Comment

Revision Content

Extensions and XUL applications can modify the way command line parameters are handled by writing a component that implements nsICommandLineHandler and registering it in a category.

Overview

The attached code is a component written using the XPCOM framework, accessed from JavaScript via XPConnect

The official (terse) page for describing how Firefox handles command lines and initial startup can be found at the nsICommandLine documentation. There, you'll find links to the code for the XPIDL file defining the interface implemented below, and to other related <tt>.idl</tt> files.

The below example component will implement two command line parameters:

<tt>firefox.exe -myapp</tt>
Opens a chrome window for My Application.

<tt>firefox.exe -viewapp url</tt>
Opens a chrome window for My Application and passes it a nsIURI object.

For more clues on writing and registering XPCOM in Javascript, you can try the tutorial

N.B. the tutorial instructions for registering are not comprehensive, and could easily take up their own page In order to register the component illustrated in the {{template.Anch("Sample code")}} section, save the JavaScript shown in the big box below into a file with the extension <tt>.js</tt> and drop it into one of the directories (see Bundles) that Firefox scans for extensions. A typical location in Windows would be "C:\Program Files\Mozilla Firefox\components" or your extension's <tt>components</tt> directory.

Then, (with Firefox shut down) delete the <tt>compreg.dat</tt> file, to force Firefox to rebuild its component list, and restart Firefox. You'll find the <tt>compreg.dat</tt> file in your profile directory, not to be confused with the file by the same name in your Firefox directory.

Your profile directory will be in your home directory (~/.mozilla/firefox under Linux) or in something like: "C:\Documents and Settings\(user-name)\Application Data\Mozilla\Firefox\Profiles\" under windows.

After you have restarted Firefox, you should then see the component in the newly created <tt>compreg.dat</tt> file.

Its contract ID will look like: @mozilla.org/commandlinehandler/general-startup;1?type=myapp

If you have the Extension Developer's Extension installed, you should be able to access the component via the Javascript shell, by examining the Components.classes and Components.classesByID arrays. You'll find an example in the tutorial.

Sample Code

const nsIAppShellService    = Components.interfaces.nsIAppShellService;
const nsISupports           = Components.interfaces.nsISupports;
const nsICategoryManager    = Components.interfaces.nsICategoryManager;
const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
const nsICommandLine        = Components.interfaces.nsICommandLine;
const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler;
const nsIFactory            = Components.interfaces.nsIFactory;
const nsIModule             = Components.interfaces.nsIModule;
const nsIWindowWatcher      = Components.interfaces.nsIWindowWatcher;

// CHANGEME: to the chrome URI of your extension or application
const CHROME_URI = "chrome://myapp/content/";

// CHANGEME: change the contract id, CID, and category to be unique
// to your application.
const clh_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=myapp";

// use uuidgen to generate a unique ID
const clh_CID = Components.ID("{2991c315-b871-42cd-b33f-bfee4fcbf682}");

// category names are sorted alphabetically. Typical command-line handlers use a
// category that begins with the letter "m".
const clh_category = "m-myapp";

/**
 * Utility functions
 */

/**
 * Opens a chrome window.
 * @param aChromeURISpec a string specifying the URI of the window to open.
 * @param aArgument an argument to pass to the window (may be null)
 */
function openWindow(aChromeURISpec, aArgument)
{
  var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
    getService(Components.interfaces.nsIWindowWatcher);
  ww.openWindow(null, aChromeURISpec, "_blank",
                "chrome,menubar,toolbar,status,resizable,dialog=no",
                aArgument);
}
 
/**
 * The XPCOM component that implements nsICommandLineHandler.
 * It also implements nsIFactory to serve as its own singleton factory.
 */
const myAppHandler = {
  /* nsISupports */
  QueryInterface : function clh_QI(iid)
  {
    if (iid.equals(nsICommandLineHandler) ||
        iid.equals(nsIFactory) ||
        iid.equals(nsISupports))
      return this;

    throw Components.results.NS_ERROR_NO_INTERFACE;
  },

  /* nsICommandLineHandler */

  handle : function clh_handle(cmdLine)
  {
    try {
      // CHANGEME: change "viewapp" to your command line flag that takes an argument
      var uristr = cmdLine.handleFlagWithParam("viewapp", false);
      if (uristr) {
        // convert uristr to an nsIURI
        var uri = cmdLine.resolveURI(uristr);
        openWindow(CHROME_URI, uri);
        cmdLine.preventDefault = true;
      }
    }
    catch (e) {
      Components.utils.reportError("incorrect parameter passed to -viewapp on the command line.");
    }

    // CHANGEME: change "myapp" to your command line flag (no argument)
    if (cmdLine.handleFlag("myapp", false)) {
      openWindow(CHROME_URI, null);
      cmdLine.preventDefault = true;
    }
  },

  // CHANGEME: change the help info as appropriate, but
  // follow the guidelines in nsICommandLineHandler.idl
  // specifically, flag descriptions should start at
  // character 24, and lines should be wrapped at
  // 72 characters with embedded newlines,
  // and finally, the string should end with a newline
  helpInfo : "  -myapp               Open My Application\n"  
             "  -viewapp <uri>       View and edit the URI in My Application,\n"  
             "                       wrapping this description\n",

  /* nsIFactory */

  createInstance : function clh_CI(outer, iid)
  {
    if (outer != null)
      throw Components.results.NS_ERROR_NO_AGGREGATION;

    return this.QueryInterface(iid);
  },

  lockFactory : function clh_lock(lock)
  {
    /* no-op */
  }
};

/**
 * The XPCOM glue that implements nsIModule
 */
const myAppHandlerModule = {
  /* nsISupports */
  QueryInterface : function mod_QI(iid)
  {
    if (iid.equals(nsIModule) ||
        iid.equals(nsISupports))
      return this;

    throw Components.results.NS_ERROR_NO_INTERFACE;
  },

  /* nsIModule */
  getClassObject : function mod_gch(compMgr, cid, iid)
  {
    if (cid.equals(clh_CID))
      return myAppHandler.QueryInterface(iid);

    throw Components.results.NS_ERROR_NOT_REGISTERED;
  },

  registerSelf : function mod_regself(compMgr, fileSpec, location, type)
  {
    compMgr.QueryInterface(nsIComponentRegistrar);

    compMgr.registerFactoryLocation(clh_CID,
                                    "myAppHandler",
                                    clh_contractID,
                                    fileSpec,
                                    location,
                                    type);

    var catMan = Components.classes["@mozilla.org/categorymanager;1"].
      getService(nsICategoryManager);
    catMan.addCategoryEntry("command-line-handler",
                            clh_category,
                            clh_contractID, true, true);
  },

  unregisterSelf : function mod_unreg(compMgr, location, type)
  {
    compMgr.QueryInterface(nsIComponentRegistrar);
    compMgr.unregisterFactoryLocation(clh_CID, location);

    var catMan = Components.classes["@mozilla.org/categorymanager;1"].
      getService(nsICategoryManager);
    catMan.deleteCategoryEntry("command-line-handler", clh_category);
  },

  canUnload : function (compMgr)
  {
    return true;
  }
};

/* The NSGetModule function is the magic entry point that XPCOM uses to find what XPCOM objects
 * this component provides
 */
function NSGetModule(comMgr, fileSpec)
{
  return myAppHandlerModule;
}

Revision Source

<p>
</p><p>Extensions and XUL applications can modify the way command line parameters are handled by writing a component that implements <a href="en/NsICommandLineHandler">nsICommandLineHandler</a> and registering it in a category.
</p>
<h3 name="Overview"> Overview </h3>
<p>The attached code is a component written using the <a href="en/XPCOM">XPCOM</a> framework, accessed from <a href="en/JavaScript">JavaScript</a> via <a href="en/XPConnect">XPConnect</a>  
</p><p>The official (terse) page for describing how Firefox handles command lines and initial startup can be found at the <a href="en/NsICommandLine">nsICommandLine</a> documentation.  There, you'll find links to the code for the <a href="en/XPIDL">XPIDL</a> file defining the interface implemented below, and to other related <tt>.idl</tt> files.
</p><p>The below example component will implement two command line parameters:
</p><p></p><dl>
<dt><tt>firefox.exe -myapp</tt>
</dt><dd>Opens a chrome window for My Application.
<p></p></dd><dt><tt>firefox.exe -viewapp <var>url</var></tt>
</dt><dd>Opens a chrome window for My Application and passes it a nsIURI object.
</dd></dl>
<p>For more clues on writing and registering XPCOM in Javascript, you can try
<a href="en/How_to_Build_an_XPCOM_Component_in_Javascript"> the tutorial</a> 
</p><p><span class="comment">N.B. the tutorial instructions for registering are not comprehensive,     and could easily take up their own page</span>
In order to register the component illustrated in the {{template.Anch("Sample code")}} section, save the JavaScript shown in the big box below into a file with the extension <tt>.js</tt> and drop it into one of the directories (see <a href="en/Bundles">Bundles</a>) that Firefox scans for extensions.  A typical location in Windows would be <i>"C:\Program Files\Mozilla Firefox\components"</i> or your extension's <tt>components</tt> directory.
</p><p>Then, (with Firefox shut down) delete the <tt>compreg.dat</tt> file, to force Firefox to rebuild its component list, and restart Firefox.  You'll find the <tt>compreg.dat</tt> file in your profile directory, not to be confused with the file by the same name in your Firefox directory.
</p><p>Your profile directory will be in your home directory (<i>~/.mozilla/firefox</i> under Linux) or in something like: <i>"C:\Documents and Settings\(user-name)\Application Data\Mozilla\Firefox\Profiles\"</i> under windows.
</p><p>After you have restarted Firefox, you should then see the component in the newly created <tt>compreg.dat</tt> file.
</p><p>Its contract ID will look like: <code>@mozilla.org/commandlinehandler/general-startup;1?type=myapp</code>
</p><p>If you have the <a class="external" href="http://ted.mielczarek.org/code/mozilla/extensiondev/">Extension Developer's Extension</a> installed, you should be able to access the component via the Javascript shell, by examining the Components.classes and Components.classesByID arrays.  You'll find an example in <a href="en/How_to_Build_an_XPCOM_Component_in_Javascript"> the tutorial.</a>
</p>
<h3 name="Sample_Code"> Sample Code </h3>
<pre class="eval">const <a href="en/NsIAppShellService">nsIAppShellService</a>    = Components.interfaces.nsIAppShellService;
const <a href="en/NsISupports">nsISupports</a>           = Components.interfaces.nsISupports;
const <a href="en/NsICategoryManager">nsICategoryManager</a>    = Components.interfaces.nsICategoryManager;
const <a href="en/NsIComponentRegistrar">nsIComponentRegistrar</a> = Components.interfaces.nsIComponentRegistrar;
const <a href="en/NsICommandLine">nsICommandLine</a>        = Components.interfaces.nsICommandLine;
const <a href="en/NsICommandLineHandler">nsICommandLineHandler</a> = Components.interfaces.nsICommandLineHandler;
const <a href="en/NsIFactory">nsIFactory</a>            = Components.interfaces.nsIFactory;
const <a href="en/NsIModule">nsIModule</a>             = Components.interfaces.nsIModule;
const <a href="en/NsIWindowWatcher">nsIWindowWatcher</a>      = Components.interfaces.nsIWindowWatcher;

// <b>CHANGEME: to the chrome URI of your extension or application</b>
const CHROME_URI = "chrome://myapp/content/";

// <b>CHANGEME: change the contract id, CID, and category to be unique</b>
// <b>to your application.</b>
const clh_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=myapp";

// use uuidgen to generate a unique ID
const clh_CID = Components.ID("{2991c315-b871-42cd-b33f-bfee4fcbf682}");

// category names are sorted alphabetically. Typical command-line handlers use a
// category that begins with the letter "m".
const clh_category = "m-myapp";

/**
 * Utility functions
 */

/**
 * Opens a chrome window.
 * @param aChromeURISpec a string specifying the URI of the window to open.
 * @param aArgument an argument to pass to the window (may be null)
 */
function openWindow(aChromeURISpec, aArgument)
{
  var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
    getService(Components.interfaces.<a href="en/NsIWindowWatcher">nsIWindowWatcher</a>);
  ww.openWindow(null, aChromeURISpec, "_blank",
                "chrome,menubar,toolbar,status,resizable,dialog=no",
                aArgument);
}
 
/**
 * The XPCOM component that implements <a href="en/NsICommandLineHandler">nsICommandLineHandler</a>.
 * It also implements <a href="en/NsIFactory">nsIFactory</a> to serve as its own singleton factory.
 */
const myAppHandler = {
  /* nsISupports */
  QueryInterface : function clh_QI(iid)
  {
    if (iid.equals(<a href="en/NsICommandLineHandler">nsICommandLineHandler</a>) ||
        iid.equals(<a href="en/NsIFactory">nsIFactory</a>) ||
        iid.equals(<a href="en/NsISupports">nsISupports</a>))
      return this;

    throw Components.results.<a href="en/NS_ERROR_NO_INTERFACE">NS_ERROR_NO_INTERFACE</a>;
  },

  /* <a href="en/NsICommandLineHandler">nsICommandLineHandler</a> */

  handle : function clh_handle(cmdLine)
  {
    try {
      // <b>CHANGEME: change "viewapp" to your command line flag that takes an argument</b>
      var uristr = cmdLine.handleFlagWithParam("viewapp", false);
      if (uristr) {
        // convert uristr to an <a href="en/NsIURI">nsIURI</a>
        var uri = cmdLine.resolveURI(uristr);
        openWindow(CHROME_URI, uri);
        cmdLine.preventDefault = true;
      }
    }
    catch (e) {
      Components.utils.reportError("incorrect parameter passed to -viewapp on the command line.");
    }

    // <b>CHANGEME: change "myapp" to your command line flag (no argument)</b>
    if (cmdLine.handleFlag("myapp", false)) {
      openWindow(CHROME_URI, null);
      cmdLine.preventDefault = true;
    }
  },

  <b>// CHANGEME: change the help info as appropriate, but</b>
  <b>// follow the guidelines in nsICommandLineHandler.idl</b>
  <b>// specifically, flag descriptions should start at</b>
  <b>// character 24, and lines should be wrapped at</b>
  <b>// 72 characters with embedded newlines,</b>
  <b>// and finally, the string should end with a newline</b>
  helpInfo : "  -myapp               Open My Application\n"  
             "  -viewapp &lt;uri&gt;       View and edit the URI in My Application,\n"  
             "                       wrapping this description\n",

  /* nsIFactory */

  createInstance : function clh_CI(outer, iid)
  {
    if (outer != null)
      throw Components.results.<a href="en/NS_ERROR_NO_AGGREGATION">NS_ERROR_NO_AGGREGATION</a>;

    return this.QueryInterface(iid);
  },

  lockFactory : function clh_lock(lock)
  {
    /* no-op */
  }
};

/**
 * The XPCOM glue that implements nsIModule
 */
const myAppHandlerModule = {
  /* nsISupports */
  QueryInterface : function mod_QI(iid)
  {
    if (iid.equals(<a href="en/NsIModule">nsIModule</a>) ||
        iid.equals(<a href="en/NsISupports">nsISupports</a>))
      return this;

    throw Components.results.<a href="en/NS_ERROR_NO_INTERFACE">NS_ERROR_NO_INTERFACE</a>;
  },

  /* nsIModule */
  getClassObject : function mod_gch(compMgr, cid, iid)
  {
    if (cid.equals(clh_CID))
      return myAppHandler.QueryInterface(iid);

    throw Components.results.<a href="en/NS_ERROR_NOT_REGISTERED">NS_ERROR_NOT_REGISTERED</a>;
  },

  registerSelf : function mod_regself(compMgr, fileSpec, location, type)
  {
    compMgr.QueryInterface(<a href="en/NsIComponentRegistrar">nsIComponentRegistrar</a>);

    compMgr.registerFactoryLocation(clh_CID,
                                    "myAppHandler",
                                    clh_contractID,
                                    fileSpec,
                                    location,
                                    type);

    var catMan = Components.classes["@mozilla.org/categorymanager;1"].
      getService(<a href="en/NsICategoryManager">nsICategoryManager</a>);
    catMan.addCategoryEntry("command-line-handler",
                            clh_category,
                            clh_contractID, true, true);
  },

  unregisterSelf : function mod_unreg(compMgr, location, type)
  {
    compMgr.QueryInterface(nsIComponentRegistrar);
    compMgr.unregisterFactoryLocation(clh_CID, location);

    var catMan = Components.classes["@mozilla.org/categorymanager;1"].
      getService(nsICategoryManager);
    catMan.deleteCategoryEntry("command-line-handler", clh_category);
  },

  canUnload : function (compMgr)
  {
    return true;
  }
};

/* The NSGetModule function is the magic entry point that XPCOM uses to find what XPCOM objects
 * this component provides
 */
function NSGetModule(comMgr, fileSpec)
{
  return myAppHandlerModule;
}
</pre>
Revert to this revision