home *** CD-ROM | disk | FTP | other *** search
/ Chip 2002 January / 01_02.iso / linux / mozilla-installer_linux / xpi / browser.xpi / bin / components / nsHelperAppDlg.js < prev    next >
Text File  |  2001-11-20  |  24KB  |  645 lines

  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: NPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Netscape Public License
  6.  * Version 1.1 (the "License"); you may not use this file except in
  7.  * compliance with the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/NPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Mozilla browser.
  16.  *
  17.  * The Initial Developer of the Original Code is 
  18.  * Netscape Communications Corporation.
  19.  * Portions created by the Initial Developer are Copyright (C) 2001
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *  Bill Law    <law@netscape.com>
  24.  *  Scott MacGregor <mscott@netscape.com>
  25.  *
  26.  *
  27.  * Alternatively, the contents of this file may be used under the terms of
  28.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  29.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30.  * in which case the provisions of the GPL or the LGPL are applicable instead
  31.  * of those above. If you wish to allow use of your version of this file only
  32.  * under the terms of either the GPL or the LGPL, and not to allow others to
  33.  * use your version of this file under the terms of the NPL, indicate your
  34.  * decision by deleting the provisions above and replace them with the notice
  35.  * and other provisions required by the GPL or the LGPL. If you do not delete
  36.  * the provisions above, a recipient may use your version of this file under
  37.  * the terms of any one of the NPL, the GPL or the LGPL.
  38.  *
  39.  * ***** END LICENSE BLOCK ***** */
  40.  
  41. /* This file implements the nsIHelperAppLauncherDialog interface.
  42.  *
  43.  * The implementation consists of a JavaScript "class" named nsHelperAppDialog,
  44.  * comprised of:
  45.  *   - a JS constructor function
  46.  *   - a prototype providing all the interface methods and implementation stuff
  47.  *
  48.  * In addition, this file implements an nsIModule object that registers the
  49.  * nsHelperAppDialog component.
  50.  */
  51.  
  52.  
  53. /* ctor
  54.  */
  55. function nsHelperAppDialog() {
  56.     // Initialize data properties.
  57.     this.mLauncher = null;
  58.     this.mContext  = null;
  59.     this.mSourcePath = null;
  60.     this.choseApp  = false;
  61.     this.chosenApp = null;
  62.     this.givenDefaultApp = false;
  63.     this.strings   = new Array;
  64.     this.elements  = new Array;
  65. }
  66.  
  67. nsHelperAppDialog.prototype = {
  68.     // Turn this on to get debugging messages.
  69.     debug: false,
  70.  
  71.     nsIMIMEInfo  : Components.interfaces.nsIMIMEInfo,
  72.  
  73.     // Dump text (if debug is on).
  74.     dump: function( text ) {
  75.         if ( this.debug ) {
  76.             dump( text ); 
  77.         }
  78.     },
  79.  
  80.     // This "class" supports nsIHelperAppLauncherDialog, and nsISupports.
  81.     QueryInterface: function (iid) {
  82.         if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
  83.             !iid.equals(Components.interfaces.nsISupports)) {
  84.             throw Components.results.NS_ERROR_NO_INTERFACE;
  85.         }
  86.         return this;
  87.     },
  88.  
  89.     // ---------- nsIHelperAppLauncherDialog methods ----------
  90.  
  91.     // show: Open XUL dialog using window watcher.  Since the dialog is not
  92.     //       modal, it needs to be a top level window and the way to open
  93.     //       one of those is via that route).
  94.     show: function(aLauncher, aContext)  {
  95.          this.mLauncher = aLauncher;
  96.          this.mContext  = aContext;
  97.          // Display the dialog using the Window Watcher interface.
  98.          var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  99.                     .getService( Components.interfaces.nsIWindowWatcher );
  100.          this.mDialog = ww.openWindow( null, // no parent
  101.                                        "chrome://global/content/nsHelperAppDlg.xul",
  102.                                        null,
  103.                                        "chrome,titlebar,dialog=yes",
  104.                                        null );
  105.          // Hook this object to the dialog.
  106.          this.mDialog.dialog = this;
  107.     },
  108.  
  109.     // promptForSaveToFile:  Display file picker dialog and return selected file.
  110.     promptForSaveToFile: function(aContext, aDefaultFile, aSuggestedFileExtension) {
  111.         var result = "";
  112.  
  113.         // Use file picker to show dialog.
  114.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  115.         var picker = Components.classes[ "@mozilla.org/filepicker;1" ]
  116.                        .createInstance( nsIFilePicker );
  117.         var bundle = Components.classes[ "@mozilla.org/intl/stringbundle;1" ]
  118.                        .getService( Components.interfaces.nsIStringBundleService )
  119.                            .createBundle( "chrome://global/locale/nsHelperAppDlg.properties");
  120.  
  121.         var windowTitle = bundle.GetStringFromName( "saveDialogTitle" );
  122.         
  123.         var parent = aContext
  124.                         .QueryInterface( Components.interfaces.nsIInterfaceRequestor )
  125.                         .getInterface( Components.interfaces.nsIDOMWindowInternal );
  126.         picker.init( parent, windowTitle, nsIFilePicker.modeSave );
  127.         picker.defaultString = aDefaultFile;
  128.  
  129.         var wildCardExtension = "*";
  130.         if ( aSuggestedFileExtension ) {
  131.             wildCardExtension += aSuggestedFileExtension;
  132.             picker.appendFilter( wildCardExtension, wildCardExtension );
  133.         }
  134.  
  135.         picker.appendFilters( nsIFilePicker.filterAll );
  136.  
  137.         // Pull in the user's preferences and get the default download directory.
  138.         var prefs = Components.classes[ "@mozilla.org/preferences-service;1" ]
  139.                               .getService( Components.interfaces.nsIPrefBranch );
  140.         try {
  141.             var startDir = prefs.getComplexValue("browser.download.dir",
  142.                                                  Components.interfaces.nsILocalFile);
  143.             if ( startDir.exists() ) {
  144.                 picker.displayDirectory = startDir;
  145.             }
  146.         } catch( exception ) {
  147.         }
  148.  
  149.         var dlgResult = picker.show();
  150.  
  151.         if ( dlgResult == nsIFilePicker.returnCancel ) {
  152.             throw Components.results.NS_ERROR_FAILURE;
  153.         }
  154.  
  155.  
  156.         // be sure to save the directory the user chose as the new browser.download.dir
  157.         result = picker.file;
  158.  
  159.         if ( result ) {
  160.             var newDir = result.parent;
  161.             prefs.setComplexValue("browser.download.dir",
  162.                                   Components.interfaces.nsILocalFile, newDir);
  163.         }
  164.         return result;
  165.     },
  166.     
  167.     // showProgressDialog:  For now, use old dialog.  At some point, the caller should be
  168.     //                      converted to use the new generic progress dialog (when it's
  169.     //                      finished).
  170.     showProgressDialog: function(aLauncher, aContext) {
  171.          // Display the dialog using the Window Watcher interface.
  172.          var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  173.                     .getService( Components.interfaces.nsIWindowWatcher );
  174.          ww.openWindow( null, // no parent
  175.                         "chrome://global/content/helperAppDldProgress.xul",
  176.                         null,
  177.                         "chrome,titlebar,minimizable,dialog=yes",
  178.                         aLauncher );
  179.     },
  180.     
  181.     // ---------- implementation methods ----------
  182.  
  183.     // initDialog:  Fill various dialog fields with initial content.
  184.     initDialog : function() {
  185.          // Check if file is executable (in which case, we will go straight to
  186.          // "save to disk").
  187.          var ignore1 = new Object;
  188.          var ignore2 = new Object;
  189.          var tmpFile = this.mLauncher.getDownloadInfo( ignore1, ignore2 );
  190.          if ( tmpFile.isExecutable() ) {
  191.              this.mLauncher.saveToDisk( null, false );
  192.              this.mDialog.close();
  193.              return;
  194.          }
  195.  
  196.          // Put product brand short name in prompt.
  197.          var prompt = this.dialogElement( "prompt" );
  198.          var modified = this.replaceInsert( prompt.firstChild.nodeValue, 1, this.getString( "brandShortName" ) );
  199.          prompt.firstChild.nodeValue = modified;
  200.  
  201.          // Put file name in window title.
  202.          var win   = this.dialogElement( "nsHelperAppDlg" );
  203.          var url   = this.mLauncher.source.QueryInterface( Components.interfaces.nsIURL );
  204.          var fname = "";
  205.          this.mSourcePath = url.prePath;
  206.          if ( url ) {
  207.              // A url, use file name from it.
  208.              fname = url.fileName;
  209.              this.mSourcePath += url.directory;
  210.          } else {
  211.              // A generic uri, use path.
  212.              fname = this.mLauncher.source.path;
  213.              this.mSourcePath += url.path;
  214.          }
  215.  
  216.          var title = this.replaceInsert( win.getAttribute( "title" ), 1, fname);
  217.          win.setAttribute( "title", title );
  218.  
  219.          // Put content type and location into intro.
  220.          this.initIntro(url);
  221.  
  222.          var iconString = "moz-icon://" + fname + "?size=32&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
  223.  
  224.          this.dialogElement("contentTypeImage").setAttribute("src", iconString);
  225.  
  226.          this.initAppAndSaveToDiskValues();
  227.  
  228.          // always make sure the window starts off with this checked....
  229.          this.dialogElement( "alwaysAskMe" ).checked = true;
  230.  
  231.          // Add special debug hook.
  232.          if ( this.debug ) {
  233.              prompt.setAttribute( "onclick", "dialog.doDebug()" );
  234.          }
  235.  
  236.          // Set up dialog button callbacks.
  237.          var object = this; // "this.onOK()" doesn't work!
  238.          this.mDialog.doSetOKCancel( function () { return object.onOK(); },
  239.                                      function () { return object.onCancel(); } );
  240.  
  241.          // Position it.
  242.          if ( this.mDialog.opener ) {
  243.              this.mDialog.moveToAlertPosition();
  244.          } else {
  245.              this.mDialog.sizeToContent();
  246.              this.mDialog.centerWindowOnScreen();
  247.          }
  248.  
  249.          // Set initial focus
  250.          this.dialogElement( "mode" ).focus();
  251.     },
  252.  
  253.     // initIntro:
  254.     initIntro: function(url) {
  255.         var intro = this.dialogElement( "intro" );
  256.         var desc = this.mLauncher.MIMEInfo.Description;
  257.         var modified;
  258.         if ( desc != "" ) 
  259.         {
  260.           // Use intro with descriptive text.
  261.           modified = this.replaceInsert( this.getString( "intro.withDesc" ), 1, this.mLauncher.MIMEInfo.Description );
  262.         } 
  263.         else 
  264.         {
  265.           // Use intro without descriptive text.
  266.           modified = this.getString( "intro.noDesc" );
  267.         }
  268.  
  269.         modified = this.replaceInsert( modified, 2, this.mLauncher.MIMEInfo.MIMEType );
  270.  
  271.         // if mSourcePath is a local file, then let's use the pretty path name instead of an ugly
  272.         // url...
  273.         var pathString = this.mSourcePath;
  274.         try 
  275.         {
  276.           var fileURL = url.QueryInterface( Components.interfaces.nsIFileURL);
  277.           if (fileURL)
  278.           {
  279.              var fileObject = fileURL.file;
  280.              if (fileObject)
  281.              {
  282.                var parentObject = fileObject.parent;
  283.                if (parentObject)
  284.                {
  285.                  pathString = parentObject.unicodePath;
  286.                }
  287.              }
  288.           }
  289.         } catch(ex) {}
  290.  
  291.  
  292.         intro.firstChild.nodeValue = "";
  293.         intro.firstChild.nodeValue = modified;
  294.  
  295.         // Set the location text, which is separate from the intro text so it can be cropped
  296.         var location = this.dialogElement( "location" );
  297.         location.value = pathString;
  298.     },
  299.  
  300.     // initAppAndSaveToDiskValues:
  301.     initAppAndSaveToDiskValues: function() {
  302.  
  303.         // Pre-select the choice the user made last time.
  304.         this.chosenApp = this.mLauncher.MIMEInfo.preferredApplicationHandler;
  305.         var applicationDescription = this.mLauncher.MIMEInfo.applicationDescription;
  306.  
  307.         if (applicationDescription != "")
  308.         {
  309.           this.updateApplicationName(applicationDescription); 
  310.           this.givenDefaultApp = true;
  311.         }
  312.         else if (this.chosenApp && this.chosenApp.unicodePath)
  313.         {
  314.           // If a user-chosen application, show its path.
  315.           this.updateApplicationName(this.chosenApp.unicodePath);
  316.           this.choseApp = true;
  317.         }
  318.         else
  319.          this.updateApplicationName(this.getString("noApplicationSpecified"));
  320.  
  321.         if ( (applicationDescription || this.choseApp) && this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk ) 
  322.         {
  323.           var openUsing = this.dialogElement( "openUsing" );
  324.           openUsing.radioGroup.selectedItem = openUsing;
  325.         }
  326.         else 
  327.         {
  328.           // Save to disk.
  329.           var saveToDisk = this.dialogElement( "saveToDisk" );
  330.           saveToDisk.radioGroup.selectedItem = saveToDisk;
  331.           // Disable choose app button.
  332.           this.dialogElement( "chooseApp" ).setAttribute( "disabled", "true" );
  333.         }
  334.     },
  335.  
  336.     updateApplicationName: function(newValue)
  337.     {
  338.       var applicationText = this.getString( "openUsingString" );
  339.       applicationText = this.replaceInsert( applicationText, 1, newValue );
  340.       var expl = this.dialogElement( "openUsing" );
  341.       expl.label = applicationText;
  342.     },
  343.  
  344.     // Enable pick app button if the user chooses that option.
  345.     toggleChoice : function () {
  346.         // See what option is selected.
  347.         if ( this.dialogElement( "openUsing" ).selected ) {
  348.             // We can enable the pick app button.
  349.             this.dialogElement( "chooseApp" ).removeAttribute( "disabled" );
  350.         } else {
  351.             // We can disable the pick app button.
  352.             this.dialogElement( "chooseApp" ).setAttribute( "disabled", "true" );
  353.         }
  354.  
  355.        this.updateOKButton();
  356.     },
  357.  
  358.     processAlwaysAskState : function () 
  359.     {
  360.       // if the user deselected the always ask checkbox, then store that on the mime object for future use...
  361.       if (!this.dialogElement( "alwaysAskMe" ).checked)
  362.       {
  363.         // we first need to rest the user action if the user selected save to disk instead of open...
  364.         // reset the preferred action in this case...we need to do this b4 setting the always ask before handling state
  365.  
  366.         if (!this.dialogElement( "openUsing" ).selected)
  367.         this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
  368.          
  369.  
  370.         this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = false;
  371.       }
  372.     },
  373.     updateOKButton: function() {
  374.         var ok = false;
  375.         if ( this.dialogElement( "saveToDisk" ).selected ) 
  376.         {
  377.             // This is always OK.
  378.             ok = true;
  379.         } 
  380.         else 
  381.         {
  382.           // only enable the OK button if we have a default app to use or if 
  383.           // the user chose an app....
  384.           if ((this.choseApp && this.chosenApp.unicodePath) || this.givenDefaultApp)
  385.             ok = true;
  386.         }
  387.         
  388.         // Enable Ok button if ok to press.
  389.         this.dialogElement( "ok" ).disabled = !ok;
  390.     },
  391.  
  392.     // onOK:
  393.     onOK: function() {
  394.  
  395.       this.processAlwaysAskState(); 
  396.  
  397.       if ( this.dialogElement( "openUsing" ).selected ) 
  398.       {
  399.          // If no app "chosen" then convert input string to file.
  400.          if (this.chosenApp)
  401.            this.mLauncher.launchWithApplication( this.chosenApp, false );
  402.           else 
  403.            this.mLauncher.launchWithApplication( null, false );
  404.       }
  405.       else
  406.         this.mLauncher.saveToDisk( null, false );
  407.         
  408.       // Unhook dialog from this object.
  409.       this.mDialog.dialog = null;
  410.  
  411.       // Close up dialog by returning true.
  412.       return true;
  413.      //this.mDialog.close();
  414.     },
  415.  
  416.     // onCancel:
  417.     onCancel: function() {
  418.         // Cancel app launcher.
  419.         try {
  420.             this.mLauncher.Cancel();
  421.         } catch( exception ) {
  422.         }
  423.         
  424.         // Unhook dialog from this object.
  425.         this.mDialog.dialog = null;
  426.  
  427.         // Close up dialog by returning true.
  428.         return true;
  429.     },
  430.  
  431.     // dialogElement:  Try cache; obtain from document if not there.
  432.     dialogElement: function( id ) {
  433.          // Check if we've already fetched it.
  434.          if ( !( id in this.elements ) ) {
  435.              // No, then get it from dialog.
  436.              this.elements[ id ] = this.mDialog.document.getElementById( id );
  437.          }
  438.          return this.elements[ id ];
  439.     },
  440.  
  441.     // chooseApp:  Open file picker and prompt user for application.
  442.     chooseApp: function() {
  443.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  444.         var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance( nsIFilePicker );
  445.         fp.init( this.mDialog,
  446.                  this.getString( "chooseAppFilePickerTitle" ),
  447.                  nsIFilePicker.modeOpen );
  448.  
  449.         // XXX - We want to say nsIFilePicker.filterExecutable or something
  450.         fp.appendFilters( nsIFilePicker.filterAll );
  451.         
  452.         if ( fp.show() == nsIFilePicker.returnOK && fp.file ) {
  453.             // Remember the file they chose to run.
  454.             this.choseApp = true;
  455.             this.chosenApp    = fp.file;
  456.             // Update dialog.
  457.  
  458.             this.updateApplicationName(this.chosenApp.unicodePath);
  459.         }
  460.     },
  461.  
  462.     // setDefault:  Open "edit MIMEInfo" dialog (borrowed from prefs).
  463.     setDefault: function() {
  464.         // Get RDF service.
  465.         var rdf = Components.classes[ "@mozilla.org/rdf/rdf-service;1" ]
  466.                     .getService( Components.interfaces.nsIRDFService );
  467.         // Now ask if it knows about this mime type.
  468.         var exists = false;
  469.         var fileLocator = Components.classes[ "@mozilla.org/file/directory_service;1" ]
  470.                             .getService( Components.interfaces.nsIProperties );
  471.         var file        = fileLocator.get( "UMimTyp", Components.interfaces.nsIFile );
  472.         
  473.         // We must try creating a fresh remote DS in order to avoid accidentally
  474.         // having GetDataSource trigger an asych load.
  475.         var ds = Components.classes[ "@mozilla.org/rdf/datasource;1?name=xml-datasource" ].createInstance( Components.interfaces.nsIRDFDataSource );
  476.         try {
  477.             // Initialize it.  This will fail if the uriloader (or anybody else)
  478.             // has already loaded/registered this data source.
  479.             var remoteDS = ds.QueryInterface( Components.interfaces.nsIRDFRemoteDataSource );
  480.             remoteDS.Init( file.URL );
  481.             remoteDS.Refresh( true );
  482.         } catch ( all ) {
  483.             // OK then, presume it was already registered; get it.
  484.             ds = rdf.GetDataSource( file.URL );
  485.         }
  486.  
  487.         // Now check if this mimetype is really in there;
  488.         // This is done by seeing if there's a "value" arc from the mimetype resource
  489.         // to the mimetype literal string.
  490.         var mimeRes       = rdf.GetResource( "urn:mimetype:" + this.mLauncher.MIMEInfo.MIMEType );
  491.         var valueProperty = rdf.GetResource( "http://home.netscape.com/NC-rdf#value" );
  492.         var mimeLiteral   = rdf.GetLiteral( this.mLauncher.MIMEInfo.MIMEType );
  493.         exists =  ds.HasAssertion( mimeRes, valueProperty, mimeLiteral, true );
  494.  
  495.         var dlgUrl;
  496.         if ( exists ) {
  497.             // Open "edit mime type" dialog.
  498.             dlgUrl = "chrome://communicator/content/pref/pref-applications-edit.xul";
  499.         } else {
  500.             // Open "add mime type" dialog.
  501.             dlgUrl = "chrome://communicator/content/pref/pref-applications-new.xul";
  502.         }
  503.  
  504.         // Open whichever dialog is appropriate, passing this dialog object as argument.
  505.         this.mDialog.openDialog( dlgUrl,
  506.                                  "_blank",
  507.                                  "chrome,modal=yes,resizable=no",
  508.                                  this );
  509.  
  510.         // Refresh dialog with updated info about the default action.
  511.         this.initIntro();
  512.         this.initAppAndSaveToDiskValues();
  513.     },
  514.  
  515.     // updateMIMEInfo:  This is called from the pref-applications-edit dialog when the user
  516.     //                  presses OK.  Take the updated MIMEInfo and have the helper app service
  517.     //                  "write" it back out to the RDF datasource.
  518.     updateMIMEInfo: function() {
  519.         this.dumpObjectProperties( "\tMIMEInfo", this.mLauncher.MIMEInfo );
  520.     },
  521.  
  522.     // dumpInfo:
  523.     doDebug: function() {
  524.         const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
  525.         // Open new progress dialog.
  526.         var progress = Components.classes[ "@mozilla.org/progressdialog;1" ]
  527.                          .createInstance( nsIProgressDialog );
  528.         // Show it.
  529.         progress.open( this.mDialog );
  530.     },
  531.  
  532.     // dumpObj:
  533.     dumpObj: function( spec ) {
  534.          var val = "<undefined>";
  535.          try {
  536.              val = eval( "this."+spec ).toString();
  537.          } catch( exception ) {
  538.          }
  539.          this.dump( spec + "=" + val + "\n" );
  540.     },
  541.  
  542.     // dumpObjectProperties
  543.     dumpObjectProperties: function( desc, obj ) {
  544.          for( prop in obj ) {
  545.              this.dump( desc + "." + prop + "=" );
  546.              var val = "<undefined>";
  547.              try {
  548.                  val = obj[ prop ];
  549.              } catch ( exception ) {
  550.              }
  551.              this.dump( val + "\n" );
  552.          }
  553.     },
  554.  
  555.     // getString: Fetch data string from dialog content (and cache it).
  556.     getString: function( id ) {
  557.         // Check if we've fetched this string already.
  558.         if ( !( id in this.strings ) ) {
  559.             // Try to get it.
  560.             var elem = this.mDialog.document.getElementById( id );
  561.             if ( elem
  562.                  &&
  563.                  elem.firstChild
  564.                  &&
  565.                  elem.firstChild.nodeValue ) {
  566.                 this.strings[ id ] = elem.firstChild.nodeValue;
  567.             } else {
  568.                 // If unable to fetch string, use an empty string.
  569.                 this.strings[ id ] = "";
  570.             }
  571.         }
  572.         return this.strings[ id ];
  573.     },
  574.  
  575.     // replaceInsert: Replace given insert with replacement text and return the result.
  576.     replaceInsert: function( text, insertNo, replacementText ) {
  577.         var result = text;
  578.         var regExp = new RegExp("#"+insertNo);
  579.         result = result.replace( regExp, replacementText );
  580.         return result;
  581.     }
  582. }
  583.  
  584. // This Component's module implementation.  All the code below is used to get this
  585. // component registered and accessible via XPCOM.
  586. var module = {
  587.     firstTime: true,
  588.  
  589.     // registerSelf: Register this component.
  590.     registerSelf: function (compMgr, fileSpec, location, type) {
  591.         if (this.firstTime) {
  592.             this.firstTime = false;
  593.             throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  594.         }
  595.         compMgr.registerComponentWithType( this.cid,
  596.                                            "Mozilla Helper App Launcher Dialog",
  597.                                            this.contractId,
  598.                                            fileSpec,
  599.                                            location,
  600.                                            true,
  601.                                            true,
  602.                                            type );
  603.     },
  604.  
  605.     // getClassObject: Return this component's factory object.
  606.     getClassObject: function (compMgr, cid, iid) {
  607.         if (!cid.equals(this.cid)) {
  608.             throw Components.results.NS_ERROR_NO_INTERFACE;
  609.         }
  610.  
  611.         if (!iid.equals(Components.interfaces.nsIFactory)) {
  612.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  613.         }
  614.  
  615.         return this.factory;
  616.     },
  617.  
  618.     /* CID for this class */
  619.     cid: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
  620.  
  621.     /* Contract ID for this class */
  622.     contractId: "@mozilla.org/helperapplauncherdialog;1",
  623.  
  624.     /* factory object */
  625.     factory: {
  626.         // createInstance: Return a new nsProgressDialog object.
  627.         createInstance: function (outer, iid) {
  628.             if (outer != null)
  629.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  630.  
  631.             return (new nsHelperAppDialog()).QueryInterface(iid);
  632.         }
  633.     },
  634.  
  635.     // canUnload: n/a (returns true)
  636.     canUnload: function(compMgr) {
  637.         return true;
  638.     }
  639. };
  640.  
  641. // NSGetModule: Return the nsIModule object.
  642. function NSGetModule(compMgr, fileSpec) {
  643.     return module;
  644. }
  645.