Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | const Cc = Components.classes; |
michael@0 | 6 | const Ci = Components.interfaces; |
michael@0 | 7 | const Cu = Components.utils; |
michael@0 | 8 | const Cr = Components.results; |
michael@0 | 9 | |
michael@0 | 10 | const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir"; |
michael@0 | 11 | const URI_GENERIC_ICON_DOWNLOAD = "chrome://browser/skin/images/alert-downloads-30.png"; |
michael@0 | 12 | |
michael@0 | 13 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 14 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 15 | Cu.import("resource://gre/modules/DownloadUtils.jsm"); |
michael@0 | 16 | |
michael@0 | 17 | XPCOMUtils.defineLazyGetter(this, "ContentUtil", function() { |
michael@0 | 18 | Cu.import("resource:///modules/ContentUtil.jsm"); |
michael@0 | 19 | return ContentUtil; |
michael@0 | 20 | }); |
michael@0 | 21 | XPCOMUtils.defineLazyModuleGetter(this, "Downloads", |
michael@0 | 22 | "resource://gre/modules/Downloads.jsm"); |
michael@0 | 23 | XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", |
michael@0 | 24 | "resource://gre/modules/FileUtils.jsm"); |
michael@0 | 25 | XPCOMUtils.defineLazyModuleGetter(this, "Task", |
michael@0 | 26 | "resource://gre/modules/Task.jsm"); |
michael@0 | 27 | |
michael@0 | 28 | // ----------------------------------------------------------------------- |
michael@0 | 29 | // HelperApp Launcher Dialog |
michael@0 | 30 | // ----------------------------------------------------------------------- |
michael@0 | 31 | |
michael@0 | 32 | function HelperAppLauncherDialog() { } |
michael@0 | 33 | |
michael@0 | 34 | HelperAppLauncherDialog.prototype = { |
michael@0 | 35 | classID: Components.ID("{e9d277a0-268a-4ec2-bb8c-10fdf3e44611}"), |
michael@0 | 36 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]), |
michael@0 | 37 | |
michael@0 | 38 | show: function hald_show(aLauncher, aContext, aReason) { |
michael@0 | 39 | // Check to see if we can open this file or not |
michael@0 | 40 | // If the file is an executable then launchWithApplication will fail in |
michael@0 | 41 | // /uriloader nsMIMEInfoWin.cpp code. So always download in that case. |
michael@0 | 42 | if (aLauncher.MIMEInfo.hasDefaultHandler && !aLauncher.targetFileIsExecutable) { |
michael@0 | 43 | aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.useSystemDefault; |
michael@0 | 44 | aLauncher.launchWithApplication(null, false); |
michael@0 | 45 | } else { |
michael@0 | 46 | let wasClicked = false; |
michael@0 | 47 | this._showDownloadInfobar(aLauncher); |
michael@0 | 48 | } |
michael@0 | 49 | }, |
michael@0 | 50 | |
michael@0 | 51 | _getDownloadSize: function dv__getDownloadSize (aSize) { |
michael@0 | 52 | let displaySize = DownloadUtils.convertByteUnits(aSize); |
michael@0 | 53 | // displaySize[0] is formatted size, displaySize[1] is units |
michael@0 | 54 | if (aSize > 0) |
michael@0 | 55 | return displaySize.join(""); |
michael@0 | 56 | else { |
michael@0 | 57 | let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); |
michael@0 | 58 | return browserBundle.GetStringFromName("downloadsUnknownSize"); |
michael@0 | 59 | } |
michael@0 | 60 | }, |
michael@0 | 61 | |
michael@0 | 62 | _getChromeWindow: function (aWindow) { |
michael@0 | 63 | let chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 64 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 65 | .QueryInterface(Ci.nsIDocShellTreeItem) |
michael@0 | 66 | .rootTreeItem |
michael@0 | 67 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 68 | .getInterface(Ci.nsIDOMWindow) |
michael@0 | 69 | .QueryInterface(Ci.nsIDOMChromeWindow); |
michael@0 | 70 | return chromeWin; |
michael@0 | 71 | }, |
michael@0 | 72 | |
michael@0 | 73 | _showDownloadInfobar: function do_showDownloadInfobar(aLauncher) { |
michael@0 | 74 | let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); |
michael@0 | 75 | |
michael@0 | 76 | let runButtonText = |
michael@0 | 77 | browserBundle.GetStringFromName("downloadOpen"); |
michael@0 | 78 | let saveButtonText = |
michael@0 | 79 | browserBundle.GetStringFromName("downloadSave"); |
michael@0 | 80 | let cancelButtonText = |
michael@0 | 81 | browserBundle.GetStringFromName("downloadCancel"); |
michael@0 | 82 | |
michael@0 | 83 | let buttons = [ |
michael@0 | 84 | { |
michael@0 | 85 | isDefault: true, |
michael@0 | 86 | label: runButtonText, |
michael@0 | 87 | accessKey: "", |
michael@0 | 88 | callback: function() { |
michael@0 | 89 | aLauncher.saveToDisk(null, false); |
michael@0 | 90 | Services.obs.notifyObservers(aLauncher.targetFile, "dl-run", "true"); |
michael@0 | 91 | } |
michael@0 | 92 | }, |
michael@0 | 93 | { |
michael@0 | 94 | label: saveButtonText, |
michael@0 | 95 | accessKey: "", |
michael@0 | 96 | callback: function() { |
michael@0 | 97 | aLauncher.saveToDisk(null, false); |
michael@0 | 98 | Services.obs.notifyObservers(aLauncher.targetFile, "dl-run", "false"); |
michael@0 | 99 | } |
michael@0 | 100 | }, |
michael@0 | 101 | { |
michael@0 | 102 | label: cancelButtonText, |
michael@0 | 103 | accessKey: "", |
michael@0 | 104 | callback: function() { aLauncher.cancel(Cr.NS_BINDING_ABORTED); } |
michael@0 | 105 | } |
michael@0 | 106 | ]; |
michael@0 | 107 | |
michael@0 | 108 | let window = Services.wm.getMostRecentWindow("navigator:browser"); |
michael@0 | 109 | let chromeWin = this._getChromeWindow(window).wrappedJSObject; |
michael@0 | 110 | let notificationBox = chromeWin.Browser.getNotificationBox(); |
michael@0 | 111 | let document = notificationBox.ownerDocument; |
michael@0 | 112 | let downloadSize = this._getDownloadSize(aLauncher.contentLength); |
michael@0 | 113 | |
michael@0 | 114 | let msg = browserBundle.GetStringFromName("alertDownloadSave2"); |
michael@0 | 115 | |
michael@0 | 116 | let fragment = ContentUtil.populateFragmentFromString( |
michael@0 | 117 | document.createDocumentFragment(), |
michael@0 | 118 | msg, |
michael@0 | 119 | { |
michael@0 | 120 | text: aLauncher.suggestedFileName, |
michael@0 | 121 | className: "download-filename-text" |
michael@0 | 122 | }, |
michael@0 | 123 | { |
michael@0 | 124 | text: downloadSize, |
michael@0 | 125 | className: "download-size-text" |
michael@0 | 126 | }, |
michael@0 | 127 | { |
michael@0 | 128 | text: aLauncher.source.host, |
michael@0 | 129 | className: "download-host-text" |
michael@0 | 130 | } |
michael@0 | 131 | ); |
michael@0 | 132 | let newBar = notificationBox.appendNotification("", |
michael@0 | 133 | "save-download", |
michael@0 | 134 | URI_GENERIC_ICON_DOWNLOAD, |
michael@0 | 135 | notificationBox.PRIORITY_WARNING_HIGH, |
michael@0 | 136 | buttons); |
michael@0 | 137 | let messageContainer = document.getAnonymousElementByAttribute(newBar, "anonid", "messageText"); |
michael@0 | 138 | messageContainer.appendChild(fragment); |
michael@0 | 139 | }, |
michael@0 | 140 | |
michael@0 | 141 | promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) { |
michael@0 | 142 | throw new Components.Exception("Async version must be used", Cr.NS_ERROR_NOT_AVAILABLE); |
michael@0 | 143 | }, |
michael@0 | 144 | |
michael@0 | 145 | promptForSaveToFileAsync: function hald_promptForSaveToFileAsync(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) { |
michael@0 | 146 | let file = null; |
michael@0 | 147 | let prefs = Services.prefs; |
michael@0 | 148 | |
michael@0 | 149 | Task.spawn(function() { |
michael@0 | 150 | if (!aForcePrompt) { |
michael@0 | 151 | // Check to see if the user wishes to auto save to the default download |
michael@0 | 152 | // folder without prompting. Note that preference might not be set. |
michael@0 | 153 | let autodownload = true; |
michael@0 | 154 | try { |
michael@0 | 155 | autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR); |
michael@0 | 156 | } catch (e) { } |
michael@0 | 157 | |
michael@0 | 158 | if (autodownload) { |
michael@0 | 159 | // Retrieve the user's preferred download directory |
michael@0 | 160 | let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); |
michael@0 | 161 | let defaultFolder = new FileUtils.File(preferredDir); |
michael@0 | 162 | |
michael@0 | 163 | try { |
michael@0 | 164 | file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt); |
michael@0 | 165 | } |
michael@0 | 166 | catch (e) { |
michael@0 | 167 | } |
michael@0 | 168 | |
michael@0 | 169 | // Check to make sure we have a valid directory, otherwise, prompt |
michael@0 | 170 | if (file) { |
michael@0 | 171 | aLauncher.saveDestinationAvailable(file); |
michael@0 | 172 | return; |
michael@0 | 173 | } |
michael@0 | 174 | } |
michael@0 | 175 | } |
michael@0 | 176 | |
michael@0 | 177 | // Use file picker to show dialog. |
michael@0 | 178 | let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); |
michael@0 | 179 | let windowTitle = ""; |
michael@0 | 180 | let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); |
michael@0 | 181 | picker.init(parent, windowTitle, Ci.nsIFilePicker.modeSave); |
michael@0 | 182 | picker.defaultString = aDefaultFile; |
michael@0 | 183 | |
michael@0 | 184 | if (aSuggestedFileExt) { |
michael@0 | 185 | // aSuggestedFileExtension includes the period, so strip it |
michael@0 | 186 | picker.defaultExtension = aSuggestedFileExt.substring(1); |
michael@0 | 187 | } |
michael@0 | 188 | else { |
michael@0 | 189 | try { |
michael@0 | 190 | picker.defaultExtension = aLauncher.MIMEInfo.primaryExtension; |
michael@0 | 191 | } |
michael@0 | 192 | catch (e) { } |
michael@0 | 193 | } |
michael@0 | 194 | |
michael@0 | 195 | let wildCardExtension = "*"; |
michael@0 | 196 | if (aSuggestedFileExt) { |
michael@0 | 197 | wildCardExtension += aSuggestedFileExt; |
michael@0 | 198 | picker.appendFilter(aLauncher.MIMEInfo.description, wildCardExtension); |
michael@0 | 199 | } |
michael@0 | 200 | |
michael@0 | 201 | picker.appendFilters(Ci.nsIFilePicker.filterAll); |
michael@0 | 202 | |
michael@0 | 203 | // Default to lastDir if it is valid, otherwise use the user's preferred |
michael@0 | 204 | // downloads directory. getPreferredDownloadsDirectory should always |
michael@0 | 205 | // return a valid directory string, so we can safely default to it. |
michael@0 | 206 | let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); |
michael@0 | 207 | picker.displayDirectory = new FileUtils.File(preferredDir); |
michael@0 | 208 | |
michael@0 | 209 | // The last directory preference may not exist, which will throw. |
michael@0 | 210 | try { |
michael@0 | 211 | let lastDir = prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile); |
michael@0 | 212 | if (isUsableDirectory(lastDir)) |
michael@0 | 213 | picker.displayDirectory = lastDir; |
michael@0 | 214 | } |
michael@0 | 215 | catch (e) { } |
michael@0 | 216 | |
michael@0 | 217 | picker.open(function(aResult) { |
michael@0 | 218 | if (aResult == Ci.nsIFilePicker.returnCancel) { |
michael@0 | 219 | // null result means user cancelled. |
michael@0 | 220 | aLauncher.saveDestinationAvailable(null); |
michael@0 | 221 | return; |
michael@0 | 222 | } |
michael@0 | 223 | |
michael@0 | 224 | // Be sure to save the directory the user chose through the Save As... |
michael@0 | 225 | // dialog as the new browser.download.dir since the old one |
michael@0 | 226 | // didn't exist. |
michael@0 | 227 | file = picker.file; |
michael@0 | 228 | |
michael@0 | 229 | if (file) { |
michael@0 | 230 | try { |
michael@0 | 231 | // Remove the file so that it's not there when we ensure non-existence later; |
michael@0 | 232 | // this is safe because for the file to exist, the user would have had to |
michael@0 | 233 | // confirm that he wanted the file overwritten. |
michael@0 | 234 | if (file.exists()) |
michael@0 | 235 | file.remove(false); |
michael@0 | 236 | } |
michael@0 | 237 | catch (e) { } |
michael@0 | 238 | let newDir = file.parent.QueryInterface(Ci.nsILocalFile); |
michael@0 | 239 | prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir); |
michael@0 | 240 | file = this.validateLeafName(newDir, file.leafName, null); |
michael@0 | 241 | } |
michael@0 | 242 | aLauncher.saveDestinationAvailable(file); |
michael@0 | 243 | }.bind(this)); |
michael@0 | 244 | }.bind(this)); |
michael@0 | 245 | }, |
michael@0 | 246 | |
michael@0 | 247 | validateLeafName: function hald_validateLeafName(aLocalFile, aLeafName, aFileExt) { |
michael@0 | 248 | if (!(aLocalFile && this.isUsableDirectory(aLocalFile))) |
michael@0 | 249 | return null; |
michael@0 | 250 | |
michael@0 | 251 | // Remove any leading periods, since we don't want to save hidden files |
michael@0 | 252 | // automatically. |
michael@0 | 253 | aLeafName = aLeafName.replace(/^\.+/, ""); |
michael@0 | 254 | |
michael@0 | 255 | if (aLeafName == "") |
michael@0 | 256 | aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : ""); |
michael@0 | 257 | aLocalFile.append(aLeafName); |
michael@0 | 258 | |
michael@0 | 259 | this.makeFileUnique(aLocalFile); |
michael@0 | 260 | return aLocalFile; |
michael@0 | 261 | }, |
michael@0 | 262 | |
michael@0 | 263 | makeFileUnique: function hald_makeFileUnique(aLocalFile) { |
michael@0 | 264 | try { |
michael@0 | 265 | // Note - this code is identical to that in |
michael@0 | 266 | // toolkit/content/contentAreaUtils.js. |
michael@0 | 267 | // If you are updating this code, update that code too! We can't share code |
michael@0 | 268 | // here since this is called in a js component. |
michael@0 | 269 | var collisionCount = 0; |
michael@0 | 270 | while (aLocalFile.exists()) { |
michael@0 | 271 | collisionCount++; |
michael@0 | 272 | if (collisionCount == 1) { |
michael@0 | 273 | // Append "(2)" before the last dot in (or at the end of) the filename |
michael@0 | 274 | // special case .ext.gz etc files so we don't wind up with .tar(2).gz |
michael@0 | 275 | if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i)) |
michael@0 | 276 | aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&"); |
michael@0 | 277 | else |
michael@0 | 278 | aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&"); |
michael@0 | 279 | } |
michael@0 | 280 | else { |
michael@0 | 281 | // replace the last (n) in the filename with (n+1) |
michael@0 | 282 | aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")"); |
michael@0 | 283 | } |
michael@0 | 284 | } |
michael@0 | 285 | aLocalFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); |
michael@0 | 286 | } |
michael@0 | 287 | catch (e) { |
michael@0 | 288 | dump("*** exception in validateLeafName: " + e + "\n"); |
michael@0 | 289 | |
michael@0 | 290 | if (e.result == Cr.NS_ERROR_FILE_ACCESS_DENIED) |
michael@0 | 291 | throw e; |
michael@0 | 292 | |
michael@0 | 293 | if (aLocalFile.leafName == "" || aLocalFile.isDirectory()) { |
michael@0 | 294 | aLocalFile.append("unnamed"); |
michael@0 | 295 | if (aLocalFile.exists()) |
michael@0 | 296 | aLocalFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); |
michael@0 | 297 | } |
michael@0 | 298 | } |
michael@0 | 299 | }, |
michael@0 | 300 | |
michael@0 | 301 | isUsableDirectory: function hald_isUsableDirectory(aDirectory) { |
michael@0 | 302 | return aDirectory.exists() && aDirectory.isDirectory() && aDirectory.isWritable(); |
michael@0 | 303 | }, |
michael@0 | 304 | }; |
michael@0 | 305 | |
michael@0 | 306 | this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HelperAppLauncherDialog]); |