home *** CD-ROM | disk | FTP | other *** search
/ InterCD 2001 November / november_2001.iso / Browsers / Netscape 6.1 / browser.xpi / bin / components / nsHelperAppDlg.js < prev    next >
Encoding:
JavaScript  |  2001-07-05  |  23.1 KB  |  621 lines

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