Wed, 31 Dec 2014 06:09:35 +0100
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]); |