toolkit/mozapps/downloads/nsHelperAppDlg.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2; js-indent-level: 2; -*- */
michael@0 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
michael@0 3 /*
michael@0 4 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 5 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 7 */
michael@0 8
michael@0 9 Components.utils.import("resource://gre/modules/Services.jsm");
michael@0 10
michael@0 11 ///////////////////////////////////////////////////////////////////////////////
michael@0 12 //// Helper Functions
michael@0 13
michael@0 14 /**
michael@0 15 * Determines if a given directory is able to be used to download to.
michael@0 16 *
michael@0 17 * @param aDirectory
michael@0 18 * The directory to check.
michael@0 19 * @return true if we can use the directory, false otherwise.
michael@0 20 */
michael@0 21 function isUsableDirectory(aDirectory)
michael@0 22 {
michael@0 23 return aDirectory.exists() && aDirectory.isDirectory() &&
michael@0 24 aDirectory.isWritable();
michael@0 25 }
michael@0 26
michael@0 27 // Web progress listener so we can detect errors while mLauncher is
michael@0 28 // streaming the data to a temporary file.
michael@0 29 function nsUnknownContentTypeDialogProgressListener(aHelperAppDialog) {
michael@0 30 this.helperAppDlg = aHelperAppDialog;
michael@0 31 }
michael@0 32
michael@0 33 nsUnknownContentTypeDialogProgressListener.prototype = {
michael@0 34 // nsIWebProgressListener methods.
michael@0 35 // Look for error notifications and display alert to user.
michael@0 36 onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
michael@0 37 if ( aStatus != Components.results.NS_OK ) {
michael@0 38 // Display error alert (using text supplied by back-end).
michael@0 39 // FIXME this.dialog is undefined?
michael@0 40 Services.prompt.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
michael@0 41 // Close the dialog.
michael@0 42 this.helperAppDlg.onCancel();
michael@0 43 if ( this.helperAppDlg.mDialog ) {
michael@0 44 this.helperAppDlg.mDialog.close();
michael@0 45 }
michael@0 46 }
michael@0 47 },
michael@0 48
michael@0 49 // Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, and onRefreshAttempted notifications.
michael@0 50 onProgressChange: function( aWebProgress,
michael@0 51 aRequest,
michael@0 52 aCurSelfProgress,
michael@0 53 aMaxSelfProgress,
michael@0 54 aCurTotalProgress,
michael@0 55 aMaxTotalProgress ) {
michael@0 56 },
michael@0 57
michael@0 58 onProgressChange64: function( aWebProgress,
michael@0 59 aRequest,
michael@0 60 aCurSelfProgress,
michael@0 61 aMaxSelfProgress,
michael@0 62 aCurTotalProgress,
michael@0 63 aMaxTotalProgress ) {
michael@0 64 },
michael@0 65
michael@0 66
michael@0 67
michael@0 68 onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
michael@0 69 },
michael@0 70
michael@0 71 onLocationChange: function( aWebProgress, aRequest, aLocation, aFlags ) {
michael@0 72 },
michael@0 73
michael@0 74 onSecurityChange: function( aWebProgress, aRequest, state ) {
michael@0 75 },
michael@0 76
michael@0 77 onRefreshAttempted: function( aWebProgress, aURI, aDelay, aSameURI ) {
michael@0 78 return true;
michael@0 79 }
michael@0 80 };
michael@0 81
michael@0 82 ///////////////////////////////////////////////////////////////////////////////
michael@0 83 //// nsUnknownContentTypeDialog
michael@0 84
michael@0 85 /* This file implements the nsIHelperAppLauncherDialog interface.
michael@0 86 *
michael@0 87 * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog,
michael@0 88 * comprised of:
michael@0 89 * - a JS constructor function
michael@0 90 * - a prototype providing all the interface methods and implementation stuff
michael@0 91 *
michael@0 92 * In addition, this file implements an nsIModule object that registers the
michael@0 93 * nsUnknownContentTypeDialog component.
michael@0 94 */
michael@0 95
michael@0 96 const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
michael@0 97 const nsITimer = Components.interfaces.nsITimer;
michael@0 98
michael@0 99 let downloadModule = {};
michael@0 100 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 101 Components.utils.import("resource://gre/modules/DownloadLastDir.jsm", downloadModule);
michael@0 102 Components.utils.import("resource://gre/modules/DownloadPaths.jsm");
michael@0 103 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
michael@0 104 Components.utils.import("resource://gre/modules/Downloads.jsm");
michael@0 105 Components.utils.import("resource://gre/modules/FileUtils.jsm");
michael@0 106 Components.utils.import("resource://gre/modules/Task.jsm");
michael@0 107
michael@0 108 /* ctor
michael@0 109 */
michael@0 110 function nsUnknownContentTypeDialog() {
michael@0 111 // Initialize data properties.
michael@0 112 this.mLauncher = null;
michael@0 113 this.mContext = null;
michael@0 114 this.chosenApp = null;
michael@0 115 this.givenDefaultApp = false;
michael@0 116 this.updateSelf = true;
michael@0 117 this.mTitle = "";
michael@0 118 }
michael@0 119
michael@0 120 nsUnknownContentTypeDialog.prototype = {
michael@0 121 classID: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
michael@0 122
michael@0 123 nsIMIMEInfo : Components.interfaces.nsIMIMEInfo,
michael@0 124
michael@0 125 QueryInterface: function (iid) {
michael@0 126 if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
michael@0 127 !iid.equals(Components.interfaces.nsITimerCallback) &&
michael@0 128 !iid.equals(Components.interfaces.nsISupports)) {
michael@0 129 throw Components.results.NS_ERROR_NO_INTERFACE;
michael@0 130 }
michael@0 131 return this;
michael@0 132 },
michael@0 133
michael@0 134 // ---------- nsIHelperAppLauncherDialog methods ----------
michael@0 135
michael@0 136 // show: Open XUL dialog using window watcher. Since the dialog is not
michael@0 137 // modal, it needs to be a top level window and the way to open
michael@0 138 // one of those is via that route).
michael@0 139 show: function(aLauncher, aContext, aReason) {
michael@0 140 this.mLauncher = aLauncher;
michael@0 141 this.mContext = aContext;
michael@0 142
michael@0 143 const nsITimer = Components.interfaces.nsITimer;
michael@0 144 this._showTimer = Components.classes["@mozilla.org/timer;1"]
michael@0 145 .createInstance(nsITimer);
michael@0 146 this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
michael@0 147 },
michael@0 148
michael@0 149 // When opening from new tab, if tab closes while dialog is opening,
michael@0 150 // (which is a race condition on the XUL file being cached and the timer
michael@0 151 // in nsExternalHelperAppService), the dialog gets a blur and doesn't
michael@0 152 // activate the OK button. So we wait a bit before doing opening it.
michael@0 153 reallyShow: function() {
michael@0 154 try {
michael@0 155 var ir = this.mContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
michael@0 156 var dwi = ir.getInterface(Components.interfaces.nsIDOMWindow);
michael@0 157 var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
michael@0 158 .getService(Components.interfaces.nsIWindowWatcher);
michael@0 159 this.mDialog = ww.openWindow(dwi,
michael@0 160 "chrome://mozapps/content/downloads/unknownContentType.xul",
michael@0 161 null,
michael@0 162 "chrome,centerscreen,titlebar,dialog=yes,dependent",
michael@0 163 null);
michael@0 164 } catch (ex) {
michael@0 165 // The containing window may have gone away. Break reference
michael@0 166 // cycles and stop doing the download.
michael@0 167 this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED);
michael@0 168 return;
michael@0 169 }
michael@0 170
michael@0 171 // Hook this object to the dialog.
michael@0 172 this.mDialog.dialog = this;
michael@0 173
michael@0 174 // Hook up utility functions.
michael@0 175 this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey;
michael@0 176
michael@0 177 // Watch for error notifications.
michael@0 178 var progressListener = new nsUnknownContentTypeDialogProgressListener(this);
michael@0 179 this.mLauncher.setWebProgressListener(progressListener);
michael@0 180 },
michael@0 181
michael@0 182 //
michael@0 183 // displayBadPermissionAlert()
michael@0 184 //
michael@0 185 // Diplay an alert panel about the bad permission of folder/directory.
michael@0 186 //
michael@0 187 displayBadPermissionAlert: function () {
michael@0 188 let bundle =
michael@0 189 Services.strings.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
michael@0 190
michael@0 191 Services.prompt.alert(this.dialog,
michael@0 192 bundle.GetStringFromName("badPermissions.title"),
michael@0 193 bundle.GetStringFromName("badPermissions"));
michael@0 194 },
michael@0 195
michael@0 196 // promptForSaveToFile: Display file picker dialog and return selected file.
michael@0 197 // This is called by the External Helper App Service
michael@0 198 // after the ucth dialog calls |saveToDisk| with a null
michael@0 199 // target filename (no target, therefore user must pick).
michael@0 200 //
michael@0 201 // Alternatively, if the user has selected to have all
michael@0 202 // files download to a specific location, return that
michael@0 203 // location and don't ask via the dialog.
michael@0 204 //
michael@0 205 // Note - this function is called without a dialog, so it cannot access any part
michael@0 206 // of the dialog XUL as other functions on this object do.
michael@0 207
michael@0 208 promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
michael@0 209 throw new Components.Exception("Async version must be used", Components.results.NS_ERROR_NOT_AVAILABLE);
michael@0 210 },
michael@0 211
michael@0 212 promptForSaveToFileAsync: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
michael@0 213 var result = null;
michael@0 214
michael@0 215 this.mLauncher = aLauncher;
michael@0 216
michael@0 217 let prefs = Components.classes["@mozilla.org/preferences-service;1"]
michael@0 218 .getService(Components.interfaces.nsIPrefBranch);
michael@0 219 let bundle =
michael@0 220 Services.strings
michael@0 221 .createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
michael@0 222
michael@0 223 Task.spawn(function() {
michael@0 224 if (!aForcePrompt) {
michael@0 225 // Check to see if the user wishes to auto save to the default download
michael@0 226 // folder without prompting. Note that preference might not be set.
michael@0 227 let autodownload = false;
michael@0 228 try {
michael@0 229 autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
michael@0 230 } catch (e) { }
michael@0 231
michael@0 232 if (autodownload) {
michael@0 233 // Retrieve the user's default download directory
michael@0 234 let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
michael@0 235 let defaultFolder = new FileUtils.File(preferredDir);
michael@0 236
michael@0 237 try {
michael@0 238 result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension);
michael@0 239 }
michael@0 240 catch (ex) {
michael@0 241 // When the default download directory is write-protected,
michael@0 242 // prompt the user for a different target file.
michael@0 243 }
michael@0 244
michael@0 245 // Check to make sure we have a valid directory, otherwise, prompt
michael@0 246 if (result) {
michael@0 247 // This path is taken when we have a writable default download directory.
michael@0 248 aLauncher.saveDestinationAvailable(result);
michael@0 249 return;
michael@0 250 }
michael@0 251 }
michael@0 252 }
michael@0 253
michael@0 254 // Use file picker to show dialog.
michael@0 255 var nsIFilePicker = Components.interfaces.nsIFilePicker;
michael@0 256 var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
michael@0 257 var windowTitle = bundle.GetStringFromName("saveDialogTitle");
michael@0 258 var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);
michael@0 259 picker.init(parent, windowTitle, nsIFilePicker.modeSave);
michael@0 260 picker.defaultString = aDefaultFile;
michael@0 261
michael@0 262 let gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
michael@0 263
michael@0 264 if (aSuggestedFileExtension) {
michael@0 265 // aSuggestedFileExtension includes the period, so strip it
michael@0 266 picker.defaultExtension = aSuggestedFileExtension.substring(1);
michael@0 267 }
michael@0 268 else {
michael@0 269 try {
michael@0 270 picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
michael@0 271 }
michael@0 272 catch (ex) { }
michael@0 273 }
michael@0 274
michael@0 275 var wildCardExtension = "*";
michael@0 276 if (aSuggestedFileExtension) {
michael@0 277 wildCardExtension += aSuggestedFileExtension;
michael@0 278 picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension);
michael@0 279 }
michael@0 280
michael@0 281 picker.appendFilters( nsIFilePicker.filterAll );
michael@0 282
michael@0 283 // Default to lastDir if it is valid, otherwise use the user's default
michael@0 284 // downloads directory. getPreferredDownloadsDirectory should always
michael@0 285 // return a valid directory path, so we can safely default to it.
michael@0 286 let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
michael@0 287 picker.displayDirectory = new FileUtils.File(preferredDir);
michael@0 288
michael@0 289 gDownloadLastDir.getFileAsync(aLauncher.source, function LastDirCallback(lastDir) {
michael@0 290 if (lastDir && isUsableDirectory(lastDir))
michael@0 291 picker.displayDirectory = lastDir;
michael@0 292
michael@0 293 if (picker.show() == nsIFilePicker.returnCancel) {
michael@0 294 // null result means user cancelled.
michael@0 295 aLauncher.saveDestinationAvailable(null);
michael@0 296 return;
michael@0 297 }
michael@0 298
michael@0 299 // Be sure to save the directory the user chose through the Save As...
michael@0 300 // dialog as the new browser.download.dir since the old one
michael@0 301 // didn't exist.
michael@0 302 result = picker.file;
michael@0 303
michael@0 304 if (result) {
michael@0 305 try {
michael@0 306 // Remove the file so that it's not there when we ensure non-existence later;
michael@0 307 // this is safe because for the file to exist, the user would have had to
michael@0 308 // confirm that he wanted the file overwritten.
michael@0 309 if (result.exists())
michael@0 310 result.remove(false);
michael@0 311 }
michael@0 312 catch (ex) {
michael@0 313 // As it turns out, the failure to remove the file, for example due to
michael@0 314 // permission error, will be handled below eventually somehow.
michael@0 315 }
michael@0 316
michael@0 317 var newDir = result.parent.QueryInterface(Components.interfaces.nsILocalFile);
michael@0 318
michael@0 319 // Do not store the last save directory as a pref inside the private browsing mode
michael@0 320 gDownloadLastDir.setFile(aLauncher.source, newDir);
michael@0 321
michael@0 322 try {
michael@0 323 result = this.validateLeafName(newDir, result.leafName, null);
michael@0 324 }
michael@0 325 catch (ex) {
michael@0 326 // When the chosen download directory is write-protected,
michael@0 327 // display an informative error message.
michael@0 328 // In all cases, download will be stopped.
michael@0 329
michael@0 330 if (ex.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED) {
michael@0 331 this.displayBadPermissionAlert();
michael@0 332 aLauncher.saveDestinationAvailable(null);
michael@0 333 return;
michael@0 334 }
michael@0 335
michael@0 336 }
michael@0 337 }
michael@0 338 aLauncher.saveDestinationAvailable(result);
michael@0 339 }.bind(this));
michael@0 340 }.bind(this)).then(null, Components.utils.reportError);
michael@0 341 },
michael@0 342
michael@0 343 /**
michael@0 344 * Ensures that a local folder/file combination does not already exist in
michael@0 345 * the file system (or finds such a combination with a reasonably similar
michael@0 346 * leaf name), creates the corresponding file, and returns it.
michael@0 347 *
michael@0 348 * @param aLocalFolder
michael@0 349 * the folder where the file resides
michael@0 350 * @param aLeafName
michael@0 351 * the string name of the file (may be empty if no name is known,
michael@0 352 * in which case a name will be chosen)
michael@0 353 * @param aFileExt
michael@0 354 * the extension of the file, if one is known; this will be ignored
michael@0 355 * if aLeafName is non-empty
michael@0 356 * @return nsILocalFile
michael@0 357 * the created file
michael@0 358 * @throw an error such as permission doesn't allow creation of
michael@0 359 * file, etc.
michael@0 360 */
michael@0 361 validateLeafName: function (aLocalFolder, aLeafName, aFileExt)
michael@0 362 {
michael@0 363 if (!(aLocalFolder && isUsableDirectory(aLocalFolder))) {
michael@0 364 throw new Components.Exception("Destination directory non-existing or permission error",
michael@0 365 Components.results.NS_ERROR_FILE_ACCESS_DENIED);
michael@0 366 }
michael@0 367 // Remove any leading periods, since we don't want to save hidden files
michael@0 368 // automatically.
michael@0 369 aLeafName = aLeafName.replace(/^\.+/, "");
michael@0 370
michael@0 371 if (aLeafName == "")
michael@0 372 aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
michael@0 373 aLocalFolder.append(aLeafName);
michael@0 374
michael@0 375 // The following assignment can throw an exception, but
michael@0 376 // is now caught properly in the caller of validateLeafName.
michael@0 377 var createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder);
michael@0 378
michael@0 379 #ifdef XP_WIN
michael@0 380 let ext;
michael@0 381 try {
michael@0 382 // We can fail here if there's no primary extension set
michael@0 383 ext = "." + this.mLauncher.MIMEInfo.primaryExtension;
michael@0 384 } catch (e) { }
michael@0 385
michael@0 386 // Append a file extension if it's an executable that doesn't have one
michael@0 387 // but make sure we actually have an extension to add
michael@0 388 let leaf = createdFile.leafName;
michael@0 389 if (ext && leaf.slice(-ext.length) != ext && createdFile.isExecutable()) {
michael@0 390 createdFile.remove(false);
michael@0 391 aLocalFolder.leafName = leaf + ext;
michael@0 392 createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder);
michael@0 393 }
michael@0 394 #endif
michael@0 395
michael@0 396 return createdFile;
michael@0 397 },
michael@0 398
michael@0 399 // ---------- implementation methods ----------
michael@0 400
michael@0 401 // initDialog: Fill various dialog fields with initial content.
michael@0 402 initDialog : function() {
michael@0 403 // Put file name in window title.
michael@0 404 var suggestedFileName = this.mLauncher.suggestedFileName;
michael@0 405
michael@0 406 // Some URIs do not implement nsIURL, so we can't just QI.
michael@0 407 var url = this.mLauncher.source;
michael@0 408 if (url instanceof Components.interfaces.nsINestedURI)
michael@0 409 url = url.innermostURI;
michael@0 410
michael@0 411 var fname = "";
michael@0 412 var iconPath = "goat";
michael@0 413 this.mSourcePath = url.prePath;
michael@0 414 if (url instanceof Components.interfaces.nsIURL) {
michael@0 415 // A url, use file name from it.
michael@0 416 fname = iconPath = url.fileName;
michael@0 417 this.mSourcePath += url.directory;
michael@0 418 } else {
michael@0 419 // A generic uri, use path.
michael@0 420 fname = url.path;
michael@0 421 this.mSourcePath += url.path;
michael@0 422 }
michael@0 423
michael@0 424 if (suggestedFileName)
michael@0 425 fname = iconPath = suggestedFileName;
michael@0 426
michael@0 427 var displayName = fname.replace(/ +/g, " ");
michael@0 428
michael@0 429 this.mTitle = this.dialogElement("strings").getFormattedString("title", [displayName]);
michael@0 430 this.mDialog.document.title = this.mTitle;
michael@0 431
michael@0 432 // Put content type, filename and location into intro.
michael@0 433 this.initIntro(url, fname, displayName);
michael@0 434
michael@0 435 var iconString = "moz-icon://" + iconPath + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
michael@0 436 this.dialogElement("contentTypeImage").setAttribute("src", iconString);
michael@0 437
michael@0 438 // if always-save and is-executable and no-handler
michael@0 439 // then set up simple ui
michael@0 440 var mimeType = this.mLauncher.MIMEInfo.MIMEType;
michael@0 441 var shouldntRememberChoice = (mimeType == "application/octet-stream" ||
michael@0 442 mimeType == "application/x-msdownload" ||
michael@0 443 this.mLauncher.targetFileIsExecutable);
michael@0 444 if (shouldntRememberChoice && !this.openWithDefaultOK()) {
michael@0 445 // hide featured choice
michael@0 446 this.dialogElement("normalBox").collapsed = true;
michael@0 447 // show basic choice
michael@0 448 this.dialogElement("basicBox").collapsed = false;
michael@0 449 // change button labels and icons; use "save" icon for the accept
michael@0 450 // button since it's the only action possible
michael@0 451 let acceptButton = this.mDialog.document.documentElement
michael@0 452 .getButton("accept");
michael@0 453 acceptButton.label = this.dialogElement("strings")
michael@0 454 .getString("unknownAccept.label");
michael@0 455 acceptButton.setAttribute("icon", "save");
michael@0 456 this.mDialog.document.documentElement.getButton("cancel").label = this.dialogElement("strings").getString("unknownCancel.label");
michael@0 457 // hide other handler
michael@0 458 this.dialogElement("openHandler").collapsed = true;
michael@0 459 // set save as the selected option
michael@0 460 this.dialogElement("mode").selectedItem = this.dialogElement("save");
michael@0 461 }
michael@0 462 else {
michael@0 463 this.initAppAndSaveToDiskValues();
michael@0 464
michael@0 465 // Initialize "always ask me" box. This should always be disabled
michael@0 466 // and set to true for the ambiguous type application/octet-stream.
michael@0 467 // We don't also check for application/x-msdownload here since we
michael@0 468 // want users to be able to autodownload .exe files.
michael@0 469 var rememberChoice = this.dialogElement("rememberChoice");
michael@0 470
michael@0 471 #if 0
michael@0 472 // Just because we have a content-type of application/octet-stream
michael@0 473 // here doesn't actually mean that the content is of that type. Many
michael@0 474 // servers default to sending text/plain for file types they don't know
michael@0 475 // about. To account for this, the uriloader does some checking to see
michael@0 476 // if a file sent as text/plain contains binary characters, and if so (*)
michael@0 477 // it morphs the content-type into application/octet-stream so that
michael@0 478 // the file can be properly handled. Since this is not generic binary
michael@0 479 // data, rather, a data format that the system probably knows about,
michael@0 480 // we don't want to use the content-type provided by this dialog's
michael@0 481 // opener, as that's the generic application/octet-stream that the
michael@0 482 // uriloader has passed, rather we want to ask the MIME Service.
michael@0 483 // This is so we don't needlessly disable the "autohandle" checkbox.
michael@0 484
michael@0 485 // commented out to close the opening brace in the if statement.
michael@0 486 // var mimeService = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService);
michael@0 487 // var type = mimeService.getTypeFromURI(this.mLauncher.source);
michael@0 488 // this.realMIMEInfo = mimeService.getFromTypeAndExtension(type, "");
michael@0 489
michael@0 490 // if (type == "application/octet-stream") {
michael@0 491 #endif
michael@0 492 if (shouldntRememberChoice) {
michael@0 493 rememberChoice.checked = false;
michael@0 494 rememberChoice.disabled = true;
michael@0 495 }
michael@0 496 else {
michael@0 497 rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling &&
michael@0 498 this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.handleInternally;
michael@0 499 }
michael@0 500 this.toggleRememberChoice(rememberChoice);
michael@0 501
michael@0 502 // XXXben - menulist won't init properly, hack.
michael@0 503 var openHandler = this.dialogElement("openHandler");
michael@0 504 openHandler.parentNode.removeChild(openHandler);
michael@0 505 var openHandlerBox = this.dialogElement("openHandlerBox");
michael@0 506 openHandlerBox.appendChild(openHandler);
michael@0 507 }
michael@0 508
michael@0 509 this.mDialog.setTimeout("dialog.postShowCallback()", 0);
michael@0 510
michael@0 511 let acceptDelay = Services.prefs.getIntPref("security.dialog_enable_delay");
michael@0 512 this.mDialog.document.documentElement.getButton("accept").disabled = true;
michael@0 513 this._showTimer = Components.classes["@mozilla.org/timer;1"]
michael@0 514 .createInstance(nsITimer);
michael@0 515 this._showTimer.initWithCallback(this, acceptDelay, nsITimer.TYPE_ONE_SHOT);
michael@0 516 },
michael@0 517
michael@0 518 notify: function (aTimer) {
michael@0 519 if (aTimer == this._showTimer) {
michael@0 520 if (!this.mDialog) {
michael@0 521 this.reallyShow();
michael@0 522 } else {
michael@0 523 // The user may have already canceled the dialog.
michael@0 524 try {
michael@0 525 if (!this._blurred) {
michael@0 526 this.mDialog.document.documentElement.getButton("accept").disabled = false;
michael@0 527 }
michael@0 528 } catch (ex) {}
michael@0 529 this._delayExpired = true;
michael@0 530 }
michael@0 531 // The timer won't release us, so we have to release it.
michael@0 532 this._showTimer = null;
michael@0 533 }
michael@0 534 else if (aTimer == this._saveToDiskTimer) {
michael@0 535 // Since saveToDisk may open a file picker and therefore block this routine,
michael@0 536 // we should only call it once the dialog is closed.
michael@0 537 this.mLauncher.saveToDisk(null, false);
michael@0 538 this._saveToDiskTimer = null;
michael@0 539 }
michael@0 540 },
michael@0 541
michael@0 542 postShowCallback: function () {
michael@0 543 this.mDialog.sizeToContent();
michael@0 544
michael@0 545 // Set initial focus
michael@0 546 this.dialogElement("mode").focus();
michael@0 547 },
michael@0 548
michael@0 549 // initIntro:
michael@0 550 initIntro: function(url, filename, displayname) {
michael@0 551 this.dialogElement( "location" ).value = displayname;
michael@0 552 this.dialogElement( "location" ).setAttribute("realname", filename);
michael@0 553 this.dialogElement( "location" ).setAttribute("tooltiptext", displayname);
michael@0 554
michael@0 555 // if mSourcePath is a local file, then let's use the pretty path name
michael@0 556 // instead of an ugly url...
michael@0 557 var pathString;
michael@0 558 if (url instanceof Components.interfaces.nsIFileURL) {
michael@0 559 try {
michael@0 560 // Getting .file might throw, or .parent could be null
michael@0 561 pathString = url.file.parent.path;
michael@0 562 } catch (ex) {}
michael@0 563 }
michael@0 564
michael@0 565 if (!pathString) {
michael@0 566 // wasn't a fileURL
michael@0 567 var tmpurl = url.clone(); // don't want to change the real url
michael@0 568 try {
michael@0 569 tmpurl.userPass = "";
michael@0 570 } catch (ex) {}
michael@0 571 pathString = tmpurl.prePath;
michael@0 572 }
michael@0 573
michael@0 574 // Set the location text, which is separate from the intro text so it can be cropped
michael@0 575 var location = this.dialogElement( "source" );
michael@0 576 location.value = pathString;
michael@0 577 location.setAttribute("tooltiptext", this.mSourcePath);
michael@0 578
michael@0 579 // Show the type of file.
michael@0 580 var type = this.dialogElement("type");
michael@0 581 var mimeInfo = this.mLauncher.MIMEInfo;
michael@0 582
michael@0 583 // 1. Try to use the pretty description of the type, if one is available.
michael@0 584 var typeString = mimeInfo.description;
michael@0 585
michael@0 586 if (typeString == "") {
michael@0 587 // 2. If there is none, use the extension to identify the file, e.g. "ZIP file"
michael@0 588 var primaryExtension = "";
michael@0 589 try {
michael@0 590 primaryExtension = mimeInfo.primaryExtension;
michael@0 591 }
michael@0 592 catch (ex) {
michael@0 593 }
michael@0 594 if (primaryExtension != "")
michael@0 595 typeString = this.dialogElement("strings").getFormattedString("fileType", [primaryExtension.toUpperCase()]);
michael@0 596 // 3. If we can't even do that, just give up and show the MIME type.
michael@0 597 else
michael@0 598 typeString = mimeInfo.MIMEType;
michael@0 599 }
michael@0 600 // When the length is unknown, contentLength would be -1
michael@0 601 if (this.mLauncher.contentLength >= 0) {
michael@0 602 let [size, unit] = DownloadUtils.
michael@0 603 convertByteUnits(this.mLauncher.contentLength);
michael@0 604 type.value = this.dialogElement("strings")
michael@0 605 .getFormattedString("orderedFileSizeWithType",
michael@0 606 [typeString, size, unit]);
michael@0 607 }
michael@0 608 else {
michael@0 609 type.value = typeString;
michael@0 610 }
michael@0 611 },
michael@0 612
michael@0 613 _blurred: false,
michael@0 614 _delayExpired: false,
michael@0 615 onBlur: function(aEvent) {
michael@0 616 this._blurred = true;
michael@0 617 this.mDialog.document.documentElement.getButton("accept").disabled = true;
michael@0 618 },
michael@0 619
michael@0 620 onFocus: function(aEvent) {
michael@0 621 this._blurred = false;
michael@0 622 if (this._delayExpired) {
michael@0 623 var script = "document.documentElement.getButton('accept').disabled = false";
michael@0 624 this.mDialog.setTimeout(script, 250);
michael@0 625 }
michael@0 626 },
michael@0 627
michael@0 628 // Returns true if opening the default application makes sense.
michael@0 629 openWithDefaultOK: function() {
michael@0 630 // The checking is different on Windows...
michael@0 631 #ifdef XP_WIN
michael@0 632 // Windows presents some special cases.
michael@0 633 // We need to prevent use of "system default" when the file is
michael@0 634 // executable (so the user doesn't launch nasty programs downloaded
michael@0 635 // from the web), and, enable use of "system default" if it isn't
michael@0 636 // executable (because we will prompt the user for the default app
michael@0 637 // in that case).
michael@0 638
michael@0 639 // Default is Ok if the file isn't executable (and vice-versa).
michael@0 640 return !this.mLauncher.targetFileIsExecutable;
michael@0 641 #else
michael@0 642 // On other platforms, default is Ok if there is a default app.
michael@0 643 // Note that nsIMIMEInfo providers need to ensure that this holds true
michael@0 644 // on each platform.
michael@0 645 return this.mLauncher.MIMEInfo.hasDefaultHandler;
michael@0 646 #endif
michael@0 647 },
michael@0 648
michael@0 649 // Set "default" application description field.
michael@0 650 initDefaultApp: function() {
michael@0 651 // Use description, if we can get one.
michael@0 652 var desc = this.mLauncher.MIMEInfo.defaultDescription;
michael@0 653 if (desc) {
michael@0 654 var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]);
michael@0 655 this.dialogElement("defaultHandler").label = defaultApp;
michael@0 656 }
michael@0 657 else {
michael@0 658 this.dialogElement("modeDeck").setAttribute("selectedIndex", "1");
michael@0 659 // Hide the default handler item too, in case the user picks a
michael@0 660 // custom handler at a later date which triggers the menulist to show.
michael@0 661 this.dialogElement("defaultHandler").hidden = true;
michael@0 662 }
michael@0 663 },
michael@0 664
michael@0 665 // getPath:
michael@0 666 getPath: function (aFile) {
michael@0 667 #ifdef XP_MACOSX
michael@0 668 return aFile.leafName || aFile.path;
michael@0 669 #else
michael@0 670 return aFile.path;
michael@0 671 #endif
michael@0 672 },
michael@0 673
michael@0 674 // initAppAndSaveToDiskValues:
michael@0 675 initAppAndSaveToDiskValues: function() {
michael@0 676 var modeGroup = this.dialogElement("mode");
michael@0 677
michael@0 678 // We don't let users open .exe files or random binary data directly
michael@0 679 // from the browser at the moment because of security concerns.
michael@0 680 var openWithDefaultOK = this.openWithDefaultOK();
michael@0 681 var mimeType = this.mLauncher.MIMEInfo.MIMEType;
michael@0 682 if (this.mLauncher.targetFileIsExecutable || (
michael@0 683 (mimeType == "application/octet-stream" ||
michael@0 684 mimeType == "application/x-msdownload") &&
michael@0 685 !openWithDefaultOK)) {
michael@0 686 this.dialogElement("open").disabled = true;
michael@0 687 var openHandler = this.dialogElement("openHandler");
michael@0 688 openHandler.disabled = true;
michael@0 689 openHandler.selectedItem = null;
michael@0 690 modeGroup.selectedItem = this.dialogElement("save");
michael@0 691 return;
michael@0 692 }
michael@0 693
michael@0 694 // Fill in helper app info, if there is any.
michael@0 695 try {
michael@0 696 this.chosenApp =
michael@0 697 this.mLauncher.MIMEInfo.preferredApplicationHandler
michael@0 698 .QueryInterface(Components.interfaces.nsILocalHandlerApp);
michael@0 699 } catch (e) {
michael@0 700 this.chosenApp = null;
michael@0 701 }
michael@0 702 // Initialize "default application" field.
michael@0 703 this.initDefaultApp();
michael@0 704
michael@0 705 var otherHandler = this.dialogElement("otherHandler");
michael@0 706
michael@0 707 // Fill application name textbox.
michael@0 708 if (this.chosenApp && this.chosenApp.executable &&
michael@0 709 this.chosenApp.executable.path) {
michael@0 710 otherHandler.setAttribute("path",
michael@0 711 this.getPath(this.chosenApp.executable));
michael@0 712
michael@0 713 otherHandler.label = this.getFileDisplayName(this.chosenApp.executable);
michael@0 714 otherHandler.hidden = false;
michael@0 715 }
michael@0 716
michael@0 717 var useDefault = this.dialogElement("useSystemDefault");
michael@0 718 var openHandler = this.dialogElement("openHandler");
michael@0 719 openHandler.selectedIndex = 0;
michael@0 720
michael@0 721 if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
michael@0 722 // Open (using system default).
michael@0 723 modeGroup.selectedItem = this.dialogElement("open");
michael@0 724 } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
michael@0 725 // Open with given helper app.
michael@0 726 modeGroup.selectedItem = this.dialogElement("open");
michael@0 727 openHandler.selectedIndex = 1;
michael@0 728 } else {
michael@0 729 // Save to disk.
michael@0 730 modeGroup.selectedItem = this.dialogElement("save");
michael@0 731 }
michael@0 732
michael@0 733 // If we don't have a "default app" then disable that choice.
michael@0 734 if (!openWithDefaultOK) {
michael@0 735 var useDefault = this.dialogElement("defaultHandler");
michael@0 736 var isSelected = useDefault.selected;
michael@0 737
michael@0 738 // Disable that choice.
michael@0 739 useDefault.hidden = true;
michael@0 740 // If that's the default, then switch to "save to disk."
michael@0 741 if (isSelected) {
michael@0 742 openHandler.selectedIndex = 1;
michael@0 743 modeGroup.selectedItem = this.dialogElement("save");
michael@0 744 }
michael@0 745 }
michael@0 746
michael@0 747 otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = false;
michael@0 748 this.updateOKButton();
michael@0 749 },
michael@0 750
michael@0 751 // Returns the user-selected application
michael@0 752 helperAppChoice: function() {
michael@0 753 return this.chosenApp;
michael@0 754 },
michael@0 755
michael@0 756 get saveToDisk() {
michael@0 757 return this.dialogElement("save").selected;
michael@0 758 },
michael@0 759
michael@0 760 get useOtherHandler() {
michael@0 761 return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1;
michael@0 762 },
michael@0 763
michael@0 764 get useSystemDefault() {
michael@0 765 return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0;
michael@0 766 },
michael@0 767
michael@0 768 toggleRememberChoice: function (aCheckbox) {
michael@0 769 this.dialogElement("settingsChange").hidden = !aCheckbox.checked;
michael@0 770 this.mDialog.sizeToContent();
michael@0 771 },
michael@0 772
michael@0 773 openHandlerCommand: function () {
michael@0 774 var openHandler = this.dialogElement("openHandler");
michael@0 775 if (openHandler.selectedItem.id == "choose")
michael@0 776 this.chooseApp();
michael@0 777 else
michael@0 778 openHandler.setAttribute("lastSelectedItemID", openHandler.selectedItem.id);
michael@0 779 },
michael@0 780
michael@0 781 updateOKButton: function() {
michael@0 782 var ok = false;
michael@0 783 if (this.dialogElement("save").selected) {
michael@0 784 // This is always OK.
michael@0 785 ok = true;
michael@0 786 }
michael@0 787 else if (this.dialogElement("open").selected) {
michael@0 788 switch (this.dialogElement("openHandler").selectedIndex) {
michael@0 789 case 0:
michael@0 790 // No app need be specified in this case.
michael@0 791 ok = true;
michael@0 792 break;
michael@0 793 case 1:
michael@0 794 // only enable the OK button if we have a default app to use or if
michael@0 795 // the user chose an app....
michael@0 796 ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path"));
michael@0 797 break;
michael@0 798 }
michael@0 799 }
michael@0 800
michael@0 801 // Enable Ok button if ok to press.
michael@0 802 this.mDialog.document.documentElement.getButton("accept").disabled = !ok;
michael@0 803 },
michael@0 804
michael@0 805 // Returns true iff the user-specified helper app has been modified.
michael@0 806 appChanged: function() {
michael@0 807 return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
michael@0 808 },
michael@0 809
michael@0 810 updateMIMEInfo: function() {
michael@0 811 // Don't update mime type preferences when the preferred action is set to
michael@0 812 // the internal handler -- this dialog is the result of the handler fallback
michael@0 813 // (e.g. Content-Disposition was set as attachment)
michael@0 814 var discardUpdate = this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.handleInternally &&
michael@0 815 !this.dialogElement("rememberChoice").checked;
michael@0 816
michael@0 817 var needUpdate = false;
michael@0 818 // If current selection differs from what's in the mime info object,
michael@0 819 // then we need to update.
michael@0 820 if (this.saveToDisk) {
michael@0 821 needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
michael@0 822 if (needUpdate)
michael@0 823 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
michael@0 824 }
michael@0 825 else if (this.useSystemDefault) {
michael@0 826 needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
michael@0 827 if (needUpdate)
michael@0 828 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
michael@0 829 }
michael@0 830 else {
michael@0 831 // For "open with", we need to check both preferred action and whether the user chose
michael@0 832 // a new app.
michael@0 833 needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
michael@0 834 if (needUpdate) {
michael@0 835 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
michael@0 836 // App may have changed - Update application
michael@0 837 var app = this.helperAppChoice();
michael@0 838 this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
michael@0 839 }
michael@0 840 }
michael@0 841 // We will also need to update if the "always ask" flag has changed.
michael@0 842 needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked);
michael@0 843
michael@0 844 // One last special case: If the input "always ask" flag was false, then we always
michael@0 845 // update. In that case we are displaying the helper app dialog for the first
michael@0 846 // time for this mime type and we need to store the user's action in the mimeTypes.rdf
michael@0 847 // data source (whether that action has changed or not; if it didn't change, then we need
michael@0 848 // to store the "always ask" flag so the helper app dialog will or won't display
michael@0 849 // next time, per the user's selection).
michael@0 850 needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
michael@0 851
michael@0 852 // Make sure mime info has updated setting for the "always ask" flag.
michael@0 853 this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked;
michael@0 854
michael@0 855 return needUpdate && !discardUpdate;
michael@0 856 },
michael@0 857
michael@0 858 // See if the user changed things, and if so, update the
michael@0 859 // mimeTypes.rdf entry for this mime type.
michael@0 860 updateHelperAppPref: function() {
michael@0 861 var ha = new this.mDialog.HelperApps();
michael@0 862 ha.updateTypeInfo(this.mLauncher.MIMEInfo);
michael@0 863 ha.destroy();
michael@0 864 },
michael@0 865
michael@0 866 // onOK:
michael@0 867 onOK: function() {
michael@0 868 // Verify typed app path, if necessary.
michael@0 869 if (this.useOtherHandler) {
michael@0 870 var helperApp = this.helperAppChoice();
michael@0 871 if (!helperApp || !helperApp.executable ||
michael@0 872 !helperApp.executable.exists()) {
michael@0 873 // Show alert and try again.
michael@0 874 var bundle = this.dialogElement("strings");
michael@0 875 var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").getAttribute("path")]);
michael@0 876 Services.prompt.alert(this.mDialog, bundle.getString("badApp.title"), msg);
michael@0 877
michael@0 878 // Disable the OK button.
michael@0 879 this.mDialog.document.documentElement.getButton("accept").disabled = true;
michael@0 880 this.dialogElement("mode").focus();
michael@0 881
michael@0 882 // Clear chosen application.
michael@0 883 this.chosenApp = null;
michael@0 884
michael@0 885 // Leave dialog up.
michael@0 886 return false;
michael@0 887 }
michael@0 888 }
michael@0 889
michael@0 890 // Remove our web progress listener (a progress dialog will be
michael@0 891 // taking over).
michael@0 892 this.mLauncher.setWebProgressListener(null);
michael@0 893
michael@0 894 // saveToDisk and launchWithApplication can return errors in
michael@0 895 // certain circumstances (e.g. The user clicks cancel in the
michael@0 896 // "Save to Disk" dialog. In those cases, we don't want to
michael@0 897 // update the helper application preferences in the RDF file.
michael@0 898 try {
michael@0 899 var needUpdate = this.updateMIMEInfo();
michael@0 900
michael@0 901 if (this.dialogElement("save").selected) {
michael@0 902 // If we're using a default download location, create a path
michael@0 903 // for the file to be saved to to pass to |saveToDisk| - otherwise
michael@0 904 // we must ask the user to pick a save name.
michael@0 905
michael@0 906 #if 0
michael@0 907 var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
michael@0 908 var targetFile = null;
michael@0 909 try {
michael@0 910 targetFile = prefs.getComplexValue("browser.download.defaultFolder",
michael@0 911 Components.interfaces.nsILocalFile);
michael@0 912 var leafName = this.dialogElement("location").getAttribute("realname");
michael@0 913 // Ensure that we don't overwrite any existing files here.
michael@0 914 targetFile = this.validateLeafName(targetFile, leafName, null);
michael@0 915 }
michael@0 916 catch(e) { }
michael@0 917
michael@0 918 this.mLauncher.saveToDisk(targetFile, false);
michael@0 919 #endif
michael@0 920
michael@0 921 // see @notify
michael@0 922 // we cannot use opener's setTimeout, see bug 420405
michael@0 923 this._saveToDiskTimer = Components.classes["@mozilla.org/timer;1"]
michael@0 924 .createInstance(nsITimer);
michael@0 925 this._saveToDiskTimer.initWithCallback(this, 0,
michael@0 926 nsITimer.TYPE_ONE_SHOT);
michael@0 927 }
michael@0 928 else
michael@0 929 this.mLauncher.launchWithApplication(null, false);
michael@0 930
michael@0 931 // Update user pref for this mime type (if necessary). We do not
michael@0 932 // store anything in the mime type preferences for the ambiguous
michael@0 933 // type application/octet-stream. We do NOT do this for
michael@0 934 // application/x-msdownload since we want users to be able to
michael@0 935 // autodownload these to disk.
michael@0 936 if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream")
michael@0 937 this.updateHelperAppPref();
michael@0 938 } catch(e) { }
michael@0 939
michael@0 940 // Unhook dialog from this object.
michael@0 941 this.mDialog.dialog = null;
michael@0 942
michael@0 943 // Close up dialog by returning true.
michael@0 944 return true;
michael@0 945 },
michael@0 946
michael@0 947 // onCancel:
michael@0 948 onCancel: function() {
michael@0 949 // Remove our web progress listener.
michael@0 950 this.mLauncher.setWebProgressListener(null);
michael@0 951
michael@0 952 // Cancel app launcher.
michael@0 953 try {
michael@0 954 this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED);
michael@0 955 } catch(exception) {
michael@0 956 }
michael@0 957
michael@0 958 // Unhook dialog from this object.
michael@0 959 this.mDialog.dialog = null;
michael@0 960
michael@0 961 // Close up dialog by returning true.
michael@0 962 return true;
michael@0 963 },
michael@0 964
michael@0 965 // dialogElement: Convenience.
michael@0 966 dialogElement: function(id) {
michael@0 967 return this.mDialog.document.getElementById(id);
michael@0 968 },
michael@0 969
michael@0 970 // Retrieve the pretty description from the file
michael@0 971 getFileDisplayName: function getFileDisplayName(file)
michael@0 972 {
michael@0 973 #ifdef XP_WIN
michael@0 974 if (file instanceof Components.interfaces.nsILocalFileWin) {
michael@0 975 try {
michael@0 976 return file.getVersionInfoField("FileDescription");
michael@0 977 } catch (e) {}
michael@0 978 }
michael@0 979 #endif
michael@0 980 #ifdef XP_MACOSX
michael@0 981 if (file instanceof Components.interfaces.nsILocalFileMac) {
michael@0 982 try {
michael@0 983 return file.bundleDisplayName;
michael@0 984 } catch (e) {}
michael@0 985 }
michael@0 986 #endif
michael@0 987 return file.leafName;
michael@0 988 },
michael@0 989
michael@0 990 // chooseApp: Open file picker and prompt user for application.
michael@0 991 chooseApp: function() {
michael@0 992 #ifdef XP_WIN
michael@0 993 // Protect against the lack of an extension
michael@0 994 var fileExtension = "";
michael@0 995 try {
michael@0 996 fileExtension = this.mLauncher.MIMEInfo.primaryExtension;
michael@0 997 } catch(ex) {
michael@0 998 }
michael@0 999
michael@0 1000 // Try to use the pretty description of the type, if one is available.
michael@0 1001 var typeString = this.mLauncher.MIMEInfo.description;
michael@0 1002
michael@0 1003 if (!typeString) {
michael@0 1004 // If there is none, use the extension to
michael@0 1005 // identify the file, e.g. "ZIP file"
michael@0 1006 if (fileExtension) {
michael@0 1007 typeString =
michael@0 1008 this.dialogElement("strings").
michael@0 1009 getFormattedString("fileType", [fileExtension.toUpperCase()]);
michael@0 1010 } else {
michael@0 1011 // If we can't even do that, just give up and show the MIME type.
michael@0 1012 typeString = this.mLauncher.MIMEInfo.MIMEType;
michael@0 1013 }
michael@0 1014 }
michael@0 1015
michael@0 1016 var params = {};
michael@0 1017 params.title =
michael@0 1018 this.dialogElement("strings").getString("chooseAppFilePickerTitle");
michael@0 1019 params.description = typeString;
michael@0 1020 params.filename = this.mLauncher.suggestedFileName;
michael@0 1021 params.mimeInfo = this.mLauncher.MIMEInfo;
michael@0 1022 params.handlerApp = null;
michael@0 1023
michael@0 1024 this.mDialog.openDialog("chrome://global/content/appPicker.xul", null,
michael@0 1025 "chrome,modal,centerscreen,titlebar,dialog=yes",
michael@0 1026 params);
michael@0 1027
michael@0 1028 if (params.handlerApp &&
michael@0 1029 params.handlerApp.executable &&
michael@0 1030 params.handlerApp.executable.isFile()) {
michael@0 1031 // Remember the file they chose to run.
michael@0 1032 this.chosenApp = params.handlerApp;
michael@0 1033
michael@0 1034 #else
michael@0 1035 var nsIFilePicker = Components.interfaces.nsIFilePicker;
michael@0 1036 var fp = Components.classes["@mozilla.org/filepicker;1"]
michael@0 1037 .createInstance(nsIFilePicker);
michael@0 1038 fp.init(this.mDialog,
michael@0 1039 this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
michael@0 1040 nsIFilePicker.modeOpen);
michael@0 1041
michael@0 1042 fp.appendFilters(nsIFilePicker.filterApps);
michael@0 1043
michael@0 1044 if (fp.show() == nsIFilePicker.returnOK && fp.file) {
michael@0 1045 // Remember the file they chose to run.
michael@0 1046 var localHandlerApp =
michael@0 1047 Components.classes["@mozilla.org/uriloader/local-handler-app;1"].
michael@0 1048 createInstance(Components.interfaces.nsILocalHandlerApp);
michael@0 1049 localHandlerApp.executable = fp.file;
michael@0 1050 this.chosenApp = localHandlerApp;
michael@0 1051 #endif
michael@0 1052
michael@0 1053 // Show the "handler" menulist since we have a (user-specified)
michael@0 1054 // application now.
michael@0 1055 this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");
michael@0 1056
michael@0 1057 // Update dialog.
michael@0 1058 var otherHandler = this.dialogElement("otherHandler");
michael@0 1059 otherHandler.removeAttribute("hidden");
michael@0 1060 otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable));
michael@0 1061 otherHandler.label = this.getFileDisplayName(this.chosenApp.executable);
michael@0 1062 this.dialogElement("openHandler").selectedIndex = 1;
michael@0 1063 this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler");
michael@0 1064
michael@0 1065 this.dialogElement("mode").selectedItem = this.dialogElement("open");
michael@0 1066 }
michael@0 1067 else {
michael@0 1068 var openHandler = this.dialogElement("openHandler");
michael@0 1069 var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
michael@0 1070 if (!lastSelectedID)
michael@0 1071 lastSelectedID = "defaultHandler";
michael@0 1072 openHandler.selectedItem = this.dialogElement(lastSelectedID);
michael@0 1073 }
michael@0 1074 },
michael@0 1075
michael@0 1076 // Turn this on to get debugging messages.
michael@0 1077 debug: false,
michael@0 1078
michael@0 1079 // Dump text (if debug is on).
michael@0 1080 dump: function( text ) {
michael@0 1081 if ( this.debug ) {
michael@0 1082 dump( text );
michael@0 1083 }
michael@0 1084 },
michael@0 1085
michael@0 1086 // dumpObj:
michael@0 1087 dumpObj: function( spec ) {
michael@0 1088 var val = "<undefined>";
michael@0 1089 try {
michael@0 1090 val = eval( "this."+spec ).toString();
michael@0 1091 } catch( exception ) {
michael@0 1092 }
michael@0 1093 this.dump( spec + "=" + val + "\n" );
michael@0 1094 },
michael@0 1095
michael@0 1096 // dumpObjectProperties
michael@0 1097 dumpObjectProperties: function( desc, obj ) {
michael@0 1098 for( prop in obj ) {
michael@0 1099 this.dump( desc + "." + prop + "=" );
michael@0 1100 var val = "<undefined>";
michael@0 1101 try {
michael@0 1102 val = obj[ prop ];
michael@0 1103 } catch ( exception ) {
michael@0 1104 }
michael@0 1105 this.dump( val + "\n" );
michael@0 1106 }
michael@0 1107 }
michael@0 1108 }
michael@0 1109
michael@0 1110 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsUnknownContentTypeDialog]);

mercurial