home *** CD-ROM | disk | FTP | other *** search
/ PC World 2003 October / PCWorld_2003-10_cd.bin / Komunik / Thunderbird / thunderbird-0.2-win32.exe / thunderbird / components / nsHelperAppDlg.js < prev    next >
Encoding:
JavaScript  |  2003-09-01  |  31.9 KB  |  834 lines

  1. /*
  2. # -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  3. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4. # The contents of this file are subject to the Mozilla Public License Version
  5. # 1.1 (the "License"); you may not use this file except in compliance with
  6. # the License. You may obtain a copy of the License at
  7. # http://www.mozilla.org/MPL/
  8. # Software distributed under the License is distributed on an "AS IS" basis,
  9. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  10. # for the specific language governing rights and limitations under the
  11. # License.
  12. # The Original Code is Mozilla.org Code.
  13. # The Initial Developer of the Original Code is
  14. # Doron Rosenberg.
  15. # Portions created by the Initial Developer are Copyright (C) 2001
  16. # the Initial Developer. All Rights Reserved.
  17. # Contributor(s):
  18. #   Bill Law <law@netscape.com>
  19. #   Scott MacGregor <mscott@netscape.com>
  20. #   Ben Goodger <ben@bengoodger.com> (2.0)
  21. # Alternatively, the contents of this file may be used under the terms of
  22. # either the GNU General Public License Version 2 or later (the "GPL"), or
  23. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  24. # in which case the provisions of the GPL or the LGPL are applicable instead
  25. # of those above. If you wish to allow use of your version of this file only
  26. # under the terms of either the GPL or the LGPL, and not to allow others to
  27. # use your version of this file under the terms of the MPL, indicate your
  28. # decision by deleting the provisions above and replace them with the notice
  29. # and other provisions required by the GPL or the LGPL. If you do not delete
  30. # the provisions above, a recipient may use your version of this file under
  31. # the terms of any one of the MPL, the GPL or the LGPL.
  32. # ***** END LICENSE BLOCK *****
  33. */
  34.  
  35. /* This file implements the nsIHelperAppLauncherDialog interface.
  36.  *
  37.  * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog,
  38.  * comprised of:
  39.  *   - a JS constructor function
  40.  *   - a prototype providing all the interface methods and implementation stuff
  41.  *
  42.  * In addition, this file implements an nsIModule object that registers the
  43.  * nsUnknownContentTypeDialog component.
  44.  */
  45.  
  46.  
  47. /* ctor
  48.  */
  49. function nsUnknownContentTypeDialog() {
  50.     // Initialize data properties.
  51.     this.mLauncher = null;
  52.     this.mContext  = null;
  53.     this.mSourcePath = null;
  54.     this.chosenApp = null;
  55.     this.givenDefaultApp = false;
  56.     this.updateSelf = true;
  57.     this.mTitle    = "";
  58. }
  59.  
  60. nsUnknownContentTypeDialog.prototype = {
  61.     nsIMIMEInfo  : Components.interfaces.nsIMIMEInfo,
  62.  
  63.     // This "class" supports nsIHelperAppLauncherDialog, and nsISupports.
  64.     QueryInterface: function (iid) {
  65.         if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
  66.             !iid.equals(Components.interfaces.nsISupports)) {
  67.             throw Components.results.NS_ERROR_NO_INTERFACE;
  68.         }
  69.         return this;
  70.     },
  71.  
  72.     // ---------- nsIHelperAppLauncherDialog methods ----------
  73.  
  74.     // show: Open XUL dialog using window watcher.  Since the dialog is not
  75.     //       modal, it needs to be a top level window and the way to open
  76.     //       one of those is via that route).
  77.     show: function(aLauncher, aContext)  {  
  78.       this.mLauncher = aLauncher;
  79.       this.mContext  = aContext;
  80.       // Display the dialog using the Window Watcher interface.
  81.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  82.                 .getService(Components.interfaces.nsIWindowWatcher);
  83.       this.mDialog = ww.openWindow(null, // no parent
  84.                                    "chrome://mozapps/content/downloads/unknownContentType.xul",
  85.                                    null,
  86.                                    "chrome,titlebar,dialog=yes",
  87.                                    null);
  88.       // Hook this object to the dialog.
  89.       this.mDialog.dialog = this;
  90.       
  91.       // Hook up utility functions. 
  92.       // XXXben these lines can disappear if we can get XULPP to run on stuff not 
  93.       //        thus referenced in jar.mns. 
  94.       this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey;
  95.       
  96.       this.mIsMac = (this.mDialog.navigator.platform.indexOf("Mac") != -1);
  97.       this.mIsWin = (this.mDialog.navigator.platform.indexOf("Win") != -1);
  98.       
  99.       // Watch for error notifications.
  100.       this.progressListener.helperAppDlg = this;
  101.       this.mLauncher.setWebProgressListener(this.progressListener);
  102.     },
  103.  
  104.     // promptForSaveToFile:  Display file picker dialog and return selected file.
  105.     //                       This is called by the External Helper App Service
  106.     //                       after the ucth dialog calls |saveToDisk| with a null
  107.     //                       target filename (no target, therefore user must pick).
  108.     //
  109.     //                       Alternatively, if the user has selected to have all
  110.     //                       files download to a specific location, return that
  111.     //                       location and don't ask via the dialog. 
  112.     //
  113.     // Note - this function is called without a dialog, so it cannot access any part
  114.     // of the dialog XUL as other functions on this object do. 
  115.     promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension) {
  116.       var result = "";
  117.       
  118.       this.mLauncher = aLauncher;
  119.  
  120.       // If the user is always downloading to the same location, the default download
  121.       // folder is stored in preferences. If a value is found stored, use that 
  122.       // automatically and don't ask via a dialog. 
  123.       const kDownloadFolderPref = "browser.download.defaultFolder";
  124.       var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
  125.       try {
  126.         result = prefs.getComplexValue(kDownloadFolderPref, Components.interfaces.nsILocalFile);
  127.         result.append(aDefaultFile);
  128.         
  129.         // Since we're automatically downloading, we don't get the file picker's 
  130.         // logic to check for existing files, so we need to do that here.
  131.         //
  132.         // Note - this code is identical to that in 
  133.         //   browser/base/content/contentAreaUtils.js. 
  134.         // If you are updating this code, update that code too! We can't share code
  135.         // here since this is called in a js component. 
  136.         while (result.exists()) {
  137.           var parts = /.+-(\d+)(\..*)?$/.exec(result.leafName);
  138.           if (parts) {
  139.             result.leafName = result.leafName.replace(/((\d+)\.)/, 
  140.                                                       function (str, p1, part, s) { 
  141.                                                         return (parseInt(part) + 1) + "."; 
  142.                                                       });
  143.           }
  144.           else {
  145.             result.leafName = result.leafName.replace(/\./, "-1$&");
  146.           }
  147.         }
  148.       }
  149.       catch (e) { }
  150.       
  151.       if (!result) {
  152.         // Use file picker to show dialog.
  153.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  154.         var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  155.  
  156.         var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);
  157.         bundle = bundle.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
  158.  
  159.         var windowTitle = bundle.GetStringFromName("saveDialogTitle");
  160.         var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowInternal);
  161.         picker.init(parent, windowTitle, nsIFilePicker.modeSave);
  162.         picker.defaultString = aDefaultFile;
  163.  
  164.         if (aSuggestedFileExtension) {
  165.           // aSuggestedFileExtension includes the period, so strip it
  166.           picker.defaultExtension = aSuggestedFileExtension.substring(1);
  167.         } 
  168.         else {
  169.           try {
  170.             picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
  171.           } 
  172.           catch (ex) { }
  173.         }
  174.  
  175.         var wildCardExtension = "*";
  176.         if (aSuggestedFileExtension) {
  177.           wildCardExtension += aSuggestedFileExtension;
  178.           picker.appendFilter(this.mLauncher.MIMEInfo.Description, wildCardExtension);
  179.         }
  180.  
  181.         picker.appendFilters( nsIFilePicker.filterAll );
  182.  
  183.         // Pull in the user's preferences and get the default download directory.
  184.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
  185.         try {
  186.           var startDir = prefs.getComplexValue("browser.download.dir", Components.interfaces.nsILocalFile);
  187.           if (startDir.exists()) {
  188.             picker.displayDirectory = startDir;
  189.           }
  190.         } 
  191.         catch(exception) { }
  192.  
  193.         var dlgResult = picker.show();
  194.  
  195.         if (dlgResult == nsIFilePicker.returnCancel) {
  196.           // null result means user cancelled.
  197.           return null;
  198.         }
  199.  
  200.  
  201.         // Be sure to save the directory the user chose as the new browser.download.dir
  202.         result = picker.file;
  203.  
  204.         if (result) {
  205.           var newDir = result.parent;
  206.           prefs.setComplexValue("browser.download.dir", Components.interfaces.nsILocalFile, newDir);
  207.         }
  208.         
  209.       }
  210.       return result;
  211.     },
  212.     
  213.     // ---------- implementation methods ----------
  214.  
  215.     // Web progress listener so we can detect errors while mLauncher is
  216.     // streaming the data to a temporary file.
  217.     progressListener: {
  218.         // Implementation properties.
  219.         helperAppDlg: null,
  220.  
  221.         // nsIWebProgressListener methods.
  222.         // Look for error notifications and display alert to user.
  223.         onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
  224.             if ( aStatus != Components.results.NS_OK ) {
  225.                 // Get prompt service.
  226.                 var prompter = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
  227.                                    .getService( Components.interfaces.nsIPromptService );
  228.                 // Display error alert (using text supplied by back-end).
  229.                 prompter.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
  230.  
  231.                 // Close the dialog.
  232.                 this.helperAppDlg.onCancel();
  233.                 if ( this.helperAppDlg.mDialog ) {
  234.                     this.helperAppDlg.mDialog.close();
  235.                 }
  236.             }
  237.         },
  238.  
  239.         // Ignore onProgressChange, onStateChange, onLocationChange, and onSecurityChange notifications.
  240.         onProgressChange: function( aWebProgress,
  241.                                     aRequest,
  242.                                     aCurSelfProgress,
  243.                                     aMaxSelfProgress,
  244.                                     aCurTotalProgress,
  245.                                     aMaxTotalProgress ) {
  246.         },
  247.  
  248.         onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
  249.         },
  250.  
  251.         onLocationChange: function( aWebProgress, aRequest, aLocation ) {
  252.         },
  253.  
  254.         onSecurityChange: function( aWebProgress, aRequest, state ) {
  255.         }
  256.     },
  257.  
  258.     // initDialog:  Fill various dialog fields with initial content.
  259.     initDialog : function() {
  260.          // Put file name in window title.
  261.          var win   = this.dialogElement( "unknownContentType" );
  262.          var suggestedFileName = this.mLauncher.suggestedFileName;
  263.  
  264.          // Some URIs do not implement nsIURL, so we can't just QI.
  265.          var url   = this.mLauncher.source;
  266.          var fname = "";
  267.          this.mSourcePath = url.prePath;
  268.          try {
  269.              url = url.QueryInterface( Components.interfaces.nsIURL );
  270.              // A url, use file name from it.
  271.              fname = url.fileName;
  272.              this.mSourcePath += url.directory;
  273.          } catch (ex) {
  274.              // A generic uri, use path.
  275.              fname = url.path;
  276.              this.mSourcePath += url.path;
  277.          }
  278.  
  279.          if (suggestedFileName)
  280.            fname = suggestedFileName;
  281.            
  282.  
  283.          this.mTitle = this.dialogElement("strings").getFormattedString("title", [fname]);
  284.          win.setAttribute( "title", this.mTitle );
  285.  
  286.          // Put content type, filename and location into intro.
  287.          this.initIntro(url, fname);
  288.  
  289.          var iconString = "moz-icon://" + fname + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
  290.          this.dialogElement("contentTypeImage").setAttribute("src", iconString);
  291.  
  292.          this.initAppAndSaveToDiskValues();
  293.  
  294.          // Initialize "always ask me" box. This should always be disabled
  295.          // and set to true for the ambiguous type application/octet-stream.
  296.          // We don't also check for application/x-msdownload here since we
  297.          // want users to be able to autodownload .exe files. 
  298.          var rememberChoice = this.dialogElement("rememberChoice");
  299.          if (this.mLauncher.MIMEInfo.MIMEType == "application/octet-stream") {
  300.             rememberChoice.checked = false;
  301.             rememberChoice.disabled = true;
  302.          }
  303.          else {
  304.             rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  305.          }
  306.          this.toggleRememberChoice(rememberChoice);
  307.          
  308.  
  309.          // XXXben - menulist won't init properly, hack. 
  310.          var openHandler = this.dialogElement("openHandler");
  311.          openHandler.parentNode.removeChild(openHandler);
  312.          var openParent = this.dialogElement("open").parentNode;
  313.          openParent.appendChild(openHandler);
  314.          
  315.          this.mDialog.setTimeout("dialog.postShowCallback()", 0);
  316.     },
  317.     
  318.     postShowCallback: function () {
  319.       // Position the window if it has never been positioned before (i.e. 
  320.       // before XUL persistence has picked up anything to fill the screenX/
  321.       // screenY attributes with. Assume if one is unset, the other will be too).
  322.       if (this.mDialog.document.documentElement.getAttribute("screenX") == "") {
  323.         if (this.mDialog.opener) {
  324.           this.mDialog.moveToAlertPosition();
  325.         } 
  326.         else {
  327.           this.mDialog.centerWindowOnScreen();
  328.         }
  329.       }
  330.       
  331.       this.mDialog.sizeToContent();
  332.  
  333.       // Set initial focus
  334.       this.dialogElement("mode").focus();
  335.     },
  336.  
  337.     // initIntro:
  338.     initIntro: function(url, filename) {
  339.         this.dialogElement( "location" ).value = filename;
  340.  
  341.         // if mSourcePath is a local file, then let's use the pretty path name instead of an ugly
  342.         // url...
  343.         var pathString = this.mSourcePath;
  344.         try 
  345.         {
  346.           var fileURL = url.QueryInterface(Components.interfaces.nsIFileURL);
  347.           if (fileURL)
  348.           {
  349.             var fileObject = fileURL.file;
  350.             if (fileObject)
  351.             {
  352.               var parentObject = fileObject.parent;
  353.               if (parentObject)
  354.               {
  355.                 pathString = parentObject.path;
  356.               }
  357.             }
  358.           }
  359.         } catch(ex) {}
  360.  
  361.         // Set the location text, which is separate from the intro text so it can be cropped
  362.         var location = this.dialogElement( "source" );
  363.         location.value = pathString;
  364.         
  365.         // Show the type of file. 
  366.         var type = this.dialogElement("type");
  367.         var mimeInfo = this.mLauncher.MIMEInfo;
  368.         
  369.         // 1. Try to use the pretty description of the type, if one is available.
  370.         var typeString = mimeInfo.Description;
  371.         
  372.         if (typeString == "") {
  373.           // 2. If there is none, use the extension to identify the file, e.g. "ZIP file"
  374.           var primaryExtension = "";
  375.           try {
  376.             primaryExtension = mimeInfo.primaryExtension;
  377.           }
  378.           catch (ex) {
  379.           }
  380.           if (primaryExtension != "")
  381.             typeString = primaryExtension.toUpperCase() + " file";
  382.           // 3. If we can't even do that, just give up and show the MIME type. 
  383.           else
  384.             typeString = mimeInfo.MIMEType;
  385.         }
  386.         
  387.         type.value = typeString;
  388.     },
  389.  
  390.     // Returns true if opening the default application makes sense.
  391.     openWithDefaultOK: function() {
  392.         var result;
  393.  
  394.         // The checking is different on Windows...
  395.         if ( this.mIsWin ) {
  396.             // Windows presents some special cases.
  397.             // We need to prevent use of "system default" when the file is
  398.             // executable (so the user doesn't launch nasty programs downloaded
  399.             // from the web), and, enable use of "system default" if it isn't
  400.             // executable (because we will prompt the user for the default app
  401.             // in that case).
  402.             
  403.             // Need to get temporary file and check for executable-ness.
  404.             var ignore1 = new Object;
  405.             var ignore2 = new Object;
  406.             var tmpFile = this.mLauncher.getDownloadInfo( ignore1, ignore2 );
  407.             
  408.             //  Default is Ok if the file isn't executable (and vice-versa).
  409.             result = !tmpFile.isExecutable();
  410.         } else {
  411.             // On other platforms, default is Ok if there is a default app.
  412.             // Note that nsIMIMEInfo providers need to ensure that this holds true
  413.             // on each platform.
  414.             result = this.mLauncher.MIMEInfo.defaultApplicationHandler;
  415.         }
  416.         return result;
  417.     },
  418.     
  419.     // Set "default" application description field.
  420.     initDefaultApp: function() {
  421.       // Use description, if we can get one.
  422.       var desc = this.mLauncher.MIMEInfo.defaultDescription;
  423.       if (desc) {
  424.         var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]);
  425.         this.dialogElement("defaultHandler").label = defaultApp;
  426.       }
  427.     },
  428.  
  429.     // getPath:
  430.     getPath: function (aFile) {
  431.       if (this.mIsMac)
  432.         return aFile.leafName || aFile.path;
  433.       return aFile.path;
  434.     },
  435.  
  436.     // initAppAndSaveToDiskValues:
  437.     initAppAndSaveToDiskValues: function() {
  438.       var modeGroup = this.dialogElement("mode");
  439.  
  440.       // We don't let users open .exe files or random binary data directly 
  441.       // from the browser at the moment because of security concerns. 
  442.       var mimeType = this.mLauncher.MIMEInfo.MIMEType;
  443.       if (mimeType == "application/octet-stream" ||
  444.           mimeType == "application/x-msdownload") {
  445.         this.dialogElement("open").disabled = true;
  446.         var openHandler = this.dialogElement("openHandler");
  447.         openHandler.disabled = true;
  448.         openHandler.label = "";
  449.         modeGroup.selectedItem = this.dialogElement("save");
  450.         return;
  451.       }
  452.     
  453.       // Fill in helper app info, if there is any.
  454.       this.chosenApp = this.mLauncher.MIMEInfo.preferredApplicationHandler;
  455.       // Initialize "default application" field.
  456.       this.initDefaultApp();
  457.  
  458.       var otherHandler = this.dialogElement("otherHandler");
  459.               
  460.       // Fill application name textbox.
  461.       if (this.chosenApp && this.chosenApp.path) {
  462.         otherHandler.setAttribute("path", this.getPath(this.chosenApp));
  463.         otherHandler.label = this.chosenApp.leafName;
  464.         otherHandler.hidden = false;
  465.       }
  466.  
  467.       var useDefault = this.dialogElement("useSystemDefault");
  468.       var openHandler = this.dialogElement("openHandler");
  469.       openHandler.selectedIndex = 0;
  470.  
  471.       if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
  472.         // Open (using system default).
  473.         modeGroup.selectedItem = this.dialogElement("open");
  474.       } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
  475.         // Open with given helper app.
  476.         modeGroup.selectedItem = this.dialogElement("open");
  477.         openHandler.selectedIndex = 1;
  478.       } else {
  479.         // Save to disk.
  480.         modeGroup.selectedItem = this.dialogElement("save");
  481.       }
  482.       
  483.       // If we don't have a "default app" then disable that choice.
  484.       if (!this.openWithDefaultOK()) {
  485.         var useDefault = this.dialogElement("defaultHandler");
  486.         var isSelected = useDefault.selected;
  487.         
  488.         // Disable that choice.
  489.         useDefault.hidden = true;
  490.         // If that's the default, then switch to "save to disk."
  491.         if (isSelected) {
  492.           openHandler.selectedIndex = 1;
  493.           modeGroup.selectedItem = this.dialogElement("save");
  494.         }
  495.       }
  496.       
  497.       // otherHandler is always disabled on Mac
  498.       if (this.mIsMac) 
  499.         otherHandler.hidden = true;
  500.  
  501.       otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = this.isMac;
  502.       this.updateOKButton();
  503.     },
  504.  
  505.     // Returns the user-selected application
  506.     helperAppChoice: function() {
  507.       return this.chosenApp;
  508.     },
  509.     
  510.     get saveToDisk() {
  511.       return this.dialogElement("save").selected;
  512.     },
  513.     
  514.     get useOtherHandler() {
  515.       return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1;
  516.     },
  517.     
  518.     get useSystemDefault() {
  519.       return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0;
  520.     },
  521.     
  522.     toggleRememberChoice: function (aCheckbox) {
  523.         this.dialogElement("settingsChange").hidden = !aCheckbox.checked;
  524.         this.mDialog.sizeToContent();
  525.     },
  526.     
  527.     openHandlerCommand: function () {
  528.       if (this.dialogElement("openHandler").selectedItem.id == "choose")
  529.         this.chooseApp();
  530.     },
  531.  
  532.     updateOKButton: function() {
  533.       var ok = false;
  534.       if (this.dialogElement("save").selected) {
  535.         // This is always OK.
  536.         ok = true;
  537.       } 
  538.       else if (this.dialogElement("open").selected) {
  539.         switch (this.dialogElement("openHandler").selectedIndex) {
  540.         case 0:
  541.           // No app need be specified in this case.
  542.           ok = true;
  543.           break;
  544.         case 1:
  545.           // only enable the OK button if we have a default app to use or if 
  546.           // the user chose an app....
  547.           ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path")); 
  548.         break;
  549.         }
  550.       }
  551.  
  552.       // Enable Ok button if ok to press.
  553.       this.mDialog.document.documentElement.getButton("accept").disabled = !ok;
  554.     },
  555.     
  556.     // Returns true iff the user-specified helper app has been modified.
  557.     appChanged: function() {
  558.       return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
  559.     },
  560.  
  561.     updateMIMEInfo: function() {
  562.       var needUpdate = false;
  563.       // If current selection differs from what's in the mime info object,
  564.       // then we need to update.
  565.       if (this.saveToDisk) {
  566.         needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
  567.         if (needUpdate)
  568.           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
  569.       } 
  570.       else if (this.useSystemDefault) {
  571.         needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
  572.         if (needUpdate)
  573.           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
  574.       } 
  575.       else {
  576.         // For "open with", we need to check both preferred action and whether the user chose
  577.         // a new app.
  578.         needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
  579.         if (needUpdate) {
  580.           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
  581.           // App may have changed - Update application and description
  582.           var app = this.helperAppChoice();
  583.           this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
  584.           this.mLauncher.MIMEInfo.applicationDescription = "";
  585.         }
  586.       }
  587.       // We will also need to update if the "always ask" flag has changed.
  588.       needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked);
  589.  
  590.       // One last special case: If the input "always ask" flag was false, then we always
  591.       // update.  In that case we are displaying the helper app dialog for the first
  592.       // time for this mime type and we need to store the user's action in the mimeTypes.rdf
  593.       // data source (whether that action has changed or not; if it didn't change, then we need
  594.       // to store the "always ask" flag so the helper app dialog will or won't display
  595.       // next time, per the user's selection).
  596.       needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  597.  
  598.       // Make sure mime info has updated setting for the "always ask" flag.
  599.       this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked;
  600.  
  601.       return needUpdate;        
  602.     },
  603.     
  604.     // See if the user changed things, and if so, update the
  605.     // mimeTypes.rdf entry for this mime type.
  606.     updateHelperAppPref: function() {
  607.       var ha = new this.mDialog.HelperApps();
  608.       ha.updateTypeInfo(this.mLauncher.MIMEInfo);
  609.     },
  610.     
  611.     // onOK:
  612.     onOK: function() {
  613.       // Verify typed app path, if necessary.
  614.       if (this.useOtherHandler) {
  615.         var helperApp = this.helperAppChoice();
  616.         if (!helperApp || !helperApp.exists()) {
  617.           // Show alert and try again.        
  618.           var bundle = this.dialogElement("strings");                    
  619.           var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").path]);
  620.           var svc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
  621.           svc.alert(this.mDialog, bundle.getString("badApp.title"), msg);
  622.  
  623.           // Disable the OK button.
  624.           this.mDialog.document.documentElement.getButton("accept").disabled = true;
  625.           this.dialogElement("mode").focus();          
  626.  
  627.           // Clear chosen application.
  628.           this.chosenApp = null;
  629.  
  630.           // Leave dialog up.
  631.           return false;
  632.         }
  633.       }
  634.         
  635.       // Remove our web progress listener (a progress dialog will be
  636.       // taking over).
  637.       this.mLauncher.setWebProgressListener(null);
  638.       
  639.       // saveToDisk and launchWithApplication can return errors in 
  640.       // certain circumstances (e.g. The user clicks cancel in the
  641.       // "Save to Disk" dialog. In those cases, we don't want to
  642.       // update the helper application preferences in the RDF file.
  643.       try {
  644.         var needUpdate = this.updateMIMEInfo();
  645.         
  646.         if (this.dialogElement("save").selected) {
  647.           // If we're using a default download location, create a path
  648.           // for the file to be saved to to pass to |saveToDisk| - otherwise
  649.           // we must ask the user to pick a save name.
  650.  
  651.           var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
  652.           var targetFile = null;
  653.           try {
  654.             targetFile = prefs.getComplexValue("browser.download.defaultFolder", 
  655.                                                Components.interfaces.nsILocalFile);
  656.             targetFile.append(this.dialogElement("location").value);
  657.           }
  658.           catch(e) {}
  659.           
  660.           this.mLauncher.saveToDisk(targetFile, false);
  661.         }
  662.         else
  663.           this.mLauncher.launchWithApplication(null, false);
  664.  
  665.         // Update user pref for this mime type (if necessary). We do not
  666.         // store anything in the mime type preferences for the ambiguous
  667.         // type application/octet-stream. We do NOT do this for 
  668.         // application/x-msdownload since we want users to be able to 
  669.         // autodownload these to disk. 
  670.         if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream")
  671.           this.updateHelperAppPref();
  672.       } catch(e) { }
  673.  
  674.       // Unhook dialog from this object.
  675.       this.mDialog.dialog = null;
  676.  
  677.       // Close up dialog by returning true.
  678.       return true;
  679.     },
  680.  
  681.     // onCancel:
  682.     onCancel: function() {
  683.       // Remove our web progress listener.
  684.       this.mLauncher.setWebProgressListener(null);
  685.  
  686.       // Cancel app launcher.
  687.       try {
  688.         this.mLauncher.Cancel();
  689.       } catch(exception) {
  690.       }
  691.  
  692.       // Unhook dialog from this object.
  693.       this.mDialog.dialog = null;
  694.  
  695.       // Close up dialog by returning true.
  696.       return true;
  697.     },
  698.  
  699.     // dialogElement:  Convenience. 
  700.     dialogElement: function(id) {
  701.       return this.mDialog.document.getElementById(id);
  702.     },
  703.  
  704.     // chooseApp:  Open file picker and prompt user for application.
  705.     chooseApp: function() {
  706.       var nsIFilePicker = Components.interfaces.nsIFilePicker;
  707.       var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  708.       fp.init(this.mDialog,
  709.               this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
  710.               nsIFilePicker.modeOpen);
  711.  
  712.       fp.appendFilters(nsIFilePicker.filterApps);
  713.  
  714.       if (fp.show() == nsIFilePicker.returnOK && fp.file) {
  715.         // Remember the file they chose to run.
  716.         this.chosenApp = fp.file;
  717.         // Update dialog.
  718.         var otherHandler = this.dialogElement("otherHandler");
  719.         otherHandler.removeAttribute("hidden");
  720.         otherHandler.setAttribute("path", this.getPath(this.chosenApp));
  721.         otherHandler.label = this.chosenApp.leafName;
  722.         this.dialogElement("openHandler").selectedIndex = 1;
  723.         
  724.         this.dialogElement("mode").selectedItem = this.dialogElement("open");
  725.       }
  726.     },
  727.  
  728.     // Turn this on to get debugging messages.
  729.     debug: false,
  730.  
  731.     // Dump text (if debug is on).
  732.     dump: function( text ) {
  733.         if ( this.debug ) {
  734.             dump( text ); 
  735.         }
  736.     },
  737.  
  738.     // dumpInfo:
  739.     doDebug: function() {
  740.         const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
  741.         // Open new progress dialog.
  742.         var progress = Components.classes[ "@mozilla.org/progressdialog;1" ]
  743.                          .createInstance( nsIProgressDialog );
  744.         // Show it.
  745.         progress.open( this.mDialog );
  746.     },
  747.  
  748.     // dumpObj:
  749.     dumpObj: function( spec ) {
  750.          var val = "<undefined>";
  751.          try {
  752.              val = eval( "this."+spec ).toString();
  753.          } catch( exception ) {
  754.          }
  755.          this.dump( spec + "=" + val + "\n" );
  756.     },
  757.  
  758.     // dumpObjectProperties
  759.     dumpObjectProperties: function( desc, obj ) {
  760.          for( prop in obj ) {
  761.              this.dump( desc + "." + prop + "=" );
  762.              var val = "<undefined>";
  763.              try {
  764.                  val = obj[ prop ];
  765.              } catch ( exception ) {
  766.              }
  767.              this.dump( val + "\n" );
  768.          }
  769.     }
  770. }
  771.  
  772. // This Component's module implementation.  All the code below is used to get this
  773. // component registered and accessible via XPCOM.
  774. var module = {
  775.     firstTime: true,
  776.  
  777.     // registerSelf: Register this component.
  778.     registerSelf: function (compMgr, fileSpec, location, type) {
  779.         if (this.firstTime) {
  780.             this.firstTime = false;
  781.             throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  782.         }
  783.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  784.  
  785.         compMgr.registerFactoryLocation( this.cid,
  786.                                          "Unknown Content Type Dialog",
  787.                                          this.contractId,
  788.                                          fileSpec,
  789.                                          location,
  790.                                          type );
  791.     },
  792.  
  793.     // getClassObject: Return this component's factory object.
  794.     getClassObject: function (compMgr, cid, iid) {
  795.         if (!cid.equals(this.cid)) {
  796.             throw Components.results.NS_ERROR_NO_INTERFACE;
  797.         }
  798.  
  799.         if (!iid.equals(Components.interfaces.nsIFactory)) {
  800.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  801.         }
  802.  
  803.         return this.factory;
  804.     },
  805.  
  806.     /* CID for this class */
  807.     cid: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
  808.  
  809.     /* Contract ID for this class */
  810.     contractId: "@mozilla.org/helperapplauncherdialog;1",
  811.  
  812.     /* factory object */
  813.     factory: {
  814.         // createInstance: Return a new nsProgressDialog object.
  815.         createInstance: function (outer, iid) {
  816.             if (outer != null)
  817.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  818.  
  819.             return (new nsUnknownContentTypeDialog()).QueryInterface(iid);
  820.         }
  821.     },
  822.  
  823.     // canUnload: n/a (returns true)
  824.     canUnload: function(compMgr) {
  825.         return true;
  826.     }
  827. };
  828.  
  829. // NSGetModule: Return the nsIModule object.
  830. function NSGetModule(compMgr, fileSpec) {
  831.     return module;
  832. }
  833.