Providing Command-Line Options

  • Revision slug: Chrome/Command_Line
  • Revision title: Providing Command-Line Options
  • Revision id: 50376
  • Created:
  • Creator: Mzarathustra
  • Is current revision? No
  • Comment /* Overview */

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 in XPCOM. For more on XPCOM, see http://developer.mozilla.org/en/docs/XPCOM Note the link on that page about avoiding memory leaks.

An overview of how Firefox handles command lines and initial startup can be found at the nsICommandLine documentation. This 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 on the difference between an API and an Application Framework, see http://en.wikipedia.org/wiki/Application_framework

As you may have guessed, the XPCOM framework was inspired by Microsoft's COM, which tends to explain the verbosity of it. XPCOM components can be written in various different languages. The below example is in Javascript, which operates via the XPConnect bridge. See http://developer.mozilla.org/en/docs/XPConnect for more on XPConnect.

In order to "register" the component illustrated in the Sample Code, simply save the Javascript shown in the big box below into a file with the extension .js and drop it into one of the directories that Firefox scans for extensions. A typical location in Windows would be: "C:\Program Files\Mozilla Firefox\components"

Then, (with Firefox shut down) delete the compreg.dat file, to force Firefox to rebuild its component list, and restart Firefox. You'll find the compreg.dat file in your profile directory, and to save time you may wish to use some sort file/find application (or simply the `find` command under Linux), starting from the Firefox install directory.

After you have restarted Firefox, you should then see the component using ordinary javascript by using the privileged array Components from a local .html file, after saying something like:

 <script>
 netscape.security.PrivilegeManager.
      enablePrivilege("UniversalXPConnect");
 for (el in Components.classesByID) { 
   document.write(el + '<br>'); .... 
 }
 <script>

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 in XPCOM.  For more on XPCOM, see http://developer.mozilla.org/en/docs/XPCOM  Note the link on that page about avoiding memory leaks.
</p><p>An overview of how Firefox handles command lines and initial startup can be found at the <a href="en/NsICommandLine">nsICommandLine</a> documentation. This example component will implement two command line parameters:
</p><p></p><dl>
<p></p><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.
<p></p></dd></dl>
<p>For more on the difference between an API and an Application Framework, see http://en.wikipedia.org/wiki/Application_framework
</p><p>As you may have guessed, the XPCOM framework was inspired by Microsoft's COM, which tends to explain the verbosity of it.  XPCOM components can be written in various different languages.  The below example is in Javascript, which operates via the XPConnect bridge.  See http://developer.mozilla.org/en/docs/XPConnect for more on XPConnect.
</p><p>In order to "register" the component illustrated in the Sample Code, simply save the Javascript shown in the big box below into a file with the extension <b>.js</b> and drop it into one of the directories that Firefox scans for extensions.  A typical location in Windows would be: <i>"C:\Program Files\Mozilla Firefox\components"</i>
</p><p>Then, (with Firefox shut down) delete the <b>compreg.dat</b> file, to force Firefox to rebuild its component list, and restart Firefox.  You'll find the <b>compreg.dat</b> file in your profile directory, and to save time you may wish to use some sort  file/find application (or simply the `find` command under Linux), starting from the Firefox install directory.
</p><p>After you have restarted Firefox, you should then see the component using ordinary javascript by using the privileged array Components from a local <b>.html</b> file, after saying something like: 
</p><p><code>  
</code></p>
<pre class="eval"> &lt;script&gt;
 netscape.security.PrivilegeManager.
      enablePrivilege("UniversalXPConnect");
 for (el in Components.classesByID) { 
   document.write(el + '&lt;br&gt;'); .... 
 }
 &lt;script&gt;
</pre>
<p>
</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