home *** CD-ROM | disk | FTP | other *** search
/ PC World 2003 July & August / PCWorld_2003-07-08_cd.bin / Komunik / firebird / MozillaFirebird-0.6-win32.exe / MozillaFirebird / components / nsHelperAppDlg.js < prev    next >
Text File  |  2003-02-22  |  32KB  |  794 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.chosenApp = null;
  61.     this.givenDefaultApp = false;
  62.     this.strings   = new Array;
  63.     this.elements  = new Array;
  64.     this.updateSelf = true;
  65.     this.mTitle    = "";
  66.     this.mIsMac    = false;
  67. }
  68.  
  69. nsHelperAppDialog.prototype = {
  70.     // Turn this on to get debugging messages.
  71.     debug: false,
  72.  
  73.     nsIMIMEInfo  : Components.interfaces.nsIMIMEInfo,
  74.  
  75.     // Dump text (if debug is on).
  76.     dump: function( text ) {
  77.         if ( this.debug ) {
  78.             dump( text ); 
  79.         }
  80.     },
  81.  
  82.     // This "class" supports nsIHelperAppLauncherDialog, and nsISupports.
  83.     QueryInterface: function (iid) {
  84.         if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
  85.             !iid.equals(Components.interfaces.nsISupports)) {
  86.             throw Components.results.NS_ERROR_NO_INTERFACE;
  87.         }
  88.         return this;
  89.     },
  90.  
  91.     // ---------- nsIHelperAppLauncherDialog methods ----------
  92.  
  93.     // show: Open XUL dialog using window watcher.  Since the dialog is not
  94.     //       modal, it needs to be a top level window and the way to open
  95.     //       one of those is via that route).
  96.     show: function(aLauncher, aContext)  {
  97.          this.mLauncher = aLauncher;
  98.          this.mContext  = aContext;
  99.          // Display the dialog using the Window Watcher interface.
  100.          var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  101.                     .getService( Components.interfaces.nsIWindowWatcher );
  102.          this.mDialog = ww.openWindow( null, // no parent
  103.                                        "chrome://global/content/nsHelperAppDlg.xul",
  104.                                        null,
  105.                                        "chrome,titlebar,dialog=yes",
  106.                                        null );
  107.          // Hook this object to the dialog.
  108.          this.mDialog.dialog = this;
  109.          // Watch for error notifications.
  110.          this.mIsMac = (this.mDialog.navigator.platform.indexOf( "Mac" ) != -1);
  111.          this.progressListener.helperAppDlg = this;
  112.          this.mLauncher.setWebProgressListener( this.progressListener );
  113.     },
  114.  
  115.     // promptForSaveToFile:  Display file picker dialog and return selected file.
  116.     promptForSaveToFile: function(aContext, aDefaultFile, aSuggestedFileExtension) {
  117.         var result = "";
  118.  
  119.         // Use file picker to show dialog.
  120.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  121.         var picker = Components.classes[ "@mozilla.org/filepicker;1" ]
  122.                        .createInstance( nsIFilePicker );
  123.         var bundle = Components.classes[ "@mozilla.org/intl/stringbundle;1" ]
  124.                        .getService( Components.interfaces.nsIStringBundleService )
  125.                            .createBundle( "chrome://global/locale/nsHelperAppDlg.properties");
  126.  
  127.         var windowTitle = bundle.GetStringFromName( "saveDialogTitle" );
  128.         
  129.         var parent = aContext
  130.                         .QueryInterface( Components.interfaces.nsIInterfaceRequestor )
  131.                         .getInterface( Components.interfaces.nsIDOMWindowInternal );
  132.         picker.init( parent, windowTitle, nsIFilePicker.modeSave );
  133.         picker.defaultString = aDefaultFile;
  134.         if (aSuggestedFileExtension) {
  135.             // aSuggestedFileExtension includes the period, so strip it
  136.             picker.defaultExtension = aSuggestedFileExtension.substring(1);
  137.         } else {
  138.             try {
  139.                 picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
  140.             } catch (ex) {
  141.             }
  142.         }
  143.  
  144.         var wildCardExtension = "*";
  145.         if ( aSuggestedFileExtension ) {
  146.             wildCardExtension += aSuggestedFileExtension;
  147.             picker.appendFilter( wildCardExtension, wildCardExtension );
  148.         }
  149.  
  150.         picker.appendFilters( nsIFilePicker.filterAll );
  151.  
  152.         // Pull in the user's preferences and get the default download directory.
  153.         var prefs = Components.classes[ "@mozilla.org/preferences-service;1" ]
  154.                               .getService( Components.interfaces.nsIPrefBranch );
  155.         try {
  156.             var startDir = prefs.getComplexValue("browser.download.dir",
  157.                                                  Components.interfaces.nsILocalFile);
  158.             if ( startDir.exists() ) {
  159.                 picker.displayDirectory = startDir;
  160.             }
  161.         } catch( exception ) {
  162.         }
  163.  
  164.         var dlgResult = picker.show();
  165.  
  166.         if ( dlgResult == nsIFilePicker.returnCancel ) {
  167.             // Null result means user cancelled.
  168.             return null;
  169.         }
  170.  
  171.  
  172.         // be sure to save the directory the user chose as the new browser.download.dir
  173.         result = picker.file;
  174.  
  175.         if ( result ) {
  176.             var newDir = result.parent;
  177.             prefs.setComplexValue("browser.download.dir",
  178.                                   Components.interfaces.nsILocalFile, newDir);
  179.         }
  180.         return result;
  181.     },
  182.     
  183.     // showProgressDialog:  For now, use old dialog.  At some point, the caller should be
  184.     //                      converted to use the new generic progress dialog (when it's
  185.     //                      finished).
  186.     showProgressDialog: function(aLauncher, aContext) {
  187.          // Display the dialog using the Window Watcher interface.
  188.          var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  189.                     .getService( Components.interfaces.nsIWindowWatcher );
  190.          ww.openWindow( null, // no parent
  191.                         "chrome://global/content/nsProgressDlg.xul",
  192.                         null,
  193.                         "chrome,titlebar,minimizable,dialog=yes",
  194.                         aLauncher );
  195.     },
  196.     
  197.     // ---------- implementation methods ----------
  198.  
  199.     // Web progress listener so we can detect errors while mLauncher is
  200.     // streaming the data to a temporary file.
  201.     progressListener: {
  202.         // Implementation properties.
  203.         helperAppDlg: null,
  204.  
  205.         // nsIWebProgressListener methods.
  206.         // Look for error notifications and display alert to user.
  207.         onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
  208.             if ( aStatus != Components.results.NS_OK ) {
  209.                 // Get prompt service.
  210.                 var prompter = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
  211.                                    .getService( Components.interfaces.nsIPromptService );
  212.                 // Display error alert (using text supplied by back-end).
  213.                 prompter.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
  214.  
  215.                 // Close the dialog.
  216.                 this.helperAppDlg.onCancel();
  217.                 if ( this.helperAppDlg.mDialog ) {
  218.                     this.helperAppDlg.mDialog.close();
  219.                 }
  220.             }
  221.         },
  222.  
  223.         // Ignore onProgressChange, onStateChange, onLocationChange, and onSecurityChange notifications.
  224.         onProgressChange: function( aWebProgress,
  225.                                     aRequest,
  226.                                     aCurSelfProgress,
  227.                                     aMaxSelfProgress,
  228.                                     aCurTotalProgress,
  229.                                     aMaxTotalProgress ) {
  230.         },
  231.  
  232.         onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
  233.         },
  234.  
  235.         onLocationChange: function( aWebProgress, aRequest, aLocation ) {
  236.         },
  237.  
  238.         onSecurityChange: function( aWebProgress, aRequest, state ) {
  239.         }
  240.     },
  241.  
  242.     // initDialog:  Fill various dialog fields with initial content.
  243.     initDialog : function() {
  244.          // Put product brand short name in prompt.
  245.          var prompt = this.dialogElement( "prompt" );
  246.          var modified = this.replaceInsert( prompt.firstChild.nodeValue, 1, this.getString( "brandShortName" ) );
  247.          prompt.firstChild.nodeValue = modified;
  248.  
  249.          // Put file name in window title.
  250.          var win   = this.dialogElement( "nsHelperAppDlg" );
  251.          var suggestedFileName = this.mLauncher.suggestedFileName;
  252.  
  253.          // Some URIs do not implement nsIURL, so we can't just QI.
  254.          var url   = this.mLauncher.source;
  255.          var fname = "";
  256.          this.mSourcePath = url.prePath;
  257.          try {
  258.              url = url.QueryInterface( Components.interfaces.nsIURL );
  259.              // A url, use file name from it.
  260.              fname = url.fileName;
  261.              this.mSourcePath += url.directory;
  262.          } catch (ex) {
  263.              // A generic uri, use path.
  264.              fname = url.path;
  265.              this.mSourcePath += url.path;
  266.          }
  267.  
  268.          if (suggestedFileName)
  269.            fname = suggestedFileName;
  270.            
  271.  
  272.          this.mTitle = this.replaceInsert( win.getAttribute( "title" ), 1, fname);
  273.          win.setAttribute( "title", this.mTitle );
  274.  
  275.          // Put content type, filename and location into intro.
  276.          this.initIntro(url, fname);
  277.  
  278.          var iconString = "moz-icon://" + fname + "?size=32&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
  279.  
  280.          this.dialogElement("contentTypeImage").setAttribute("src", iconString);
  281.  
  282.          this.initAppAndSaveToDiskValues();
  283.  
  284.          // Initialize "always ask me" box. This should always be disabled
  285.          // and set to true for the ambiguous type application/octet-stream.
  286.          var alwaysAskCheckbox = this.dialogElement( "alwaysAskMe" );
  287.          if (this.mLauncher.MIMEInfo.MIMEType == "application/octet-stream") {
  288.             alwaysAskCheckbox.checked = true;
  289.             alwaysAskCheckbox.disabled = true;
  290.          }
  291.          else {
  292.             alwaysAskCheckbox.checked = this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  293.          }
  294.  
  295.          // Position it.
  296.          if ( this.mDialog.opener ) {
  297.              this.mDialog.moveToAlertPosition();
  298.          } else {
  299.              this.mDialog.sizeToContent();
  300.              this.mDialog.centerWindowOnScreen();
  301.          }
  302.  
  303.          // Set initial focus
  304.          this.dialogElement( "mode" ).focus();
  305.     },
  306.  
  307.     // initIntro:
  308.     initIntro: function(url, filename) {
  309.         var intro = this.dialogElement( "intro" );
  310.         var desc = this.mLauncher.MIMEInfo.Description;
  311.         var modified;
  312.         if ( desc ) 
  313.         {
  314.           // Use intro with descriptive text.
  315.           modified = this.replaceInsert( this.getString( "intro.withDesc" ), 1, desc );
  316.         } 
  317.         else 
  318.         {
  319.           // Use intro without descriptive text.
  320.           modified = this.getString( "intro.noDesc" );
  321.         }
  322.  
  323.         modified = this.replaceInsert( modified, 2, this.mLauncher.MIMEInfo.MIMEType );
  324.         modified = this.replaceInsert( modified, 3, filename);
  325.         modified = this.replaceInsert( modified, 4, this.getString( "brandShortName" ));
  326.  
  327.         // if mSourcePath is a local file, then let's use the pretty path name instead of an ugly
  328.         // url...
  329.         var pathString = this.mSourcePath;
  330.         try 
  331.         {
  332.           var fileURL = url.QueryInterface(Components.interfaces.nsIFileURL);
  333.           if (fileURL)
  334.           {
  335.             var fileObject = fileURL.file;
  336.             if (fileObject)
  337.             {
  338.               var parentObject = fileObject.parent;
  339.               if (parentObject)
  340.               {
  341.                 pathString = parentObject.path;
  342.               }
  343.             }
  344.           }
  345.         } catch(ex) {}
  346.  
  347.  
  348.         intro.firstChild.nodeValue = "";
  349.         intro.firstChild.nodeValue = modified;
  350.  
  351.         // Set the location text, which is separate from the intro text so it can be cropped
  352.         var location = this.dialogElement( "location" );
  353.         location.value = pathString;
  354.     },
  355.  
  356.     // Returns true iff opening the default application makes sense.
  357.     openWithDefaultOK: function() {
  358.         var result;
  359.  
  360.         // The checking is different on Windows...
  361.         if ( this.mDialog.navigator.platform.indexOf( "Win" ) != -1 ) {
  362.             // Windows presents some special cases.
  363.             // We need to prevent use of "system default" when the file is
  364.             // executable (so the user doesn't launch nasty programs downloaded
  365.             // from the web), and, enable use of "system default" if it isn't
  366.             // executable (because we will prompt the user for the default app
  367.             // in that case).
  368.             
  369.             // Need to get temporary file and check for executable-ness.
  370.             var ignore1 = new Object;
  371.             var ignore2 = new Object;
  372.             var tmpFile = this.mLauncher.getDownloadInfo( ignore1, ignore2 );
  373.             
  374.             //  Default is Ok if the file isn't executable (and vice-versa).
  375.             result = !tmpFile.isExecutable();
  376.         } else {
  377.             // On other platforms, default is Ok if there is a default app.
  378.             // Note that nsIMIMEInfo providers need to ensure that this holds true
  379.             // on each platform.
  380.             result = this.mLauncher.MIMEInfo.defaultApplicationHandler;
  381.         }
  382.         return result;
  383.     },
  384.     
  385.     // Set "default" application description field.
  386.     initDefaultApp: function() {
  387.         // Use description, if we can get one.
  388.         var desc = this.mLauncher.MIMEInfo.defaultDescription;
  389.         if ( desc ) {
  390.             this.dialogElement( "useSystemDefault" ).label = this.replaceInsert( this.getString( "defaultApp" ), 1, desc );
  391.         }
  392.     },
  393.  
  394.     // getPath:
  395.     getPath: function(file) {
  396.         if (this.mIsMac) {
  397.             return file.leafName || file.path;
  398.         }
  399.  
  400.         return file.path;
  401.     },
  402.     
  403.     // initAppAndSaveToDiskValues:
  404.     initAppAndSaveToDiskValues: function() {
  405.         // Fill in helper app info, if there is any.
  406.         this.chosenApp = this.mLauncher.MIMEInfo.preferredApplicationHandler;
  407.         // Initialize "default application" field.
  408.         this.initDefaultApp();
  409.         
  410.         // Fill application name textbox.
  411.         if (this.chosenApp && this.chosenApp.path) {
  412.             this.dialogElement( "appPath" ).value = this.getPath(this.chosenApp);
  413.         }
  414.  
  415.         var useDefault = this.dialogElement( "useSystemDefault" );;
  416.         if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
  417.             // Open (using system default).
  418.             useDefault.radioGroup.selectedItem = useDefault;
  419.         } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
  420.             // Open with given helper app.
  421.             var openUsing = this.dialogElement( "openUsing" );
  422.             openUsing.radioGroup.selectedItem = openUsing;
  423.         } else {
  424.             // Save to disk.
  425.             var saveToDisk = this.dialogElement( "saveToDisk" );
  426.             saveToDisk.radioGroup.selectedItem = saveToDisk;
  427.         }
  428.         // If we don't have a "default app" then disable that choice.
  429.         if ( !this.openWithDefaultOK() ) {
  430.             // Disable that choice.
  431.             useDefault.hidden = true;
  432.             // If that's the default, then switch to "save to disk."
  433.             if ( useDefault.selected ) {
  434.                 useDefault.radioGroup.selectedItem = this.dialogElement( "saveToDisk" );
  435.             }
  436.         }
  437.         
  438.         // Enable/Disable choose application button and textfield
  439.         this.toggleChoice();
  440.     },
  441.  
  442.     // Enable pick app button if the user chooses that option.
  443.     toggleChoice : function () {
  444.         // See what option is selected.
  445.         var openUsing = this.dialogElement( "openUsing" ).selected;
  446.         this.dialogElement( "chooseApp" ).disabled = !openUsing;
  447.         // appPath is always disabled on Mac
  448.         this.dialogElement( "appPath" ).disabled = !openUsing || this.mIsMac;
  449.         this.updateOKButton();
  450.     },
  451.  
  452.     // Returns the user-selected application
  453.     helperAppChoice: function() {
  454.         var result = this.chosenApp;
  455.         if (!this.mIsMac) {
  456.             var typed  = this.dialogElement( "appPath" ).value;
  457.             // First, see if one was chosen via the Choose... button.
  458.             if ( result ) {
  459.                 // Verify that the user didn't type in something different later.
  460.                 if ( typed != result.path ) {
  461.                     // Use what was typed in.
  462.                     try {
  463.                         result.QueryInterface( Components.interfaces.nsILocalFile ).initWithPath( typed );
  464.                     } catch( e ) {
  465.                         // Invalid path was typed.
  466.                         result = null;
  467.                     }
  468.                 }
  469.             } else {
  470.                 // The user didn't use the Choose... button, try using what they typed in.
  471.                 result = Components.classes[ "@mozilla.org/file/local;1" ]
  472.                     .createInstance( Components.interfaces.nsILocalFile );
  473.                 try {
  474.                     result.initWithPath( typed );
  475.                 } catch( e ) {
  476.                     result = null;
  477.                 }
  478.             }
  479.             // Remember what was chosen.
  480.             this.chosenApp = result;
  481.         }
  482.         return result;
  483.     },
  484.  
  485.     updateOKButton: function() {
  486.         var ok = false;
  487.         if ( this.dialogElement( "saveToDisk" ).selected ) 
  488.         {
  489.             // This is always OK.
  490.             ok = true;
  491.         } 
  492.         else if ( this.dialogElement( "useSystemDefault" ).selected )
  493.         {
  494.             // No app need be specified in this case.
  495.             ok = true;
  496.         }
  497.         else 
  498.         {
  499.             // only enable the OK button if we have a default app to use or if 
  500.             // the user chose an app....
  501.             ok = this.chosenApp || /\S/.test( this.dialogElement( "appPath" ).value );
  502.         }
  503.         
  504.         // Enable Ok button if ok to press.
  505.         this.mDialog.document.documentElement.getButton( "accept" ).disabled = !ok;
  506.     },
  507.     
  508.     // Returns true iff the user-specified helper app has been modified.
  509.     appChanged: function() {
  510.         return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
  511.     },
  512.  
  513.     updateMIMEInfo: function() {
  514.         var needUpdate = false;
  515.         // If current selection differs from what's in the mime info object,
  516.         // then we need to update.
  517.         if ( this.dialogElement( "saveToDisk" ).selected ) {
  518.             needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
  519.             if ( needUpdate )
  520.                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
  521.         } else if ( this.dialogElement( "useSystemDefault" ).selected ) {
  522.             needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
  523.             if ( needUpdate )
  524.                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
  525.         } else {
  526.             // For "open with", we need to check both preferred action and whether the user chose
  527.             // a new app.
  528.             needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
  529.             if ( needUpdate ) {
  530.                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
  531.                 // App may have changed - Update application and description
  532.                 var app = this.helperAppChoice();
  533.                 this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
  534.                 this.mLauncher.MIMEInfo.applicationDescription = "";
  535.             }
  536.         }
  537.         // We will also need to update if the "always ask" flag has changed.
  538.         needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != this.dialogElement( "alwaysAskMe" ).checked;
  539.         
  540.         // One last special case: If the input "always ask" flag was false, then we always
  541.         // update.  In that case we are displaying the helper app dialog for the first
  542.         // time for this mime type and we need to store the user's action in the mimeTypes.rdf
  543.         // data source (whether that action has changed or not; if it didn't change, then we need
  544.         // to store the "always ask" flag so the helper app dialog will or won't display
  545.         // next time, per the user's selection).
  546.         needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  547.         
  548.         // Make sure mime info has updated setting for the "always ask" flag.
  549.         this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = this.dialogElement( "alwaysAskMe" ).checked;
  550.  
  551.         return needUpdate;        
  552.     },
  553.     
  554.     // See if the user changed things, and if so, update the
  555.     // mimeTypes.rdf entry for this mime type.
  556.     updateHelperAppPref: function() {
  557.         // We update by passing this mime info into the "Edit Type" helper app
  558.         // pref dialog.  It will update the data source and close the dialog
  559.         // automatically.
  560.         this.mDialog.openDialog( "chrome://communicator/content/pref/pref-applications-edit.xul",
  561.                                  "_blank",
  562.                                  "chrome,modal=yes,resizable=no",
  563.                                  this );
  564.     },
  565.     
  566.     // onOK:
  567.     onOK: function() {
  568.         // Verify typed app path, if necessary.
  569.         if ( this.dialogElement( "openUsing" ).selected ) {
  570.             var helperApp = this.helperAppChoice();
  571.             if ( !helperApp || !helperApp.exists() ) {
  572.                 // Show alert and try again.                            
  573.                 var msg = this.replaceInsert( this.getString( "badApp" ), 1, this.dialogElement( "appPath" ).value );
  574.                 var svc = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
  575.                             .getService( Components.interfaces.nsIPromptService );
  576.                 svc.alert( this.mDialog, this.getString( "badApp.title" ), msg );
  577.                 // Disable the OK button.
  578.                 this.mDialog.document.documentElement.getButton( "accept" ).disabled = true;
  579.                 // Select and focus the input field if input field is not disabled
  580.                 var path = this.dialogElement( "appPath" );
  581.                 if ( !path.disabled ) {
  582.                     path.select();
  583.                     path.focus();
  584.                 }
  585.                 // Clear chosen application.
  586.                 this.chosenApp = null;
  587.                 // Leave dialog up.
  588.                 return false;
  589.             }
  590.         }
  591.         
  592.         // Remove our web progress listener (a progress dialog will be
  593.         // taking over).
  594.         this.mLauncher.setWebProgressListener( null );
  595.         
  596.         // saveToDisk and launchWithApplication can return errors in 
  597.         // certain circumstances (e.g. The user clicks cancel in the
  598.         // "Save to Disk" dialog. In those cases, we don't want to
  599.         // update the helper application preferences in the RDF file.
  600.         try {
  601.             var needUpdate = this.updateMIMEInfo();
  602.             if ( this.dialogElement( "saveToDisk" ).selected )
  603.                 this.mLauncher.saveToDisk( null, false );
  604.             else
  605.                 this.mLauncher.launchWithApplication( null, false );
  606.         
  607.             // Update user pref for this mime type (if necessary). We do not
  608.             // store anything in the mime type preferences for the ambiguous
  609.             // type application/octet-stream.
  610.             if ( needUpdate &&
  611.                  this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream" ) {
  612.                 this.updateHelperAppPref();
  613.             }
  614.  
  615.         } catch(e) { }
  616.             
  617.         // Unhook dialog from this object.
  618.         this.mDialog.dialog = null;
  619.         
  620.         // Close up dialog by returning true.
  621.         return true;
  622.     },
  623.  
  624.     // onCancel:
  625.     onCancel: function() {
  626.         // Remove our web progress listener.
  627.         this.mLauncher.setWebProgressListener( null );
  628.  
  629.         // Cancel app launcher.
  630.         try {
  631.             this.mLauncher.Cancel();
  632.         } catch( exception ) {
  633.         }
  634.         
  635.         // Unhook dialog from this object.
  636.         this.mDialog.dialog = null;
  637.  
  638.         // Close up dialog by returning true.
  639.         return true;
  640.     },
  641.  
  642.     // dialogElement:  Try cache; obtain from document if not there.
  643.     dialogElement: function( id ) {
  644.          // Check if we've already fetched it.
  645.          if ( !( id in this.elements ) ) {
  646.              // No, then get it from dialog.
  647.              this.elements[ id ] = this.mDialog.document.getElementById( id );
  648.          }
  649.          return this.elements[ id ];
  650.     },
  651.  
  652.     // chooseApp:  Open file picker and prompt user for application.
  653.     chooseApp: function() {
  654.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  655.         var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance( nsIFilePicker );
  656.         fp.init( this.mDialog,
  657.                  this.getString( "chooseAppFilePickerTitle" ),
  658.                  nsIFilePicker.modeOpen );
  659.  
  660.         // XXX - We want to say nsIFilePicker.filterExecutable or something
  661.         fp.appendFilters( nsIFilePicker.filterAll );
  662.         
  663.         if ( fp.show() == nsIFilePicker.returnOK && fp.file ) {
  664.             // Remember the file they chose to run.
  665.             this.chosenApp = fp.file;
  666.             // Update dialog.
  667.             this.dialogElement( "appPath" ).value = this.getPath(this.chosenApp);
  668.         }
  669.     },
  670.  
  671.     // dumpInfo:
  672.     doDebug: function() {
  673.         const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
  674.         // Open new progress dialog.
  675.         var progress = Components.classes[ "@mozilla.org/progressdialog;1" ]
  676.                          .createInstance( nsIProgressDialog );
  677.         // Show it.
  678.         progress.open( this.mDialog );
  679.     },
  680.  
  681.     // dumpObj:
  682.     dumpObj: function( spec ) {
  683.          var val = "<undefined>";
  684.          try {
  685.              val = eval( "this."+spec ).toString();
  686.          } catch( exception ) {
  687.          }
  688.          this.dump( spec + "=" + val + "\n" );
  689.     },
  690.  
  691.     // dumpObjectProperties
  692.     dumpObjectProperties: function( desc, obj ) {
  693.          for( prop in obj ) {
  694.              this.dump( desc + "." + prop + "=" );
  695.              var val = "<undefined>";
  696.              try {
  697.                  val = obj[ prop ];
  698.              } catch ( exception ) {
  699.              }
  700.              this.dump( val + "\n" );
  701.          }
  702.     },
  703.  
  704.     // getString: Fetch data string from dialog content (and cache it).
  705.     getString: function( id ) {
  706.         // Check if we've fetched this string already.
  707.         if ( !( id in this.strings ) ) {
  708.             // Try to get it.
  709.             var elem = this.mDialog.document.getElementById( id );
  710.             if ( elem
  711.                  &&
  712.                  elem.firstChild
  713.                  &&
  714.                  elem.firstChild.nodeValue ) {
  715.                 this.strings[ id ] = elem.firstChild.nodeValue;
  716.             } else {
  717.                 // If unable to fetch string, use an empty string.
  718.                 this.strings[ id ] = "";
  719.             }
  720.         }
  721.         return this.strings[ id ];
  722.     },
  723.  
  724.     // replaceInsert: Replace given insert with replacement text and return the result.
  725.     replaceInsert: function( text, insertNo, replacementText ) {
  726.         var result = text;
  727.         var regExp = new RegExp("#"+insertNo);
  728.         result = result.replace( regExp, replacementText );
  729.         return result;
  730.     }
  731. }
  732.  
  733. // This Component's module implementation.  All the code below is used to get this
  734. // component registered and accessible via XPCOM.
  735. var module = {
  736.     firstTime: true,
  737.  
  738.     // registerSelf: Register this component.
  739.     registerSelf: function (compMgr, fileSpec, location, type) {
  740.         if (this.firstTime) {
  741.             this.firstTime = false;
  742.             throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  743.         }
  744.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  745.  
  746.         compMgr.registerFactoryLocation( this.cid,
  747.                                          "Mozilla Helper App Launcher Dialog",
  748.                                          this.contractId,
  749.                                          fileSpec,
  750.                                          location,
  751.                                          type );
  752.     },
  753.  
  754.     // getClassObject: Return this component's factory object.
  755.     getClassObject: function (compMgr, cid, iid) {
  756.         if (!cid.equals(this.cid)) {
  757.             throw Components.results.NS_ERROR_NO_INTERFACE;
  758.         }
  759.  
  760.         if (!iid.equals(Components.interfaces.nsIFactory)) {
  761.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  762.         }
  763.  
  764.         return this.factory;
  765.     },
  766.  
  767.     /* CID for this class */
  768.     cid: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
  769.  
  770.     /* Contract ID for this class */
  771.     contractId: "@mozilla.org/helperapplauncherdialog;1",
  772.  
  773.     /* factory object */
  774.     factory: {
  775.         // createInstance: Return a new nsProgressDialog object.
  776.         createInstance: function (outer, iid) {
  777.             if (outer != null)
  778.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  779.  
  780.             return (new nsHelperAppDialog()).QueryInterface(iid);
  781.         }
  782.     },
  783.  
  784.     // canUnload: n/a (returns true)
  785.     canUnload: function(compMgr) {
  786.         return true;
  787.     }
  788. };
  789.  
  790. // NSGetModule: Return the nsIModule object.
  791. function NSGetModule(compMgr, fileSpec) {
  792.     return module;
  793. }
  794.