toolkit/mozapps/downloads/nsHelperAppDlg.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1110 @@
     1.4 +/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2; js-indent-level: 2; -*- */
     1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */
     1.6 +/*
     1.7 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.8 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.9 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
    1.10 +*/
    1.11 +
    1.12 +Components.utils.import("resource://gre/modules/Services.jsm");
    1.13 +
    1.14 +///////////////////////////////////////////////////////////////////////////////
    1.15 +//// Helper Functions
    1.16 +
    1.17 +/**
    1.18 + * Determines if a given directory is able to be used to download to.
    1.19 + *
    1.20 + * @param aDirectory
    1.21 + *        The directory to check.
    1.22 + * @return true if we can use the directory, false otherwise.
    1.23 + */
    1.24 +function isUsableDirectory(aDirectory)
    1.25 +{
    1.26 +  return aDirectory.exists() && aDirectory.isDirectory() &&
    1.27 +         aDirectory.isWritable();
    1.28 +}
    1.29 +
    1.30 +// Web progress listener so we can detect errors while mLauncher is
    1.31 +// streaming the data to a temporary file.
    1.32 +function nsUnknownContentTypeDialogProgressListener(aHelperAppDialog) {
    1.33 +  this.helperAppDlg = aHelperAppDialog;
    1.34 +}
    1.35 +
    1.36 +nsUnknownContentTypeDialogProgressListener.prototype = {
    1.37 +  // nsIWebProgressListener methods.
    1.38 +  // Look for error notifications and display alert to user.
    1.39 +  onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
    1.40 +    if ( aStatus != Components.results.NS_OK ) {
    1.41 +      // Display error alert (using text supplied by back-end).
    1.42 +      // FIXME this.dialog is undefined?
    1.43 +      Services.prompt.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
    1.44 +      // Close the dialog.
    1.45 +      this.helperAppDlg.onCancel();
    1.46 +      if ( this.helperAppDlg.mDialog ) {
    1.47 +        this.helperAppDlg.mDialog.close();
    1.48 +      }
    1.49 +    }
    1.50 +  },
    1.51 +
    1.52 +  // Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, and onRefreshAttempted notifications.
    1.53 +  onProgressChange: function( aWebProgress,
    1.54 +                              aRequest,
    1.55 +                              aCurSelfProgress,
    1.56 +                              aMaxSelfProgress,
    1.57 +                              aCurTotalProgress,
    1.58 +                              aMaxTotalProgress ) {
    1.59 +  },
    1.60 +
    1.61 +  onProgressChange64: function( aWebProgress,
    1.62 +                                aRequest,
    1.63 +                                aCurSelfProgress,
    1.64 +                                aMaxSelfProgress,
    1.65 +                                aCurTotalProgress,
    1.66 +                                aMaxTotalProgress ) {
    1.67 +  },
    1.68 +
    1.69 +
    1.70 +
    1.71 +  onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
    1.72 +  },
    1.73 +
    1.74 +  onLocationChange: function( aWebProgress, aRequest, aLocation, aFlags ) {
    1.75 +  },
    1.76 +
    1.77 +  onSecurityChange: function( aWebProgress, aRequest, state ) {
    1.78 +  },
    1.79 +
    1.80 +  onRefreshAttempted: function( aWebProgress, aURI, aDelay, aSameURI ) {
    1.81 +    return true;
    1.82 +  }
    1.83 +};
    1.84 +
    1.85 +///////////////////////////////////////////////////////////////////////////////
    1.86 +//// nsUnknownContentTypeDialog
    1.87 +
    1.88 +/* This file implements the nsIHelperAppLauncherDialog interface.
    1.89 + *
    1.90 + * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog,
    1.91 + * comprised of:
    1.92 + *   - a JS constructor function
    1.93 + *   - a prototype providing all the interface methods and implementation stuff
    1.94 + *
    1.95 + * In addition, this file implements an nsIModule object that registers the
    1.96 + * nsUnknownContentTypeDialog component.
    1.97 + */
    1.98 +
    1.99 +const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
   1.100 +const nsITimer = Components.interfaces.nsITimer;
   1.101 +
   1.102 +let downloadModule = {};
   1.103 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
   1.104 +Components.utils.import("resource://gre/modules/DownloadLastDir.jsm", downloadModule);
   1.105 +Components.utils.import("resource://gre/modules/DownloadPaths.jsm");
   1.106 +Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
   1.107 +Components.utils.import("resource://gre/modules/Downloads.jsm");
   1.108 +Components.utils.import("resource://gre/modules/FileUtils.jsm");
   1.109 +Components.utils.import("resource://gre/modules/Task.jsm");
   1.110 +
   1.111 +/* ctor
   1.112 + */
   1.113 +function nsUnknownContentTypeDialog() {
   1.114 +  // Initialize data properties.
   1.115 +  this.mLauncher = null;
   1.116 +  this.mContext  = null;
   1.117 +  this.chosenApp = null;
   1.118 +  this.givenDefaultApp = false;
   1.119 +  this.updateSelf = true;
   1.120 +  this.mTitle    = "";
   1.121 +}
   1.122 +
   1.123 +nsUnknownContentTypeDialog.prototype = {
   1.124 +  classID: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
   1.125 +
   1.126 +  nsIMIMEInfo  : Components.interfaces.nsIMIMEInfo,
   1.127 +
   1.128 +  QueryInterface: function (iid) {
   1.129 +    if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
   1.130 +        !iid.equals(Components.interfaces.nsITimerCallback) &&
   1.131 +        !iid.equals(Components.interfaces.nsISupports)) {
   1.132 +      throw Components.results.NS_ERROR_NO_INTERFACE;
   1.133 +    }
   1.134 +    return this;
   1.135 +  },
   1.136 +
   1.137 +  // ---------- nsIHelperAppLauncherDialog methods ----------
   1.138 +
   1.139 +  // show: Open XUL dialog using window watcher.  Since the dialog is not
   1.140 +  //       modal, it needs to be a top level window and the way to open
   1.141 +  //       one of those is via that route).
   1.142 +  show: function(aLauncher, aContext, aReason)  {
   1.143 +    this.mLauncher = aLauncher;
   1.144 +    this.mContext  = aContext;
   1.145 +
   1.146 +    const nsITimer = Components.interfaces.nsITimer;
   1.147 +    this._showTimer = Components.classes["@mozilla.org/timer;1"]
   1.148 +                                .createInstance(nsITimer);
   1.149 +    this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
   1.150 +  },
   1.151 +
   1.152 +  // When opening from new tab, if tab closes while dialog is opening,
   1.153 +  // (which is a race condition on the XUL file being cached and the timer
   1.154 +  // in nsExternalHelperAppService), the dialog gets a blur and doesn't
   1.155 +  // activate the OK button.  So we wait a bit before doing opening it.
   1.156 +  reallyShow: function() {
   1.157 +    try {
   1.158 +      var ir = this.mContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
   1.159 +      var dwi = ir.getInterface(Components.interfaces.nsIDOMWindow);
   1.160 +      var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
   1.161 +                         .getService(Components.interfaces.nsIWindowWatcher);
   1.162 +      this.mDialog = ww.openWindow(dwi,
   1.163 +                                   "chrome://mozapps/content/downloads/unknownContentType.xul",
   1.164 +                                   null,
   1.165 +                                   "chrome,centerscreen,titlebar,dialog=yes,dependent",
   1.166 +                                   null);
   1.167 +    } catch (ex) {
   1.168 +      // The containing window may have gone away.  Break reference
   1.169 +      // cycles and stop doing the download.
   1.170 +      this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED);
   1.171 +      return;
   1.172 +    }
   1.173 +
   1.174 +    // Hook this object to the dialog.
   1.175 +    this.mDialog.dialog = this;
   1.176 +
   1.177 +    // Hook up utility functions.
   1.178 +    this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey;
   1.179 +
   1.180 +    // Watch for error notifications.
   1.181 +    var progressListener = new nsUnknownContentTypeDialogProgressListener(this);
   1.182 +    this.mLauncher.setWebProgressListener(progressListener);
   1.183 +  },
   1.184 +
   1.185 +  //
   1.186 +  // displayBadPermissionAlert()
   1.187 +  //
   1.188 +  // Diplay an alert panel about the bad permission of folder/directory.
   1.189 +  //
   1.190 +  displayBadPermissionAlert: function () {
   1.191 +    let bundle =
   1.192 +      Services.strings.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
   1.193 +
   1.194 +    Services.prompt.alert(this.dialog,
   1.195 +                   bundle.GetStringFromName("badPermissions.title"),
   1.196 +                   bundle.GetStringFromName("badPermissions"));
   1.197 +  },
   1.198 +
   1.199 +  // promptForSaveToFile:  Display file picker dialog and return selected file.
   1.200 +  //                       This is called by the External Helper App Service
   1.201 +  //                       after the ucth dialog calls |saveToDisk| with a null
   1.202 +  //                       target filename (no target, therefore user must pick).
   1.203 +  //
   1.204 +  //                       Alternatively, if the user has selected to have all
   1.205 +  //                       files download to a specific location, return that
   1.206 +  //                       location and don't ask via the dialog.
   1.207 +  //
   1.208 +  // Note - this function is called without a dialog, so it cannot access any part
   1.209 +  // of the dialog XUL as other functions on this object do.
   1.210 +
   1.211 +  promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
   1.212 +    throw new Components.Exception("Async version must be used", Components.results.NS_ERROR_NOT_AVAILABLE);
   1.213 +  },
   1.214 +
   1.215 +  promptForSaveToFileAsync: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
   1.216 +    var result = null;
   1.217 +
   1.218 +    this.mLauncher = aLauncher;
   1.219 +
   1.220 +    let prefs = Components.classes["@mozilla.org/preferences-service;1"]
   1.221 +                          .getService(Components.interfaces.nsIPrefBranch);
   1.222 +    let bundle =
   1.223 +      Services.strings
   1.224 +              .createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
   1.225 +
   1.226 +    Task.spawn(function() {
   1.227 +      if (!aForcePrompt) {
   1.228 +        // Check to see if the user wishes to auto save to the default download
   1.229 +        // folder without prompting. Note that preference might not be set.
   1.230 +        let autodownload = false;
   1.231 +        try {
   1.232 +          autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
   1.233 +        } catch (e) { }
   1.234 +
   1.235 +        if (autodownload) {
   1.236 +          // Retrieve the user's default download directory
   1.237 +          let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
   1.238 +          let defaultFolder = new FileUtils.File(preferredDir);
   1.239 +
   1.240 +          try {
   1.241 +            result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension);
   1.242 +          }
   1.243 +          catch (ex) {
   1.244 +            // When the default download directory is write-protected,
   1.245 +            // prompt the user for a different target file.
   1.246 +          }
   1.247 +
   1.248 +          // Check to make sure we have a valid directory, otherwise, prompt
   1.249 +          if (result) {
   1.250 +            // This path is taken when we have a writable default download directory.
   1.251 +            aLauncher.saveDestinationAvailable(result);
   1.252 +            return;
   1.253 +          }
   1.254 +        }
   1.255 +      }
   1.256 +
   1.257 +      // Use file picker to show dialog.
   1.258 +      var nsIFilePicker = Components.interfaces.nsIFilePicker;
   1.259 +      var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
   1.260 +      var windowTitle = bundle.GetStringFromName("saveDialogTitle");
   1.261 +      var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);
   1.262 +      picker.init(parent, windowTitle, nsIFilePicker.modeSave);
   1.263 +      picker.defaultString = aDefaultFile;
   1.264 +
   1.265 +      let gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
   1.266 +
   1.267 +      if (aSuggestedFileExtension) {
   1.268 +        // aSuggestedFileExtension includes the period, so strip it
   1.269 +        picker.defaultExtension = aSuggestedFileExtension.substring(1);
   1.270 +      }
   1.271 +      else {
   1.272 +        try {
   1.273 +          picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
   1.274 +        }
   1.275 +        catch (ex) { }
   1.276 +      }
   1.277 +
   1.278 +      var wildCardExtension = "*";
   1.279 +      if (aSuggestedFileExtension) {
   1.280 +        wildCardExtension += aSuggestedFileExtension;
   1.281 +        picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension);
   1.282 +      }
   1.283 +
   1.284 +      picker.appendFilters( nsIFilePicker.filterAll );
   1.285 +
   1.286 +      // Default to lastDir if it is valid, otherwise use the user's default
   1.287 +      // downloads directory.  getPreferredDownloadsDirectory should always
   1.288 +      // return a valid directory path, so we can safely default to it.
   1.289 +      let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
   1.290 +      picker.displayDirectory = new FileUtils.File(preferredDir);
   1.291 +
   1.292 +      gDownloadLastDir.getFileAsync(aLauncher.source, function LastDirCallback(lastDir) {
   1.293 +        if (lastDir && isUsableDirectory(lastDir))
   1.294 +          picker.displayDirectory = lastDir;
   1.295 +
   1.296 +        if (picker.show() == nsIFilePicker.returnCancel) {
   1.297 +          // null result means user cancelled.
   1.298 +          aLauncher.saveDestinationAvailable(null);
   1.299 +          return;
   1.300 +        }
   1.301 +
   1.302 +        // Be sure to save the directory the user chose through the Save As...
   1.303 +        // dialog  as the new browser.download.dir since the old one
   1.304 +        // didn't exist.
   1.305 +        result = picker.file;
   1.306 +
   1.307 +        if (result) {
   1.308 +          try {
   1.309 +            // Remove the file so that it's not there when we ensure non-existence later;
   1.310 +            // this is safe because for the file to exist, the user would have had to
   1.311 +            // confirm that he wanted the file overwritten.
   1.312 +            if (result.exists())
   1.313 +              result.remove(false);
   1.314 +          }
   1.315 +          catch (ex) {
   1.316 +            // As it turns out, the failure to remove the file, for example due to
   1.317 +            // permission error, will be handled below eventually somehow.
   1.318 +          }
   1.319 +
   1.320 +          var newDir = result.parent.QueryInterface(Components.interfaces.nsILocalFile);
   1.321 +
   1.322 +          // Do not store the last save directory as a pref inside the private browsing mode
   1.323 +          gDownloadLastDir.setFile(aLauncher.source, newDir);
   1.324 +
   1.325 +          try {
   1.326 +            result = this.validateLeafName(newDir, result.leafName, null);
   1.327 +          }
   1.328 +          catch (ex) {
   1.329 +            // When the chosen download directory is write-protected,
   1.330 +            // display an informative error message.
   1.331 +            // In all cases, download will be stopped.
   1.332 +
   1.333 +            if (ex.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED) {
   1.334 +              this.displayBadPermissionAlert();
   1.335 +              aLauncher.saveDestinationAvailable(null);
   1.336 +              return;
   1.337 +            }
   1.338 +
   1.339 +          }
   1.340 +        }
   1.341 +        aLauncher.saveDestinationAvailable(result);
   1.342 +      }.bind(this));
   1.343 +    }.bind(this)).then(null, Components.utils.reportError);
   1.344 +  },
   1.345 +
   1.346 +  /**
   1.347 +   * Ensures that a local folder/file combination does not already exist in
   1.348 +   * the file system (or finds such a combination with a reasonably similar
   1.349 +   * leaf name), creates the corresponding file, and returns it.
   1.350 +   *
   1.351 +   * @param   aLocalFolder
   1.352 +   *          the folder where the file resides
   1.353 +   * @param   aLeafName
   1.354 +   *          the string name of the file (may be empty if no name is known,
   1.355 +   *          in which case a name will be chosen)
   1.356 +   * @param   aFileExt
   1.357 +   *          the extension of the file, if one is known; this will be ignored
   1.358 +   *          if aLeafName is non-empty
   1.359 +   * @return  nsILocalFile
   1.360 +   *          the created file
   1.361 +   * @throw   an error such as permission doesn't allow creation of
   1.362 +   *          file, etc.
   1.363 +   */
   1.364 +  validateLeafName: function (aLocalFolder, aLeafName, aFileExt)
   1.365 +  {
   1.366 +    if (!(aLocalFolder && isUsableDirectory(aLocalFolder))) {
   1.367 +      throw new Components.Exception("Destination directory non-existing or permission error",
   1.368 +                                     Components.results.NS_ERROR_FILE_ACCESS_DENIED);
   1.369 +    }
   1.370 +    // Remove any leading periods, since we don't want to save hidden files
   1.371 +    // automatically.
   1.372 +    aLeafName = aLeafName.replace(/^\.+/, "");
   1.373 +
   1.374 +    if (aLeafName == "")
   1.375 +      aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
   1.376 +    aLocalFolder.append(aLeafName);
   1.377 +
   1.378 +    // The following assignment can throw an exception, but
   1.379 +    // is now caught properly in the caller of validateLeafName.
   1.380 +    var createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder);
   1.381 +
   1.382 +#ifdef XP_WIN
   1.383 +    let ext;
   1.384 +    try {
   1.385 +      // We can fail here if there's no primary extension set
   1.386 +      ext = "." + this.mLauncher.MIMEInfo.primaryExtension;
   1.387 +    } catch (e) { }
   1.388 +
   1.389 +    // Append a file extension if it's an executable that doesn't have one
   1.390 +    // but make sure we actually have an extension to add
   1.391 +    let leaf = createdFile.leafName;
   1.392 +    if (ext && leaf.slice(-ext.length) != ext && createdFile.isExecutable()) {
   1.393 +      createdFile.remove(false);
   1.394 +      aLocalFolder.leafName = leaf + ext;
   1.395 +      createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder);
   1.396 +    }
   1.397 +#endif
   1.398 +
   1.399 +    return createdFile;
   1.400 +  },
   1.401 +
   1.402 +  // ---------- implementation methods ----------
   1.403 +
   1.404 +  // initDialog:  Fill various dialog fields with initial content.
   1.405 +  initDialog : function() {
   1.406 +    // Put file name in window title.
   1.407 +    var suggestedFileName = this.mLauncher.suggestedFileName;
   1.408 +
   1.409 +    // Some URIs do not implement nsIURL, so we can't just QI.
   1.410 +    var url = this.mLauncher.source;
   1.411 +    if (url instanceof Components.interfaces.nsINestedURI)
   1.412 +      url = url.innermostURI;
   1.413 +
   1.414 +    var fname = "";
   1.415 +    var iconPath = "goat";
   1.416 +    this.mSourcePath = url.prePath;
   1.417 +    if (url instanceof Components.interfaces.nsIURL) {
   1.418 +      // A url, use file name from it.
   1.419 +      fname = iconPath = url.fileName;
   1.420 +      this.mSourcePath += url.directory;
   1.421 +    } else {
   1.422 +      // A generic uri, use path.
   1.423 +      fname = url.path;
   1.424 +      this.mSourcePath += url.path;
   1.425 +    }
   1.426 +
   1.427 +    if (suggestedFileName)
   1.428 +      fname = iconPath = suggestedFileName;
   1.429 +
   1.430 +    var displayName = fname.replace(/ +/g, " ");
   1.431 +
   1.432 +    this.mTitle = this.dialogElement("strings").getFormattedString("title", [displayName]);
   1.433 +    this.mDialog.document.title = this.mTitle;
   1.434 +
   1.435 +    // Put content type, filename and location into intro.
   1.436 +    this.initIntro(url, fname, displayName);
   1.437 +
   1.438 +    var iconString = "moz-icon://" + iconPath + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
   1.439 +    this.dialogElement("contentTypeImage").setAttribute("src", iconString);
   1.440 +
   1.441 +    // if always-save and is-executable and no-handler
   1.442 +    // then set up simple ui
   1.443 +    var mimeType = this.mLauncher.MIMEInfo.MIMEType;
   1.444 +    var shouldntRememberChoice = (mimeType == "application/octet-stream" ||
   1.445 +                                  mimeType == "application/x-msdownload" ||
   1.446 +                                  this.mLauncher.targetFileIsExecutable);
   1.447 +    if (shouldntRememberChoice && !this.openWithDefaultOK()) {
   1.448 +      // hide featured choice
   1.449 +      this.dialogElement("normalBox").collapsed = true;
   1.450 +      // show basic choice
   1.451 +      this.dialogElement("basicBox").collapsed = false;
   1.452 +      // change button labels and icons; use "save" icon for the accept
   1.453 +      // button since it's the only action possible
   1.454 +      let acceptButton = this.mDialog.document.documentElement
   1.455 +                                              .getButton("accept");
   1.456 +      acceptButton.label = this.dialogElement("strings")
   1.457 +                               .getString("unknownAccept.label");
   1.458 +      acceptButton.setAttribute("icon", "save");
   1.459 +      this.mDialog.document.documentElement.getButton("cancel").label = this.dialogElement("strings").getString("unknownCancel.label");
   1.460 +      // hide other handler
   1.461 +      this.dialogElement("openHandler").collapsed = true;
   1.462 +      // set save as the selected option
   1.463 +      this.dialogElement("mode").selectedItem = this.dialogElement("save");
   1.464 +    }
   1.465 +    else {
   1.466 +      this.initAppAndSaveToDiskValues();
   1.467 +
   1.468 +      // Initialize "always ask me" box. This should always be disabled
   1.469 +      // and set to true for the ambiguous type application/octet-stream.
   1.470 +      // We don't also check for application/x-msdownload here since we
   1.471 +      // want users to be able to autodownload .exe files.
   1.472 +      var rememberChoice = this.dialogElement("rememberChoice");
   1.473 +
   1.474 +#if 0
   1.475 +      // Just because we have a content-type of application/octet-stream
   1.476 +      // here doesn't actually mean that the content is of that type. Many
   1.477 +      // servers default to sending text/plain for file types they don't know
   1.478 +      // about. To account for this, the uriloader does some checking to see
   1.479 +      // if a file sent as text/plain contains binary characters, and if so (*)
   1.480 +      // it morphs the content-type into application/octet-stream so that
   1.481 +      // the file can be properly handled. Since this is not generic binary
   1.482 +      // data, rather, a data format that the system probably knows about,
   1.483 +      // we don't want to use the content-type provided by this dialog's
   1.484 +      // opener, as that's the generic application/octet-stream that the
   1.485 +      // uriloader has passed, rather we want to ask the MIME Service.
   1.486 +      // This is so we don't needlessly disable the "autohandle" checkbox.
   1.487 +
   1.488 +      // commented out to close the opening brace in the if statement.
   1.489 +      // var mimeService = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService);
   1.490 +      // var type = mimeService.getTypeFromURI(this.mLauncher.source);
   1.491 +      // this.realMIMEInfo = mimeService.getFromTypeAndExtension(type, "");
   1.492 +
   1.493 +      // if (type == "application/octet-stream") {
   1.494 +#endif
   1.495 +      if (shouldntRememberChoice) {
   1.496 +        rememberChoice.checked = false;
   1.497 +        rememberChoice.disabled = true;
   1.498 +      }
   1.499 +      else {
   1.500 +        rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling &&
   1.501 +                                 this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.handleInternally;
   1.502 +      }
   1.503 +      this.toggleRememberChoice(rememberChoice);
   1.504 +
   1.505 +      // XXXben - menulist won't init properly, hack.
   1.506 +      var openHandler = this.dialogElement("openHandler");
   1.507 +      openHandler.parentNode.removeChild(openHandler);
   1.508 +      var openHandlerBox = this.dialogElement("openHandlerBox");
   1.509 +      openHandlerBox.appendChild(openHandler);
   1.510 +    }
   1.511 +
   1.512 +    this.mDialog.setTimeout("dialog.postShowCallback()", 0);
   1.513 +
   1.514 +    let acceptDelay = Services.prefs.getIntPref("security.dialog_enable_delay");
   1.515 +    this.mDialog.document.documentElement.getButton("accept").disabled = true;
   1.516 +    this._showTimer = Components.classes["@mozilla.org/timer;1"]
   1.517 +                                .createInstance(nsITimer);
   1.518 +    this._showTimer.initWithCallback(this, acceptDelay, nsITimer.TYPE_ONE_SHOT);
   1.519 +  },
   1.520 +
   1.521 +  notify: function (aTimer) {
   1.522 +    if (aTimer == this._showTimer) {
   1.523 +      if (!this.mDialog) {
   1.524 +        this.reallyShow();
   1.525 +      } else {
   1.526 +        // The user may have already canceled the dialog.
   1.527 +        try {
   1.528 +          if (!this._blurred) {
   1.529 +            this.mDialog.document.documentElement.getButton("accept").disabled = false;
   1.530 +          }
   1.531 +        } catch (ex) {}
   1.532 +        this._delayExpired = true;
   1.533 +      }
   1.534 +      // The timer won't release us, so we have to release it.
   1.535 +      this._showTimer = null;
   1.536 +    }
   1.537 +    else if (aTimer == this._saveToDiskTimer) {
   1.538 +      // Since saveToDisk may open a file picker and therefore block this routine,
   1.539 +      // we should only call it once the dialog is closed.
   1.540 +      this.mLauncher.saveToDisk(null, false);
   1.541 +      this._saveToDiskTimer = null;
   1.542 +    }
   1.543 +  },
   1.544 +
   1.545 +  postShowCallback: function () {
   1.546 +    this.mDialog.sizeToContent();
   1.547 +
   1.548 +    // Set initial focus
   1.549 +    this.dialogElement("mode").focus();
   1.550 +  },
   1.551 +
   1.552 +  // initIntro:
   1.553 +  initIntro: function(url, filename, displayname) {
   1.554 +    this.dialogElement( "location" ).value = displayname;
   1.555 +    this.dialogElement( "location" ).setAttribute("realname", filename);
   1.556 +    this.dialogElement( "location" ).setAttribute("tooltiptext", displayname);
   1.557 +
   1.558 +    // if mSourcePath is a local file, then let's use the pretty path name
   1.559 +    // instead of an ugly url...
   1.560 +    var pathString;
   1.561 +    if (url instanceof Components.interfaces.nsIFileURL) {
   1.562 +      try {
   1.563 +        // Getting .file might throw, or .parent could be null
   1.564 +        pathString = url.file.parent.path;
   1.565 +      } catch (ex) {}
   1.566 +    }
   1.567 +
   1.568 +    if (!pathString) {
   1.569 +      // wasn't a fileURL
   1.570 +      var tmpurl = url.clone(); // don't want to change the real url
   1.571 +      try {
   1.572 +        tmpurl.userPass = "";
   1.573 +      } catch (ex) {}
   1.574 +      pathString = tmpurl.prePath;
   1.575 +    }
   1.576 +
   1.577 +    // Set the location text, which is separate from the intro text so it can be cropped
   1.578 +    var location = this.dialogElement( "source" );
   1.579 +    location.value = pathString;
   1.580 +    location.setAttribute("tooltiptext", this.mSourcePath);
   1.581 +
   1.582 +    // Show the type of file.
   1.583 +    var type = this.dialogElement("type");
   1.584 +    var mimeInfo = this.mLauncher.MIMEInfo;
   1.585 +
   1.586 +    // 1. Try to use the pretty description of the type, if one is available.
   1.587 +    var typeString = mimeInfo.description;
   1.588 +
   1.589 +    if (typeString == "") {
   1.590 +      // 2. If there is none, use the extension to identify the file, e.g. "ZIP file"
   1.591 +      var primaryExtension = "";
   1.592 +      try {
   1.593 +        primaryExtension = mimeInfo.primaryExtension;
   1.594 +      }
   1.595 +      catch (ex) {
   1.596 +      }
   1.597 +      if (primaryExtension != "")
   1.598 +        typeString = this.dialogElement("strings").getFormattedString("fileType", [primaryExtension.toUpperCase()]);
   1.599 +      // 3. If we can't even do that, just give up and show the MIME type.
   1.600 +      else
   1.601 +        typeString = mimeInfo.MIMEType;
   1.602 +    }
   1.603 +    // When the length is unknown, contentLength would be -1
   1.604 +    if (this.mLauncher.contentLength >= 0) {
   1.605 +      let [size, unit] = DownloadUtils.
   1.606 +                         convertByteUnits(this.mLauncher.contentLength);
   1.607 +      type.value = this.dialogElement("strings")
   1.608 +                       .getFormattedString("orderedFileSizeWithType", 
   1.609 +                                           [typeString, size, unit]);
   1.610 +    }
   1.611 +    else {
   1.612 +      type.value = typeString;
   1.613 +    }
   1.614 +  },
   1.615 +
   1.616 +  _blurred: false,
   1.617 +  _delayExpired: false,
   1.618 +  onBlur: function(aEvent) {
   1.619 +    this._blurred = true;
   1.620 +    this.mDialog.document.documentElement.getButton("accept").disabled = true;
   1.621 +  },
   1.622 +
   1.623 +  onFocus: function(aEvent) {
   1.624 +    this._blurred = false;
   1.625 +    if (this._delayExpired) {
   1.626 +      var script = "document.documentElement.getButton('accept').disabled = false";
   1.627 +      this.mDialog.setTimeout(script, 250);
   1.628 +    }
   1.629 +  },
   1.630 +
   1.631 +  // Returns true if opening the default application makes sense.
   1.632 +  openWithDefaultOK: function() {
   1.633 +    // The checking is different on Windows...
   1.634 +#ifdef XP_WIN
   1.635 +    // Windows presents some special cases.
   1.636 +    // We need to prevent use of "system default" when the file is
   1.637 +    // executable (so the user doesn't launch nasty programs downloaded
   1.638 +    // from the web), and, enable use of "system default" if it isn't
   1.639 +    // executable (because we will prompt the user for the default app
   1.640 +    // in that case).
   1.641 +
   1.642 +    //  Default is Ok if the file isn't executable (and vice-versa).
   1.643 +    return !this.mLauncher.targetFileIsExecutable;
   1.644 +#else
   1.645 +    // On other platforms, default is Ok if there is a default app.
   1.646 +    // Note that nsIMIMEInfo providers need to ensure that this holds true
   1.647 +    // on each platform.
   1.648 +    return this.mLauncher.MIMEInfo.hasDefaultHandler;
   1.649 +#endif
   1.650 +  },
   1.651 +
   1.652 +  // Set "default" application description field.
   1.653 +  initDefaultApp: function() {
   1.654 +    // Use description, if we can get one.
   1.655 +    var desc = this.mLauncher.MIMEInfo.defaultDescription;
   1.656 +    if (desc) {
   1.657 +      var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]);
   1.658 +      this.dialogElement("defaultHandler").label = defaultApp;
   1.659 +    }
   1.660 +    else {
   1.661 +      this.dialogElement("modeDeck").setAttribute("selectedIndex", "1");
   1.662 +      // Hide the default handler item too, in case the user picks a
   1.663 +      // custom handler at a later date which triggers the menulist to show.
   1.664 +      this.dialogElement("defaultHandler").hidden = true;
   1.665 +    }
   1.666 +  },
   1.667 +
   1.668 +  // getPath:
   1.669 +  getPath: function (aFile) {
   1.670 +#ifdef XP_MACOSX
   1.671 +    return aFile.leafName || aFile.path;
   1.672 +#else
   1.673 +    return aFile.path;
   1.674 +#endif
   1.675 +  },
   1.676 +
   1.677 +  // initAppAndSaveToDiskValues:
   1.678 +  initAppAndSaveToDiskValues: function() {
   1.679 +    var modeGroup = this.dialogElement("mode");
   1.680 +
   1.681 +    // We don't let users open .exe files or random binary data directly
   1.682 +    // from the browser at the moment because of security concerns.
   1.683 +    var openWithDefaultOK = this.openWithDefaultOK();
   1.684 +    var mimeType = this.mLauncher.MIMEInfo.MIMEType;
   1.685 +    if (this.mLauncher.targetFileIsExecutable || (
   1.686 +      (mimeType == "application/octet-stream" ||
   1.687 +       mimeType == "application/x-msdownload") &&
   1.688 +        !openWithDefaultOK)) {
   1.689 +      this.dialogElement("open").disabled = true;
   1.690 +      var openHandler = this.dialogElement("openHandler");
   1.691 +      openHandler.disabled = true;
   1.692 +      openHandler.selectedItem = null;
   1.693 +      modeGroup.selectedItem = this.dialogElement("save");
   1.694 +      return;
   1.695 +    }
   1.696 +
   1.697 +    // Fill in helper app info, if there is any.
   1.698 +    try {
   1.699 +      this.chosenApp =
   1.700 +        this.mLauncher.MIMEInfo.preferredApplicationHandler
   1.701 +                               .QueryInterface(Components.interfaces.nsILocalHandlerApp);
   1.702 +    } catch (e) {
   1.703 +      this.chosenApp = null;
   1.704 +    }
   1.705 +    // Initialize "default application" field.
   1.706 +    this.initDefaultApp();
   1.707 +
   1.708 +    var otherHandler = this.dialogElement("otherHandler");
   1.709 +
   1.710 +    // Fill application name textbox.
   1.711 +    if (this.chosenApp && this.chosenApp.executable &&
   1.712 +        this.chosenApp.executable.path) {
   1.713 +      otherHandler.setAttribute("path",
   1.714 +                                this.getPath(this.chosenApp.executable));
   1.715 +
   1.716 +      otherHandler.label = this.getFileDisplayName(this.chosenApp.executable);
   1.717 +      otherHandler.hidden = false;
   1.718 +    }
   1.719 +
   1.720 +    var useDefault = this.dialogElement("useSystemDefault");
   1.721 +    var openHandler = this.dialogElement("openHandler");
   1.722 +    openHandler.selectedIndex = 0;
   1.723 +
   1.724 +    if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
   1.725 +      // Open (using system default).
   1.726 +      modeGroup.selectedItem = this.dialogElement("open");
   1.727 +    } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
   1.728 +      // Open with given helper app.
   1.729 +      modeGroup.selectedItem = this.dialogElement("open");
   1.730 +      openHandler.selectedIndex = 1;
   1.731 +    } else {
   1.732 +      // Save to disk.
   1.733 +      modeGroup.selectedItem = this.dialogElement("save");
   1.734 +    }
   1.735 +
   1.736 +    // If we don't have a "default app" then disable that choice.
   1.737 +    if (!openWithDefaultOK) {
   1.738 +      var useDefault = this.dialogElement("defaultHandler");
   1.739 +      var isSelected = useDefault.selected;
   1.740 +
   1.741 +      // Disable that choice.
   1.742 +      useDefault.hidden = true;
   1.743 +      // If that's the default, then switch to "save to disk."
   1.744 +      if (isSelected) {
   1.745 +        openHandler.selectedIndex = 1;
   1.746 +        modeGroup.selectedItem = this.dialogElement("save");
   1.747 +      }
   1.748 +    }
   1.749 +
   1.750 +    otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = false;
   1.751 +    this.updateOKButton();
   1.752 +  },
   1.753 +
   1.754 +  // Returns the user-selected application
   1.755 +  helperAppChoice: function() {
   1.756 +    return this.chosenApp;
   1.757 +  },
   1.758 +
   1.759 +  get saveToDisk() {
   1.760 +    return this.dialogElement("save").selected;
   1.761 +  },
   1.762 +
   1.763 +  get useOtherHandler() {
   1.764 +    return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1;
   1.765 +  },
   1.766 +
   1.767 +  get useSystemDefault() {
   1.768 +    return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0;
   1.769 +  },
   1.770 +
   1.771 +  toggleRememberChoice: function (aCheckbox) {
   1.772 +    this.dialogElement("settingsChange").hidden = !aCheckbox.checked;
   1.773 +    this.mDialog.sizeToContent();
   1.774 +  },
   1.775 +
   1.776 +  openHandlerCommand: function () {
   1.777 +    var openHandler = this.dialogElement("openHandler");
   1.778 +    if (openHandler.selectedItem.id == "choose")
   1.779 +      this.chooseApp();
   1.780 +    else
   1.781 +      openHandler.setAttribute("lastSelectedItemID", openHandler.selectedItem.id);
   1.782 +  },
   1.783 +
   1.784 +  updateOKButton: function() {
   1.785 +    var ok = false;
   1.786 +    if (this.dialogElement("save").selected) {
   1.787 +      // This is always OK.
   1.788 +      ok = true;
   1.789 +    }
   1.790 +    else if (this.dialogElement("open").selected) {
   1.791 +      switch (this.dialogElement("openHandler").selectedIndex) {
   1.792 +      case 0:
   1.793 +        // No app need be specified in this case.
   1.794 +        ok = true;
   1.795 +        break;
   1.796 +      case 1:
   1.797 +        // only enable the OK button if we have a default app to use or if
   1.798 +        // the user chose an app....
   1.799 +        ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path")); 
   1.800 +        break;
   1.801 +      }
   1.802 +    }
   1.803 +
   1.804 +    // Enable Ok button if ok to press.
   1.805 +    this.mDialog.document.documentElement.getButton("accept").disabled = !ok;
   1.806 +  },
   1.807 +
   1.808 +  // Returns true iff the user-specified helper app has been modified.
   1.809 +  appChanged: function() {
   1.810 +    return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
   1.811 +  },
   1.812 +
   1.813 +  updateMIMEInfo: function() {
   1.814 +    // Don't update mime type preferences when the preferred action is set to
   1.815 +    // the internal handler -- this dialog is the result of the handler fallback
   1.816 +    // (e.g. Content-Disposition was set as attachment)
   1.817 +    var discardUpdate = this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.handleInternally &&
   1.818 +                        !this.dialogElement("rememberChoice").checked;
   1.819 +
   1.820 +    var needUpdate = false;
   1.821 +    // If current selection differs from what's in the mime info object,
   1.822 +    // then we need to update.
   1.823 +    if (this.saveToDisk) {
   1.824 +      needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
   1.825 +      if (needUpdate)
   1.826 +        this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
   1.827 +    }
   1.828 +    else if (this.useSystemDefault) {
   1.829 +      needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
   1.830 +      if (needUpdate)
   1.831 +        this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
   1.832 +    }
   1.833 +    else {
   1.834 +      // For "open with", we need to check both preferred action and whether the user chose
   1.835 +      // a new app.
   1.836 +      needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
   1.837 +      if (needUpdate) {
   1.838 +        this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
   1.839 +        // App may have changed - Update application
   1.840 +        var app = this.helperAppChoice();
   1.841 +        this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
   1.842 +      }
   1.843 +    }
   1.844 +    // We will also need to update if the "always ask" flag has changed.
   1.845 +    needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked);
   1.846 +
   1.847 +    // One last special case: If the input "always ask" flag was false, then we always
   1.848 +    // update.  In that case we are displaying the helper app dialog for the first
   1.849 +    // time for this mime type and we need to store the user's action in the mimeTypes.rdf
   1.850 +    // data source (whether that action has changed or not; if it didn't change, then we need
   1.851 +    // to store the "always ask" flag so the helper app dialog will or won't display
   1.852 +    // next time, per the user's selection).
   1.853 +    needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
   1.854 +
   1.855 +    // Make sure mime info has updated setting for the "always ask" flag.
   1.856 +    this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked;
   1.857 +
   1.858 +    return needUpdate && !discardUpdate;
   1.859 +  },
   1.860 +
   1.861 +  // See if the user changed things, and if so, update the
   1.862 +  // mimeTypes.rdf entry for this mime type.
   1.863 +  updateHelperAppPref: function() {
   1.864 +    var ha = new this.mDialog.HelperApps();
   1.865 +    ha.updateTypeInfo(this.mLauncher.MIMEInfo);
   1.866 +    ha.destroy();
   1.867 +  },
   1.868 +
   1.869 +  // onOK:
   1.870 +  onOK: function() {
   1.871 +    // Verify typed app path, if necessary.
   1.872 +    if (this.useOtherHandler) {
   1.873 +      var helperApp = this.helperAppChoice();
   1.874 +      if (!helperApp || !helperApp.executable ||
   1.875 +          !helperApp.executable.exists()) {
   1.876 +        // Show alert and try again.
   1.877 +        var bundle = this.dialogElement("strings");
   1.878 +        var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").getAttribute("path")]);
   1.879 +        Services.prompt.alert(this.mDialog, bundle.getString("badApp.title"), msg);
   1.880 +
   1.881 +        // Disable the OK button.
   1.882 +        this.mDialog.document.documentElement.getButton("accept").disabled = true;
   1.883 +        this.dialogElement("mode").focus();
   1.884 +
   1.885 +        // Clear chosen application.
   1.886 +        this.chosenApp = null;
   1.887 +
   1.888 +        // Leave dialog up.
   1.889 +        return false;
   1.890 +      }
   1.891 +    }
   1.892 +
   1.893 +    // Remove our web progress listener (a progress dialog will be
   1.894 +    // taking over).
   1.895 +    this.mLauncher.setWebProgressListener(null);
   1.896 +
   1.897 +    // saveToDisk and launchWithApplication can return errors in
   1.898 +    // certain circumstances (e.g. The user clicks cancel in the
   1.899 +    // "Save to Disk" dialog. In those cases, we don't want to
   1.900 +    // update the helper application preferences in the RDF file.
   1.901 +    try {
   1.902 +      var needUpdate = this.updateMIMEInfo();
   1.903 +
   1.904 +      if (this.dialogElement("save").selected) {
   1.905 +        // If we're using a default download location, create a path
   1.906 +        // for the file to be saved to to pass to |saveToDisk| - otherwise
   1.907 +        // we must ask the user to pick a save name.
   1.908 +
   1.909 +#if 0
   1.910 +        var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
   1.911 +        var targetFile = null;
   1.912 +        try {
   1.913 +          targetFile = prefs.getComplexValue("browser.download.defaultFolder",
   1.914 +                                             Components.interfaces.nsILocalFile);
   1.915 +          var leafName = this.dialogElement("location").getAttribute("realname");
   1.916 +          // Ensure that we don't overwrite any existing files here.
   1.917 +          targetFile = this.validateLeafName(targetFile, leafName, null);
   1.918 +        }
   1.919 +        catch(e) { }
   1.920 +
   1.921 +        this.mLauncher.saveToDisk(targetFile, false);
   1.922 +#endif
   1.923 +
   1.924 +        // see @notify
   1.925 +        // we cannot use opener's setTimeout, see bug 420405
   1.926 +        this._saveToDiskTimer = Components.classes["@mozilla.org/timer;1"]
   1.927 +                                          .createInstance(nsITimer);
   1.928 +        this._saveToDiskTimer.initWithCallback(this, 0,
   1.929 +                                               nsITimer.TYPE_ONE_SHOT);
   1.930 +      }
   1.931 +      else
   1.932 +        this.mLauncher.launchWithApplication(null, false);
   1.933 +
   1.934 +      // Update user pref for this mime type (if necessary). We do not
   1.935 +      // store anything in the mime type preferences for the ambiguous
   1.936 +      // type application/octet-stream. We do NOT do this for
   1.937 +      // application/x-msdownload since we want users to be able to
   1.938 +      // autodownload these to disk.
   1.939 +      if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream")
   1.940 +        this.updateHelperAppPref();
   1.941 +    } catch(e) { }
   1.942 +
   1.943 +    // Unhook dialog from this object.
   1.944 +    this.mDialog.dialog = null;
   1.945 +
   1.946 +    // Close up dialog by returning true.
   1.947 +    return true;
   1.948 +  },
   1.949 +
   1.950 +  // onCancel:
   1.951 +  onCancel: function() {
   1.952 +    // Remove our web progress listener.
   1.953 +    this.mLauncher.setWebProgressListener(null);
   1.954 +
   1.955 +    // Cancel app launcher.
   1.956 +    try {
   1.957 +      this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED);
   1.958 +    } catch(exception) {
   1.959 +    }
   1.960 +
   1.961 +    // Unhook dialog from this object.
   1.962 +    this.mDialog.dialog = null;
   1.963 +
   1.964 +    // Close up dialog by returning true.
   1.965 +    return true;
   1.966 +  },
   1.967 +
   1.968 +  // dialogElement:  Convenience.
   1.969 +  dialogElement: function(id) {
   1.970 +    return this.mDialog.document.getElementById(id);
   1.971 +  },
   1.972 +
   1.973 +  // Retrieve the pretty description from the file
   1.974 +  getFileDisplayName: function getFileDisplayName(file)
   1.975 +  {
   1.976 +#ifdef XP_WIN
   1.977 +    if (file instanceof Components.interfaces.nsILocalFileWin) {
   1.978 +      try {
   1.979 +        return file.getVersionInfoField("FileDescription");
   1.980 +      } catch (e) {}
   1.981 +    }
   1.982 +#endif
   1.983 +#ifdef XP_MACOSX
   1.984 +    if (file instanceof Components.interfaces.nsILocalFileMac) {
   1.985 +      try {
   1.986 +        return file.bundleDisplayName;
   1.987 +      } catch (e) {}
   1.988 +    }
   1.989 +#endif
   1.990 +    return file.leafName;
   1.991 +  },
   1.992 +
   1.993 +  // chooseApp:  Open file picker and prompt user for application.
   1.994 +  chooseApp: function() {
   1.995 +#ifdef XP_WIN
   1.996 +    // Protect against the lack of an extension
   1.997 +    var fileExtension = "";
   1.998 +    try {
   1.999 +      fileExtension = this.mLauncher.MIMEInfo.primaryExtension;
  1.1000 +    } catch(ex) {
  1.1001 +    }
  1.1002 +
  1.1003 +    // Try to use the pretty description of the type, if one is available.
  1.1004 +    var typeString = this.mLauncher.MIMEInfo.description;
  1.1005 +
  1.1006 +    if (!typeString) {
  1.1007 +      // If there is none, use the extension to
  1.1008 +      // identify the file, e.g. "ZIP file"
  1.1009 +      if (fileExtension) {
  1.1010 +        typeString =
  1.1011 +          this.dialogElement("strings").
  1.1012 +          getFormattedString("fileType", [fileExtension.toUpperCase()]);
  1.1013 +      } else {
  1.1014 +        // If we can't even do that, just give up and show the MIME type.
  1.1015 +        typeString = this.mLauncher.MIMEInfo.MIMEType;
  1.1016 +      }
  1.1017 +    }
  1.1018 +
  1.1019 +    var params = {};
  1.1020 +    params.title =
  1.1021 +      this.dialogElement("strings").getString("chooseAppFilePickerTitle");
  1.1022 +    params.description = typeString;
  1.1023 +    params.filename    = this.mLauncher.suggestedFileName;
  1.1024 +    params.mimeInfo    = this.mLauncher.MIMEInfo;
  1.1025 +    params.handlerApp  = null;
  1.1026 +
  1.1027 +    this.mDialog.openDialog("chrome://global/content/appPicker.xul", null,
  1.1028 +                            "chrome,modal,centerscreen,titlebar,dialog=yes",
  1.1029 +                            params);
  1.1030 +
  1.1031 +    if (params.handlerApp &&
  1.1032 +        params.handlerApp.executable &&
  1.1033 +        params.handlerApp.executable.isFile()) {
  1.1034 +      // Remember the file they chose to run.
  1.1035 +      this.chosenApp = params.handlerApp;
  1.1036 +
  1.1037 +#else
  1.1038 +    var nsIFilePicker = Components.interfaces.nsIFilePicker;
  1.1039 +    var fp = Components.classes["@mozilla.org/filepicker;1"]
  1.1040 +                       .createInstance(nsIFilePicker);
  1.1041 +    fp.init(this.mDialog,
  1.1042 +            this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
  1.1043 +            nsIFilePicker.modeOpen);
  1.1044 +
  1.1045 +    fp.appendFilters(nsIFilePicker.filterApps);
  1.1046 +
  1.1047 +    if (fp.show() == nsIFilePicker.returnOK && fp.file) {
  1.1048 +      // Remember the file they chose to run.
  1.1049 +      var localHandlerApp =
  1.1050 +        Components.classes["@mozilla.org/uriloader/local-handler-app;1"].
  1.1051 +                   createInstance(Components.interfaces.nsILocalHandlerApp);
  1.1052 +      localHandlerApp.executable = fp.file;
  1.1053 +      this.chosenApp = localHandlerApp;
  1.1054 +#endif
  1.1055 +
  1.1056 +      // Show the "handler" menulist since we have a (user-specified)
  1.1057 +      // application now.
  1.1058 +      this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");
  1.1059 +
  1.1060 +      // Update dialog.
  1.1061 +      var otherHandler = this.dialogElement("otherHandler");
  1.1062 +      otherHandler.removeAttribute("hidden");
  1.1063 +      otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable));
  1.1064 +      otherHandler.label = this.getFileDisplayName(this.chosenApp.executable);
  1.1065 +      this.dialogElement("openHandler").selectedIndex = 1;
  1.1066 +      this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler");
  1.1067 +
  1.1068 +      this.dialogElement("mode").selectedItem = this.dialogElement("open");
  1.1069 +    }
  1.1070 +    else {
  1.1071 +      var openHandler = this.dialogElement("openHandler");
  1.1072 +      var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
  1.1073 +      if (!lastSelectedID)
  1.1074 +        lastSelectedID = "defaultHandler";
  1.1075 +      openHandler.selectedItem = this.dialogElement(lastSelectedID);
  1.1076 +    }
  1.1077 +  },
  1.1078 +
  1.1079 +  // Turn this on to get debugging messages.
  1.1080 +  debug: false,
  1.1081 +
  1.1082 +  // Dump text (if debug is on).
  1.1083 +  dump: function( text ) {
  1.1084 +    if ( this.debug ) {
  1.1085 +      dump( text );
  1.1086 +    }
  1.1087 +  },
  1.1088 +
  1.1089 +  // dumpObj:
  1.1090 +  dumpObj: function( spec ) {
  1.1091 +    var val = "<undefined>";
  1.1092 +    try {
  1.1093 +      val = eval( "this."+spec ).toString();
  1.1094 +    } catch( exception ) {
  1.1095 +    }
  1.1096 +    this.dump( spec + "=" + val + "\n" );
  1.1097 +  },
  1.1098 +
  1.1099 +  // dumpObjectProperties
  1.1100 +  dumpObjectProperties: function( desc, obj ) {
  1.1101 +    for( prop in obj ) {
  1.1102 +      this.dump( desc + "." + prop + "=" );
  1.1103 +      var val = "<undefined>";
  1.1104 +      try {
  1.1105 +        val = obj[ prop ];
  1.1106 +      } catch ( exception ) {
  1.1107 +      }
  1.1108 +      this.dump( val + "\n" );
  1.1109 +    }
  1.1110 +  }
  1.1111 +}
  1.1112 +
  1.1113 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsUnknownContentTypeDialog]);

mercurial