1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/metro/components/HelperAppDialog.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,306 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +const Cc = Components.classes; 1.9 +const Ci = Components.interfaces; 1.10 +const Cu = Components.utils; 1.11 +const Cr = Components.results; 1.12 + 1.13 +const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir"; 1.14 +const URI_GENERIC_ICON_DOWNLOAD = "chrome://browser/skin/images/alert-downloads-30.png"; 1.15 + 1.16 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.17 +Cu.import("resource://gre/modules/Services.jsm"); 1.18 +Cu.import("resource://gre/modules/DownloadUtils.jsm"); 1.19 + 1.20 +XPCOMUtils.defineLazyGetter(this, "ContentUtil", function() { 1.21 + Cu.import("resource:///modules/ContentUtil.jsm"); 1.22 + return ContentUtil; 1.23 +}); 1.24 +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", 1.25 + "resource://gre/modules/Downloads.jsm"); 1.26 +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", 1.27 + "resource://gre/modules/FileUtils.jsm"); 1.28 +XPCOMUtils.defineLazyModuleGetter(this, "Task", 1.29 + "resource://gre/modules/Task.jsm"); 1.30 + 1.31 +// ----------------------------------------------------------------------- 1.32 +// HelperApp Launcher Dialog 1.33 +// ----------------------------------------------------------------------- 1.34 + 1.35 +function HelperAppLauncherDialog() { } 1.36 + 1.37 +HelperAppLauncherDialog.prototype = { 1.38 + classID: Components.ID("{e9d277a0-268a-4ec2-bb8c-10fdf3e44611}"), 1.39 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]), 1.40 + 1.41 + show: function hald_show(aLauncher, aContext, aReason) { 1.42 + // Check to see if we can open this file or not 1.43 + // If the file is an executable then launchWithApplication will fail in 1.44 + // /uriloader nsMIMEInfoWin.cpp code. So always download in that case. 1.45 + if (aLauncher.MIMEInfo.hasDefaultHandler && !aLauncher.targetFileIsExecutable) { 1.46 + aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.useSystemDefault; 1.47 + aLauncher.launchWithApplication(null, false); 1.48 + } else { 1.49 + let wasClicked = false; 1.50 + this._showDownloadInfobar(aLauncher); 1.51 + } 1.52 + }, 1.53 + 1.54 + _getDownloadSize: function dv__getDownloadSize (aSize) { 1.55 + let displaySize = DownloadUtils.convertByteUnits(aSize); 1.56 + // displaySize[0] is formatted size, displaySize[1] is units 1.57 + if (aSize > 0) 1.58 + return displaySize.join(""); 1.59 + else { 1.60 + let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); 1.61 + return browserBundle.GetStringFromName("downloadsUnknownSize"); 1.62 + } 1.63 + }, 1.64 + 1.65 + _getChromeWindow: function (aWindow) { 1.66 + let chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.67 + .getInterface(Ci.nsIWebNavigation) 1.68 + .QueryInterface(Ci.nsIDocShellTreeItem) 1.69 + .rootTreeItem 1.70 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.71 + .getInterface(Ci.nsIDOMWindow) 1.72 + .QueryInterface(Ci.nsIDOMChromeWindow); 1.73 + return chromeWin; 1.74 + }, 1.75 + 1.76 + _showDownloadInfobar: function do_showDownloadInfobar(aLauncher) { 1.77 + let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); 1.78 + 1.79 + let runButtonText = 1.80 + browserBundle.GetStringFromName("downloadOpen"); 1.81 + let saveButtonText = 1.82 + browserBundle.GetStringFromName("downloadSave"); 1.83 + let cancelButtonText = 1.84 + browserBundle.GetStringFromName("downloadCancel"); 1.85 + 1.86 + let buttons = [ 1.87 + { 1.88 + isDefault: true, 1.89 + label: runButtonText, 1.90 + accessKey: "", 1.91 + callback: function() { 1.92 + aLauncher.saveToDisk(null, false); 1.93 + Services.obs.notifyObservers(aLauncher.targetFile, "dl-run", "true"); 1.94 + } 1.95 + }, 1.96 + { 1.97 + label: saveButtonText, 1.98 + accessKey: "", 1.99 + callback: function() { 1.100 + aLauncher.saveToDisk(null, false); 1.101 + Services.obs.notifyObservers(aLauncher.targetFile, "dl-run", "false"); 1.102 + } 1.103 + }, 1.104 + { 1.105 + label: cancelButtonText, 1.106 + accessKey: "", 1.107 + callback: function() { aLauncher.cancel(Cr.NS_BINDING_ABORTED); } 1.108 + } 1.109 + ]; 1.110 + 1.111 + let window = Services.wm.getMostRecentWindow("navigator:browser"); 1.112 + let chromeWin = this._getChromeWindow(window).wrappedJSObject; 1.113 + let notificationBox = chromeWin.Browser.getNotificationBox(); 1.114 + let document = notificationBox.ownerDocument; 1.115 + let downloadSize = this._getDownloadSize(aLauncher.contentLength); 1.116 + 1.117 + let msg = browserBundle.GetStringFromName("alertDownloadSave2"); 1.118 + 1.119 + let fragment = ContentUtil.populateFragmentFromString( 1.120 + document.createDocumentFragment(), 1.121 + msg, 1.122 + { 1.123 + text: aLauncher.suggestedFileName, 1.124 + className: "download-filename-text" 1.125 + }, 1.126 + { 1.127 + text: downloadSize, 1.128 + className: "download-size-text" 1.129 + }, 1.130 + { 1.131 + text: aLauncher.source.host, 1.132 + className: "download-host-text" 1.133 + } 1.134 + ); 1.135 + let newBar = notificationBox.appendNotification("", 1.136 + "save-download", 1.137 + URI_GENERIC_ICON_DOWNLOAD, 1.138 + notificationBox.PRIORITY_WARNING_HIGH, 1.139 + buttons); 1.140 + let messageContainer = document.getAnonymousElementByAttribute(newBar, "anonid", "messageText"); 1.141 + messageContainer.appendChild(fragment); 1.142 + }, 1.143 + 1.144 + promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) { 1.145 + throw new Components.Exception("Async version must be used", Cr.NS_ERROR_NOT_AVAILABLE); 1.146 + }, 1.147 + 1.148 + promptForSaveToFileAsync: function hald_promptForSaveToFileAsync(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) { 1.149 + let file = null; 1.150 + let prefs = Services.prefs; 1.151 + 1.152 + Task.spawn(function() { 1.153 + if (!aForcePrompt) { 1.154 + // Check to see if the user wishes to auto save to the default download 1.155 + // folder without prompting. Note that preference might not be set. 1.156 + let autodownload = true; 1.157 + try { 1.158 + autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR); 1.159 + } catch (e) { } 1.160 + 1.161 + if (autodownload) { 1.162 + // Retrieve the user's preferred download directory 1.163 + let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); 1.164 + let defaultFolder = new FileUtils.File(preferredDir); 1.165 + 1.166 + try { 1.167 + file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt); 1.168 + } 1.169 + catch (e) { 1.170 + } 1.171 + 1.172 + // Check to make sure we have a valid directory, otherwise, prompt 1.173 + if (file) { 1.174 + aLauncher.saveDestinationAvailable(file); 1.175 + return; 1.176 + } 1.177 + } 1.178 + } 1.179 + 1.180 + // Use file picker to show dialog. 1.181 + let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); 1.182 + let windowTitle = ""; 1.183 + let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); 1.184 + picker.init(parent, windowTitle, Ci.nsIFilePicker.modeSave); 1.185 + picker.defaultString = aDefaultFile; 1.186 + 1.187 + if (aSuggestedFileExt) { 1.188 + // aSuggestedFileExtension includes the period, so strip it 1.189 + picker.defaultExtension = aSuggestedFileExt.substring(1); 1.190 + } 1.191 + else { 1.192 + try { 1.193 + picker.defaultExtension = aLauncher.MIMEInfo.primaryExtension; 1.194 + } 1.195 + catch (e) { } 1.196 + } 1.197 + 1.198 + let wildCardExtension = "*"; 1.199 + if (aSuggestedFileExt) { 1.200 + wildCardExtension += aSuggestedFileExt; 1.201 + picker.appendFilter(aLauncher.MIMEInfo.description, wildCardExtension); 1.202 + } 1.203 + 1.204 + picker.appendFilters(Ci.nsIFilePicker.filterAll); 1.205 + 1.206 + // Default to lastDir if it is valid, otherwise use the user's preferred 1.207 + // downloads directory. getPreferredDownloadsDirectory should always 1.208 + // return a valid directory string, so we can safely default to it. 1.209 + let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); 1.210 + picker.displayDirectory = new FileUtils.File(preferredDir); 1.211 + 1.212 + // The last directory preference may not exist, which will throw. 1.213 + try { 1.214 + let lastDir = prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile); 1.215 + if (isUsableDirectory(lastDir)) 1.216 + picker.displayDirectory = lastDir; 1.217 + } 1.218 + catch (e) { } 1.219 + 1.220 + picker.open(function(aResult) { 1.221 + if (aResult == Ci.nsIFilePicker.returnCancel) { 1.222 + // null result means user cancelled. 1.223 + aLauncher.saveDestinationAvailable(null); 1.224 + return; 1.225 + } 1.226 + 1.227 + // Be sure to save the directory the user chose through the Save As... 1.228 + // dialog as the new browser.download.dir since the old one 1.229 + // didn't exist. 1.230 + file = picker.file; 1.231 + 1.232 + if (file) { 1.233 + try { 1.234 + // Remove the file so that it's not there when we ensure non-existence later; 1.235 + // this is safe because for the file to exist, the user would have had to 1.236 + // confirm that he wanted the file overwritten. 1.237 + if (file.exists()) 1.238 + file.remove(false); 1.239 + } 1.240 + catch (e) { } 1.241 + let newDir = file.parent.QueryInterface(Ci.nsILocalFile); 1.242 + prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir); 1.243 + file = this.validateLeafName(newDir, file.leafName, null); 1.244 + } 1.245 + aLauncher.saveDestinationAvailable(file); 1.246 + }.bind(this)); 1.247 + }.bind(this)); 1.248 + }, 1.249 + 1.250 + validateLeafName: function hald_validateLeafName(aLocalFile, aLeafName, aFileExt) { 1.251 + if (!(aLocalFile && this.isUsableDirectory(aLocalFile))) 1.252 + return null; 1.253 + 1.254 + // Remove any leading periods, since we don't want to save hidden files 1.255 + // automatically. 1.256 + aLeafName = aLeafName.replace(/^\.+/, ""); 1.257 + 1.258 + if (aLeafName == "") 1.259 + aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : ""); 1.260 + aLocalFile.append(aLeafName); 1.261 + 1.262 + this.makeFileUnique(aLocalFile); 1.263 + return aLocalFile; 1.264 + }, 1.265 + 1.266 + makeFileUnique: function hald_makeFileUnique(aLocalFile) { 1.267 + try { 1.268 + // Note - this code is identical to that in 1.269 + // toolkit/content/contentAreaUtils.js. 1.270 + // If you are updating this code, update that code too! We can't share code 1.271 + // here since this is called in a js component. 1.272 + var collisionCount = 0; 1.273 + while (aLocalFile.exists()) { 1.274 + collisionCount++; 1.275 + if (collisionCount == 1) { 1.276 + // Append "(2)" before the last dot in (or at the end of) the filename 1.277 + // special case .ext.gz etc files so we don't wind up with .tar(2).gz 1.278 + if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i)) 1.279 + aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&"); 1.280 + else 1.281 + aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&"); 1.282 + } 1.283 + else { 1.284 + // replace the last (n) in the filename with (n+1) 1.285 + aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")"); 1.286 + } 1.287 + } 1.288 + aLocalFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); 1.289 + } 1.290 + catch (e) { 1.291 + dump("*** exception in validateLeafName: " + e + "\n"); 1.292 + 1.293 + if (e.result == Cr.NS_ERROR_FILE_ACCESS_DENIED) 1.294 + throw e; 1.295 + 1.296 + if (aLocalFile.leafName == "" || aLocalFile.isDirectory()) { 1.297 + aLocalFile.append("unnamed"); 1.298 + if (aLocalFile.exists()) 1.299 + aLocalFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); 1.300 + } 1.301 + } 1.302 + }, 1.303 + 1.304 + isUsableDirectory: function hald_isUsableDirectory(aDirectory) { 1.305 + return aDirectory.exists() && aDirectory.isDirectory() && aDirectory.isWritable(); 1.306 + }, 1.307 +}; 1.308 + 1.309 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HelperAppLauncherDialog]);