home *** CD-ROM | disk | FTP | other *** search
/ Chip 2004 April / CMCD0404.ISO / Software / Complet / thunderbird / components / nsHelperAppDlg.js < prev    next >
Encoding:
JavaScript  |  2004-02-07  |  31.4 KB  |  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, aForced)  {
  97.          this.mLauncher = aLauncher;
  98.          this.mContext  = aContext;
  99.          this.mForced   = aForced;
  100.          // Display the dialog using the Window Watcher interface.
  101.          var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  102.                     .getService( Components.interfaces.nsIWindowWatcher );
  103.          this.mDialog = ww.openWindow( null, // no parent
  104.                                        "chrome://global/content/nsHelperAppDlg.xul",
  105.                                        null,
  106.                                        "chrome,titlebar,dialog=yes",
  107.                                        null );
  108.          // Hook this object to the dialog.
  109.          this.mDialog.dialog = this;
  110.          // Watch for error notifications.
  111.          this.mIsMac = (this.mDialog.navigator.platform.indexOf( "Mac" ) != -1);
  112.          this.progressListener.helperAppDlg = this;
  113.          this.mLauncher.setWebProgressListener( this.progressListener );
  114.     },
  115.  
  116.     // promptForSaveToFile:  Display file picker dialog and return selected file.
  117.     promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension) {
  118.         var result = "";
  119.  
  120.         // Use file picker to show dialog.
  121.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  122.         var picker = Components.classes[ "@mozilla.org/filepicker;1" ]
  123.                        .createInstance( nsIFilePicker );
  124.         var bundle = Components.classes[ "@mozilla.org/intl/stringbundle;1" ]
  125.                        .getService( Components.interfaces.nsIStringBundleService )
  126.                            .createBundle( "chrome://global/locale/nsHelperAppDlg.properties");
  127.  
  128.         var windowTitle = bundle.GetStringFromName( "saveDialogTitle" );
  129.         
  130.         var parent = aContext
  131.                         .QueryInterface( Components.interfaces.nsIInterfaceRequestor )
  132.                         .getInterface( Components.interfaces.nsIDOMWindowInternal );
  133.         picker.init( parent, windowTitle, nsIFilePicker.modeSave );
  134.         picker.defaultString = aDefaultFile;
  135.         if (aSuggestedFileExtension) {
  136.             // aSuggestedFileExtension includes the period, so strip it
  137.             picker.defaultExtension = aSuggestedFileExtension.substring(1);
  138.         } else {
  139.             try {
  140.                 picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
  141.             } catch (ex) {
  142.             }
  143.         }
  144.  
  145.         var wildCardExtension = "*";
  146.         if ( aSuggestedFileExtension ) {
  147.             wildCardExtension += aSuggestedFileExtension;
  148.             picker.appendFilter( wildCardExtension, wildCardExtension );
  149.         }
  150.  
  151.         picker.appendFilters( nsIFilePicker.filterAll );
  152.  
  153.         // Pull in the user's preferences and get the default download directory.
  154.         var prefs = Components.classes[ "@mozilla.org/preferences-service;1" ]
  155.                               .getService( Components.interfaces.nsIPrefBranch );
  156.         try {
  157.             var startDir = prefs.getComplexValue("browser.download.dir",
  158.                                                  Components.interfaces.nsILocalFile);
  159.             if ( startDir.exists() ) {
  160.                 picker.displayDirectory = startDir;
  161.             }
  162.         } catch( exception ) {
  163.         }
  164.  
  165.         var dlgResult = picker.show();
  166.  
  167.         if ( dlgResult == nsIFilePicker.returnCancel ) {
  168.             // Null result means user cancelled.
  169.             return null;
  170.         }
  171.  
  172.  
  173.         // be sure to save the directory the user chose as the new browser.download.dir
  174.         result = picker.file;
  175.  
  176.         if ( result ) {
  177.             var newDir = result.parent;
  178.             prefs.setComplexValue("browser.download.dir",
  179.                                   Components.interfaces.nsILocalFile, newDir);
  180.         }
  181.         return result;
  182.     },
  183.     
  184.     // ---------- implementation methods ----------
  185.  
  186.     // Web progress listener so we can detect errors while mLauncher is
  187.     // streaming the data to a temporary file.
  188.     progressListener: {
  189.         // Implementation properties.
  190.         helperAppDlg: null,
  191.  
  192.         // nsIWebProgressListener methods.
  193.         // Look for error notifications and display alert to user.
  194.         onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
  195.             if ( aStatus != Components.results.NS_OK ) {
  196.                 // Get prompt service.
  197.                 var prompter = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
  198.                                    .getService( Components.interfaces.nsIPromptService );
  199.                 // Display error alert (using text supplied by back-end).
  200.                 prompter.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
  201.  
  202.                 // Close the dialog.
  203.                 this.helperAppDlg.onCancel();
  204.                 if ( this.helperAppDlg.mDialog ) {
  205.                     this.helperAppDlg.mDialog.close();
  206.                 }
  207.             }
  208.         },
  209.  
  210.         // Ignore onProgressChange, onStateChange, onLocationChange, and onSecurityChange notifications.
  211.         onProgressChange: function( aWebProgress,
  212.                                     aRequest,
  213.                                     aCurSelfProgress,
  214.                                     aMaxSelfProgress,
  215.                                     aCurTotalProgress,
  216.                                     aMaxTotalProgress ) {
  217.         },
  218.  
  219.         onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
  220.         },
  221.  
  222.         onLocationChange: function( aWebProgress, aRequest, aLocation ) {
  223.         },
  224.  
  225.         onSecurityChange: function( aWebProgress, aRequest, state ) {
  226.         }
  227.     },
  228.  
  229.     // initDialog:  Fill various dialog fields with initial content.
  230.     initDialog : function() {
  231.          // Put product brand short name in prompt.
  232.          var prompt = this.dialogElement( "prompt" );
  233.          var modified = this.replaceInsert( prompt.firstChild.nodeValue, 1, this.getString( "brandShortName" ) );
  234.          prompt.firstChild.nodeValue = modified;
  235.  
  236.          // Put file name in window title.
  237.          var win   = this.dialogElement( "nsHelperAppDlg" );
  238.          var suggestedFileName = this.mLauncher.suggestedFileName;
  239.  
  240.          // Some URIs do not implement nsIURL, so we can't just QI.
  241.          var url   = this.mLauncher.source;
  242.          var fname = "";
  243.          this.mSourcePath = url.prePath;
  244.          try {
  245.              url = url.QueryInterface( Components.interfaces.nsIURL );
  246.              // A url, use file name from it.
  247.              fname = url.fileName;
  248.              this.mSourcePath += url.directory;
  249.          } catch (ex) {
  250.              // A generic uri, use path.
  251.              fname = url.path;
  252.              this.mSourcePath += url.path;
  253.          }
  254.  
  255.          if (suggestedFileName)
  256.            fname = suggestedFileName;
  257.            
  258.  
  259.          this.mTitle = this.replaceInsert( win.getAttribute( "title" ), 1, fname);
  260.          win.setAttribute( "title", this.mTitle );
  261.  
  262.          // Put content type, filename and location into intro.
  263.          this.initIntro(url, fname);
  264.  
  265.          var iconString = "moz-icon://" + fname + "?size=32&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
  266.  
  267.          this.dialogElement("contentTypeImage").setAttribute("src", iconString);
  268.  
  269.          this.initAppAndSaveToDiskValues();
  270.  
  271.          // Initialize "always ask me" box. This should always be disabled
  272.          // and set to true for the ambiguous type application/octet-stream.
  273.          // Same if this dialog was forced
  274.          var alwaysHandleCheckbox = this.dialogElement( "alwaysHandle" );
  275.          if (this.mForced ||
  276.              this.mLauncher.MIMEInfo.MIMEType == "application/octet-stream"){
  277.             alwaysHandleCheckbox.checked = false;
  278.             alwaysHandleCheckbox.disabled = true;
  279.          }
  280.          else {
  281.             alwaysHandleCheckbox.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  282.          }
  283.  
  284.          // Position it.
  285.          if ( this.mDialog.opener ) {
  286.              this.mDialog.moveToAlertPosition();
  287.          } else {
  288.              this.mDialog.sizeToContent();
  289.              this.mDialog.centerWindowOnScreen();
  290.          }
  291.  
  292.          // Set initial focus
  293.          this.dialogElement( "mode" ).focus();
  294.     },
  295.  
  296.     // initIntro:
  297.     initIntro: function(url, filename) {
  298.         var intro = this.dialogElement( "intro" );
  299.         var desc = this.mLauncher.MIMEInfo.Description;
  300.         var modified;
  301.         if ( this.mForced && desc )
  302.         {
  303.           modified = this.replaceInsert( this.getString( "intro.attachment.label" ), 1, desc );
  304.         }
  305.         else if ( this.mForced && !desc )
  306.         {
  307.           modified = this.getString( "intro.attachment.noDesc.label" );
  308.         }
  309.         else if ( desc )
  310.         {
  311.           // Use intro with descriptive text.
  312.           modified = this.replaceInsert( this.getString( "intro.withDesc" ), 1, desc );
  313.         } 
  314.         else 
  315.         {
  316.           // Use intro without descriptive text.
  317.           modified = this.getString( "intro.noDesc" );
  318.         }
  319.  
  320.         modified = this.replaceInsert( modified, 2, this.mLauncher.MIMEInfo.MIMEType );
  321.         modified = this.replaceInsert( modified, 3, filename);
  322.         modified = this.replaceInsert( modified, 4, this.getString( "brandShortName" ));
  323.  
  324.         // if mSourcePath is a local file, then let's use the pretty path name instead of an ugly
  325.         // url...
  326.         var pathString = this.mSourcePath;
  327.         try 
  328.         {
  329.           var fileURL = url.QueryInterface(Components.interfaces.nsIFileURL);
  330.           if (fileURL)
  331.           {
  332.             var fileObject = fileURL.file;
  333.             if (fileObject)
  334.             {
  335.               var parentObject = fileObject.parent;
  336.               if (parentObject)
  337.               {
  338.                 pathString = parentObject.path;
  339.               }
  340.             }
  341.           }
  342.         } catch(ex) {}
  343.  
  344.  
  345.         intro.firstChild.nodeValue = "";
  346.         intro.firstChild.nodeValue = modified;
  347.  
  348.         // Set the location text, which is separate from the intro text so it can be cropped
  349.         var location = this.dialogElement( "location" );
  350.         location.value = pathString;
  351.     },
  352.  
  353.     // Returns true iff opening the default application makes sense.
  354.     openWithDefaultOK: function() {
  355.         var result;
  356.  
  357.         // The checking is different on Windows...
  358.         if ( this.mDialog.navigator.platform.indexOf( "Win" ) != -1 ) {
  359.             // Windows presents some special cases.
  360.             // We need to prevent use of "system default" when the file is
  361.             // executable (so the user doesn't launch nasty programs downloaded
  362.             // from the web), and, enable use of "system default" if it isn't
  363.             // executable (because we will prompt the user for the default app
  364.             // in that case).
  365.             
  366.             // Need to get temporary file and check for executable-ness.
  367.             var tmpFile = this.mLauncher.targetFile;
  368.             
  369.             //  Default is Ok if the file isn't executable (and vice-versa).
  370.             result = !tmpFile.isExecutable();
  371.         } else {
  372.             // On other platforms, default is Ok if there is a default app.
  373.             // Note that nsIMIMEInfo providers need to ensure that this holds true
  374.             // on each platform.
  375.             result = this.mLauncher.MIMEInfo.hasDefaultHandler;
  376.         }
  377.         return result;
  378.     },
  379.     
  380.     // Set "default" application description field.
  381.     initDefaultApp: function() {
  382.         // Use description, if we can get one.
  383.         var desc = this.mLauncher.MIMEInfo.defaultDescription;
  384.         if ( desc ) {
  385.             this.dialogElement( "useSystemDefault" ).label = this.replaceInsert( this.getString( "defaultApp" ), 1, desc );
  386.         }
  387.     },
  388.  
  389.     // getPath:
  390.     getPath: function(file) {
  391.         if (this.mIsMac) {
  392.             return file.leafName || file.path;
  393.         }
  394.  
  395.         return file.path;
  396.     },
  397.     
  398.     // initAppAndSaveToDiskValues:
  399.     initAppAndSaveToDiskValues: function() {
  400.         // Fill in helper app info, if there is any.
  401.         this.chosenApp = this.mLauncher.MIMEInfo.preferredApplicationHandler;
  402.         // Initialize "default application" field.
  403.         this.initDefaultApp();
  404.         
  405.         // Fill application name textbox.
  406.         if (this.chosenApp && this.chosenApp.path) {
  407.             this.dialogElement( "appPath" ).value = this.getPath(this.chosenApp);
  408.         }
  409.  
  410.         var useDefault = this.dialogElement( "useSystemDefault" );;
  411.         if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
  412.             // Open (using system default).
  413.             useDefault.radioGroup.selectedItem = useDefault;
  414.         } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
  415.             // Open with given helper app.
  416.             var openUsing = this.dialogElement( "openUsing" );
  417.             openUsing.radioGroup.selectedItem = openUsing;
  418.         } else {
  419.             // Save to disk.
  420.             var saveToDisk = this.dialogElement( "saveToDisk" );
  421.             saveToDisk.radioGroup.selectedItem = saveToDisk;
  422.         }
  423.         // If we don't have a "default app" then disable that choice.
  424.         if ( !this.openWithDefaultOK() ) {
  425.             // Disable that choice.
  426.             useDefault.hidden = true;
  427.             // If that's the default, then switch to "save to disk."
  428.             if ( useDefault.selected ) {
  429.                 useDefault.radioGroup.selectedItem = this.dialogElement( "saveToDisk" );
  430.             }
  431.         }
  432.         
  433.         // Enable/Disable choose application button and textfield
  434.         this.toggleChoice();
  435.     },
  436.  
  437.     // Enable pick app button if the user chooses that option.
  438.     toggleChoice : function () {
  439.         // See what option is selected.
  440.         var openUsing = this.dialogElement( "openUsing" ).selected;
  441.         this.dialogElement( "chooseApp" ).disabled = !openUsing;
  442.         // appPath is always disabled on Mac
  443.         this.dialogElement( "appPath" ).disabled = !openUsing || this.mIsMac;
  444.         this.updateOKButton();
  445.     },
  446.  
  447.     // Returns the user-selected application
  448.     helperAppChoice: function() {
  449.         var result = this.chosenApp;
  450.         if (!this.mIsMac) {
  451.             var typed  = this.dialogElement( "appPath" ).value;
  452.             // First, see if one was chosen via the Choose... button.
  453.             if ( result ) {
  454.                 // Verify that the user didn't type in something different later.
  455.                 if ( typed != result.path ) {
  456.                     // Use what was typed in.
  457.                     try {
  458.                         result.QueryInterface( Components.interfaces.nsILocalFile ).initWithPath( typed );
  459.                     } catch( e ) {
  460.                         // Invalid path was typed.
  461.                         result = null;
  462.                     }
  463.                 }
  464.             } else {
  465.                 // The user didn't use the Choose... button, try using what they typed in.
  466.                 result = Components.classes[ "@mozilla.org/file/local;1" ]
  467.                     .createInstance( Components.interfaces.nsILocalFile );
  468.                 try {
  469.                     result.initWithPath( typed );
  470.                 } catch( e ) {
  471.                     result = null;
  472.                 }
  473.             }
  474.             // Remember what was chosen.
  475.             this.chosenApp = result;
  476.         }
  477.         return result;
  478.     },
  479.  
  480.     updateOKButton: function() {
  481.         var ok = false;
  482.         if ( this.dialogElement( "saveToDisk" ).selected ) 
  483.         {
  484.             // This is always OK.
  485.             ok = true;
  486.         } 
  487.         else if ( this.dialogElement( "useSystemDefault" ).selected )
  488.         {
  489.             // No app need be specified in this case.
  490.             ok = true;
  491.         }
  492.         else 
  493.         {
  494.             // only enable the OK button if we have a default app to use or if 
  495.             // the user chose an app....
  496.             ok = this.chosenApp || /\S/.test( this.dialogElement( "appPath" ).value );
  497.         }
  498.         
  499.         // Enable Ok button if ok to press.
  500.         this.mDialog.document.documentElement.getButton( "accept" ).disabled = !ok;
  501.     },
  502.     
  503.     // Returns true iff the user-specified helper app has been modified.
  504.     appChanged: function() {
  505.         return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
  506.     },
  507.  
  508.     updateMIMEInfo: function() {
  509.         var needUpdate = false;
  510.         // If current selection differs from what's in the mime info object,
  511.         // then we need to update.
  512.         if ( this.dialogElement( "saveToDisk" ).selected ) {
  513.             needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
  514.             if ( needUpdate )
  515.                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
  516.         } else if ( this.dialogElement( "useSystemDefault" ).selected ) {
  517.             needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
  518.             if ( needUpdate )
  519.                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
  520.         } else {
  521.             // For "open with", we need to check both preferred action and whether the user chose
  522.             // a new app.
  523.             needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
  524.             if ( needUpdate ) {
  525.                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
  526.                 // App may have changed - Update application and description
  527.                 var app = this.helperAppChoice();
  528.                 this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
  529.                 this.mLauncher.MIMEInfo.applicationDescription = "";
  530.             }
  531.         }
  532.         // Only care about the state of "always ask" if this dialog wasn't forced
  533.         if ( !this.mForced )
  534.         {
  535.           // We will also need to update if the "always ask" flag has changed.
  536.           needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling == this.dialogElement( "alwaysHandle" ).checked;
  537.         
  538.           // One last special case: If the input "always ask" flag was false, then we always
  539.           // update.  In that case we are displaying the helper app dialog for the first
  540.           // time for this mime type and we need to store the user's action in the mimeTypes.rdf
  541.           // data source (whether that action has changed or not; if it didn't change, then we need
  542.           // to store the "always ask" flag so the helper app dialog will or won't display
  543.           // next time, per the user's selection).
  544.           needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  545.         
  546.           // Make sure mime info has updated setting for the "always ask" flag.
  547.           this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement( "alwaysHandle" ).checked;
  548.         }
  549.  
  550.         return needUpdate;        
  551.     },
  552.     
  553.     // See if the user changed things, and if so, update the
  554.     // mimeTypes.rdf entry for this mime type.
  555.     updateHelperAppPref: function() {
  556.         // We update by passing this mime info into the "Edit Type" helper app
  557.         // pref dialog.  It will update the data source and close the dialog
  558.         // automatically.
  559.         this.mDialog.openDialog( "chrome://communicator/content/pref/pref-applications-edit.xul",
  560.                                  "_blank",
  561.                                  "chrome,modal=yes,resizable=no",
  562.                                  this );
  563.     },
  564.     
  565.     // onOK:
  566.     onOK: function() {
  567.         // Verify typed app path, if necessary.
  568.         if ( this.dialogElement( "openUsing" ).selected ) {
  569.             var helperApp = this.helperAppChoice();
  570.             if ( !helperApp || !helperApp.exists() ) {
  571.                 // Show alert and try again.                            
  572.                 var msg = this.replaceInsert( this.getString( "badApp" ), 1, this.dialogElement( "appPath" ).value );
  573.                 var svc = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
  574.                             .getService( Components.interfaces.nsIPromptService );
  575.                 svc.alert( this.mDialog, this.getString( "badApp.title" ), msg );
  576.                 // Disable the OK button.
  577.                 this.mDialog.document.documentElement.getButton( "accept" ).disabled = true;
  578.                 // Select and focus the input field if input field is not disabled
  579.                 var path = this.dialogElement( "appPath" );
  580.                 if ( !path.disabled ) {
  581.                     path.select();
  582.                     path.focus();
  583.                 }
  584.                 // Clear chosen application.
  585.                 this.chosenApp = null;
  586.                 // Leave dialog up.
  587.                 return false;
  588.             }
  589.         }
  590.         
  591.         // Remove our web progress listener (a progress dialog will be
  592.         // taking over).
  593.         this.mLauncher.setWebProgressListener( null );
  594.         
  595.         // saveToDisk and launchWithApplication can return errors in 
  596.         // certain circumstances (e.g. The user clicks cancel in the
  597.         // "Save to Disk" dialog. In those cases, we don't want to
  598.         // update the helper application preferences in the RDF file.
  599.         try {
  600.             var needUpdate = this.updateMIMEInfo();
  601.             if ( this.dialogElement( "saveToDisk" ).selected )
  602.                 this.mLauncher.saveToDisk( null, false );
  603.             else
  604.                 this.mLauncher.launchWithApplication( null, false );
  605.         
  606.             // Update user pref for this mime type (if necessary). We do not
  607.             // store anything in the mime type preferences for the ambiguous
  608.             // type application/octet-stream.
  609.             if ( needUpdate &&
  610.                  this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream" )
  611.             {
  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.