home *** CD-ROM | disk | FTP | other *** search
/ Chip 2003 September / Chip_2003-09_cd1.bin / zkuste / macos / Files / wamcom.sit / wamcom-131-macos9-20030721 / Components / nsHelperAppDlg.js < prev    next >
Text File  |  2003-06-30  |  32KB  |  801 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 provided.
  388.         var desc = this.mLauncher.MIMEInfo.defaultDescription;
  389.         if ( !desc ) {
  390.             // Otherwise, use helper application file name
  391.             var app = this.mLauncher.MIMEInfo.defaultApplicationHandler;
  392.             if ( app ) {
  393.                 desc = app.leafName;
  394.             }
  395.         }
  396.         if ( desc ) {
  397.             this.dialogElement( "useSystemDefault" ).label = this.replaceInsert( this.getString( "defaultApp" ), 1, desc );
  398.         }
  399.     },
  400.  
  401.     // getPath:
  402.     getPath: function(file) {
  403.         if (this.mIsMac) {
  404.             return file.leafName || file.path;
  405.         }
  406.  
  407.         return file.path;
  408.     },
  409.     
  410.     // initAppAndSaveToDiskValues:
  411.     initAppAndSaveToDiskValues: function() {
  412.         // Fill in helper app info, if there is any.
  413.         this.chosenApp = this.mLauncher.MIMEInfo.preferredApplicationHandler;
  414.         // Initialize "default application" field.
  415.         this.initDefaultApp();
  416.         
  417.         // Fill application name textbox.
  418.         if (this.chosenApp && this.chosenApp.path) {
  419.             this.dialogElement( "appPath" ).value = this.getPath(this.chosenApp);
  420.         }
  421.  
  422.         var useDefault = this.dialogElement( "useSystemDefault" );;
  423.         if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
  424.             // Open (using system default).
  425.             useDefault.radioGroup.selectedItem = useDefault;
  426.         } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
  427.             // Open with given helper app.
  428.             var openUsing = this.dialogElement( "openUsing" );
  429.             openUsing.radioGroup.selectedItem = openUsing;
  430.         } else {
  431.             // Save to disk.
  432.             var saveToDisk = this.dialogElement( "saveToDisk" );
  433.             saveToDisk.radioGroup.selectedItem = saveToDisk;
  434.         }
  435.         // If we don't have a "default app" then disable that choice.
  436.         if ( !this.openWithDefaultOK() ) {
  437.             // Disable that choice.
  438.             useDefault.hidden = true;
  439.             // If that's the default, then switch to "save to disk."
  440.             if ( useDefault.selected ) {
  441.                 useDefault.radioGroup.selectedItem = this.dialogElement( "saveToDisk" );
  442.             }
  443.         }
  444.         
  445.         // Enable/Disable choose application button and textfield
  446.         this.toggleChoice();
  447.     },
  448.  
  449.     // Enable pick app button if the user chooses that option.
  450.     toggleChoice : function () {
  451.         // See what option is selected.
  452.         var openUsing = this.dialogElement( "openUsing" ).selected;
  453.         this.dialogElement( "chooseApp" ).disabled = !openUsing;
  454.         // appPath is always disabled on Mac
  455.         this.dialogElement( "appPath" ).disabled = !openUsing || this.mIsMac;
  456.         this.updateOKButton();
  457.     },
  458.  
  459.     // Returns the user-selected application
  460.     helperAppChoice: function() {
  461.         var result = this.chosenApp;
  462.         if (!this.mIsMac) {
  463.             var typed  = this.dialogElement( "appPath" ).value;
  464.             // First, see if one was chosen via the Choose... button.
  465.             if ( result ) {
  466.                 // Verify that the user didn't type in something different later.
  467.                 if ( typed != result.path ) {
  468.                     // Use what was typed in.
  469.                     try {
  470.                         result.QueryInterface( Components.interfaces.nsILocalFile ).initWithPath( typed );
  471.                     } catch( e ) {
  472.                         // Invalid path was typed.
  473.                         result = null;
  474.                     }
  475.                 }
  476.             } else {
  477.                 // The user didn't use the Choose... button, try using what they typed in.
  478.                 result = Components.classes[ "@mozilla.org/file/local;1" ]
  479.                     .createInstance( Components.interfaces.nsILocalFile );
  480.                 try {
  481.                     result.initWithPath( typed );
  482.                 } catch( e ) {
  483.                     result = null;
  484.                 }
  485.             }
  486.             // Remember what was chosen.
  487.             this.chosenApp = result;
  488.         }
  489.         return result;
  490.     },
  491.  
  492.     updateOKButton: function() {
  493.         var ok = false;
  494.         if ( this.dialogElement( "saveToDisk" ).selected ) 
  495.         {
  496.             // This is always OK.
  497.             ok = true;
  498.         } 
  499.         else if ( this.dialogElement( "useSystemDefault" ).selected )
  500.         {
  501.             // No app need be specified in this case.
  502.             ok = true;
  503.         }
  504.         else 
  505.         {
  506.             // only enable the OK button if we have a default app to use or if 
  507.             // the user chose an app....
  508.             ok = this.chosenApp || /\S/.test( this.dialogElement( "appPath" ).value );
  509.         }
  510.         
  511.         // Enable Ok button if ok to press.
  512.         this.mDialog.document.documentElement.getButton( "accept" ).disabled = !ok;
  513.     },
  514.     
  515.     // Returns true iff the user-specified helper app has been modified.
  516.     appChanged: function() {
  517.         return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
  518.     },
  519.  
  520.     updateMIMEInfo: function() {
  521.         var needUpdate = false;
  522.         // If current selection differs from what's in the mime info object,
  523.         // then we need to update.
  524.         if ( this.dialogElement( "saveToDisk" ).selected ) {
  525.             needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
  526.             if ( needUpdate )
  527.                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
  528.         } else if ( this.dialogElement( "useSystemDefault" ).selected ) {
  529.             needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
  530.             if ( needUpdate )
  531.                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
  532.         } else {
  533.             // For "open with", we need to check both preferred action and whether the user chose
  534.             // a new app.
  535.             needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
  536.             if ( needUpdate ) {
  537.                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
  538.                 // App may have changed - Update application and description
  539.                 var app = this.helperAppChoice();
  540.                 this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
  541.                 this.mLauncher.MIMEInfo.applicationDescription = app.leafName;
  542.             }
  543.         }
  544.         // We will also need to update if the "always ask" flag has changed.
  545.         needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != this.dialogElement( "alwaysAskMe" ).checked;
  546.         
  547.         // One last special case: If the input "always ask" flag was false, then we always
  548.         // update.  In that case we are displaying the helper app dialog for the first
  549.         // time for this mime type and we need to store the user's action in the mimeTypes.rdf
  550.         // data source (whether that action has changed or not; if it didn't change, then we need
  551.         // to store the "always ask" flag so the helper app dialog will or won't display
  552.         // next time, per the user's selection).
  553.         needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  554.         
  555.         // Make sure mime info has updated setting for the "always ask" flag.
  556.         this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = this.dialogElement( "alwaysAskMe" ).checked;
  557.  
  558.         return needUpdate;        
  559.     },
  560.     
  561.     // See if the user changed things, and if so, update the
  562.     // mimeTypes.rdf entry for this mime type.
  563.     updateHelperAppPref: function() {
  564.         // We update by passing this mime info into the "Edit Type" helper app
  565.         // pref dialog.  It will update the data source and close the dialog
  566.         // automatically.
  567.         this.mDialog.openDialog( "chrome://communicator/content/pref/pref-applications-edit.xul",
  568.                                  "_blank",
  569.                                  "chrome,modal=yes,resizable=no",
  570.                                  this );
  571.     },
  572.     
  573.     // onOK:
  574.     onOK: function() {
  575.         // Verify typed app path, if necessary.
  576.         if ( this.dialogElement( "openUsing" ).selected ) {
  577.             var helperApp = this.helperAppChoice();
  578.             if ( !helperApp || !helperApp.exists() ) {
  579.                 // Show alert and try again.                            
  580.                 var msg = this.replaceInsert( this.getString( "badApp" ), 1, this.dialogElement( "appPath" ).value );
  581.                 var svc = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
  582.                             .getService( Components.interfaces.nsIPromptService );
  583.                 svc.alert( this.mDialog, this.getString( "badApp.title" ), msg );
  584.                 // Disable the OK button.
  585.                 this.mDialog.document.documentElement.getButton( "accept" ).disabled = true;
  586.                 // Select and focus the input field if input field is not disabled
  587.                 var path = this.dialogElement( "appPath" );
  588.                 if ( !path.disabled ) {
  589.                     path.select();
  590.                     path.focus();
  591.                 }
  592.                 // Clear chosen application.
  593.                 this.chosenApp = null;
  594.                 // Leave dialog up.
  595.                 return false;
  596.             }
  597.         }
  598.         
  599.         // Remove our web progress listener (a progress dialog will be
  600.         // taking over).
  601.         this.mLauncher.setWebProgressListener( null );
  602.         
  603.         // saveToDisk and launchWithApplication can return errors in 
  604.         // certain circumstances (e.g. The user clicks cancel in the
  605.         // "Save to Disk" dialog. In those cases, we don't want to
  606.         // update the helper application preferences in the RDF file.
  607.         try {
  608.             var needUpdate = this.updateMIMEInfo();
  609.             if ( this.dialogElement( "saveToDisk" ).selected )
  610.                 this.mLauncher.saveToDisk( null, false );
  611.             else
  612.                 this.mLauncher.launchWithApplication( null, false );
  613.         
  614.             // Update user pref for this mime type (if necessary). We do not
  615.             // store anything in the mime type preferences for the ambiguous
  616.             // type application/octet-stream.
  617.             if ( needUpdate &&
  618.                  this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream" ) {
  619.                 this.updateHelperAppPref();
  620.             }
  621.  
  622.         } catch(e) { }
  623.             
  624.         // Unhook dialog from this object.
  625.         this.mDialog.dialog = null;
  626.         
  627.         // Close up dialog by returning true.
  628.         return true;
  629.     },
  630.  
  631.     // onCancel:
  632.     onCancel: function() {
  633.         // Remove our web progress listener.
  634.         this.mLauncher.setWebProgressListener( null );
  635.  
  636.         // Cancel app launcher.
  637.         try {
  638.             this.mLauncher.Cancel();
  639.         } catch( exception ) {
  640.         }
  641.         
  642.         // Unhook dialog from this object.
  643.         this.mDialog.dialog = null;
  644.  
  645.         // Close up dialog by returning true.
  646.         return true;
  647.     },
  648.  
  649.     // dialogElement:  Try cache; obtain from document if not there.
  650.     dialogElement: function( id ) {
  651.          // Check if we've already fetched it.
  652.          if ( !( id in this.elements ) ) {
  653.              // No, then get it from dialog.
  654.              this.elements[ id ] = this.mDialog.document.getElementById( id );
  655.          }
  656.          return this.elements[ id ];
  657.     },
  658.  
  659.     // chooseApp:  Open file picker and prompt user for application.
  660.     chooseApp: function() {
  661.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  662.         var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance( nsIFilePicker );
  663.         fp.init( this.mDialog,
  664.                  this.getString( "chooseAppFilePickerTitle" ),
  665.                  nsIFilePicker.modeOpen );
  666.  
  667.         // XXX - We want to say nsIFilePicker.filterExecutable or something
  668.         fp.appendFilters( nsIFilePicker.filterAll );
  669.         
  670.         if ( fp.show() == nsIFilePicker.returnOK && fp.file ) {
  671.             // Remember the file they chose to run.
  672.             this.chosenApp = fp.file;
  673.             // Update dialog.
  674.             this.dialogElement( "appPath" ).value = this.getPath(this.chosenApp);
  675.         }
  676.     },
  677.  
  678.     // dumpInfo:
  679.     doDebug: function() {
  680.         const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
  681.         // Open new progress dialog.
  682.         var progress = Components.classes[ "@mozilla.org/progressdialog;1" ]
  683.                          .createInstance( nsIProgressDialog );
  684.         // Show it.
  685.         progress.open( this.mDialog );
  686.     },
  687.  
  688.     // dumpObj:
  689.     dumpObj: function( spec ) {
  690.          var val = "<undefined>";
  691.          try {
  692.              val = eval( "this."+spec ).toString();
  693.          } catch( exception ) {
  694.          }
  695.          this.dump( spec + "=" + val + "\n" );
  696.     },
  697.  
  698.     // dumpObjectProperties
  699.     dumpObjectProperties: function( desc, obj ) {
  700.          for( prop in obj ) {
  701.              this.dump( desc + "." + prop + "=" );
  702.              var val = "<undefined>";
  703.              try {
  704.                  val = obj[ prop ];
  705.              } catch ( exception ) {
  706.              }
  707.              this.dump( val + "\n" );
  708.          }
  709.     },
  710.  
  711.     // getString: Fetch data string from dialog content (and cache it).
  712.     getString: function( id ) {
  713.         // Check if we've fetched this string already.
  714.         if ( !( id in this.strings ) ) {
  715.             // Try to get it.
  716.             var elem = this.mDialog.document.getElementById( id );
  717.             if ( elem
  718.                  &&
  719.                  elem.firstChild
  720.                  &&
  721.                  elem.firstChild.nodeValue ) {
  722.                 this.strings[ id ] = elem.firstChild.nodeValue;
  723.             } else {
  724.                 // If unable to fetch string, use an empty string.
  725.                 this.strings[ id ] = "";
  726.             }
  727.         }
  728.         return this.strings[ id ];
  729.     },
  730.  
  731.     // replaceInsert: Replace given insert with replacement text and return the result.
  732.     replaceInsert: function( text, insertNo, replacementText ) {
  733.         var result = text;
  734.         var regExp = new RegExp("#"+insertNo);
  735.         result = result.replace( regExp, replacementText );
  736.         return result;
  737.     }
  738. }
  739.  
  740. // This Component's module implementation.  All the code below is used to get this
  741. // component registered and accessible via XPCOM.
  742. var module = {
  743.     firstTime: true,
  744.  
  745.     // registerSelf: Register this component.
  746.     registerSelf: function (compMgr, fileSpec, location, type) {
  747.         if (this.firstTime) {
  748.             this.firstTime = false;
  749.             throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  750.         }
  751.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  752.  
  753.         compMgr.registerFactoryLocation( this.cid,
  754.                                          "Mozilla Helper App Launcher Dialog",
  755.                                          this.contractId,
  756.                                          fileSpec,
  757.                                          location,
  758.                                          type );
  759.     },
  760.  
  761.     // getClassObject: Return this component's factory object.
  762.     getClassObject: function (compMgr, cid, iid) {
  763.         if (!cid.equals(this.cid)) {
  764.             throw Components.results.NS_ERROR_NO_INTERFACE;
  765.         }
  766.  
  767.         if (!iid.equals(Components.interfaces.nsIFactory)) {
  768.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  769.         }
  770.  
  771.         return this.factory;
  772.     },
  773.  
  774.     /* CID for this class */
  775.     cid: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
  776.  
  777.     /* Contract ID for this class */
  778.     contractId: "@mozilla.org/helperapplauncherdialog;1",
  779.  
  780.     /* factory object */
  781.     factory: {
  782.         // createInstance: Return a new nsProgressDialog object.
  783.         createInstance: function (outer, iid) {
  784.             if (outer != null)
  785.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  786.  
  787.             return (new nsHelperAppDialog()).QueryInterface(iid);
  788.         }
  789.     },
  790.  
  791.     // canUnload: n/a (returns true)
  792.     canUnload: function(compMgr) {
  793.         return true;
  794.     }
  795. };
  796.  
  797. // NSGetModule: Return the nsIModule object.
  798. function NSGetModule(compMgr, fileSpec) {
  799.     return module;
  800. }
  801.