michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir"; michael@0: const URI_GENERIC_ICON_DOWNLOAD = "chrome://browser/skin/images/alert-downloads-30.png"; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/DownloadUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "ContentUtil", function() { michael@0: Cu.import("resource:///modules/ContentUtil.jsm"); michael@0: return ContentUtil; michael@0: }); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Downloads", michael@0: "resource://gre/modules/Downloads.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", michael@0: "resource://gre/modules/FileUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", michael@0: "resource://gre/modules/Task.jsm"); michael@0: michael@0: // ----------------------------------------------------------------------- michael@0: // HelperApp Launcher Dialog michael@0: // ----------------------------------------------------------------------- michael@0: michael@0: function HelperAppLauncherDialog() { } michael@0: michael@0: HelperAppLauncherDialog.prototype = { michael@0: classID: Components.ID("{e9d277a0-268a-4ec2-bb8c-10fdf3e44611}"), michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]), michael@0: michael@0: show: function hald_show(aLauncher, aContext, aReason) { michael@0: // Check to see if we can open this file or not michael@0: // If the file is an executable then launchWithApplication will fail in michael@0: // /uriloader nsMIMEInfoWin.cpp code. So always download in that case. michael@0: if (aLauncher.MIMEInfo.hasDefaultHandler && !aLauncher.targetFileIsExecutable) { michael@0: aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.useSystemDefault; michael@0: aLauncher.launchWithApplication(null, false); michael@0: } else { michael@0: let wasClicked = false; michael@0: this._showDownloadInfobar(aLauncher); michael@0: } michael@0: }, michael@0: michael@0: _getDownloadSize: function dv__getDownloadSize (aSize) { michael@0: let displaySize = DownloadUtils.convertByteUnits(aSize); michael@0: // displaySize[0] is formatted size, displaySize[1] is units michael@0: if (aSize > 0) michael@0: return displaySize.join(""); michael@0: else { michael@0: let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); michael@0: return browserBundle.GetStringFromName("downloadsUnknownSize"); michael@0: } michael@0: }, michael@0: michael@0: _getChromeWindow: function (aWindow) { michael@0: let chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem) michael@0: .rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow) michael@0: .QueryInterface(Ci.nsIDOMChromeWindow); michael@0: return chromeWin; michael@0: }, michael@0: michael@0: _showDownloadInfobar: function do_showDownloadInfobar(aLauncher) { michael@0: let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); michael@0: michael@0: let runButtonText = michael@0: browserBundle.GetStringFromName("downloadOpen"); michael@0: let saveButtonText = michael@0: browserBundle.GetStringFromName("downloadSave"); michael@0: let cancelButtonText = michael@0: browserBundle.GetStringFromName("downloadCancel"); michael@0: michael@0: let buttons = [ michael@0: { michael@0: isDefault: true, michael@0: label: runButtonText, michael@0: accessKey: "", michael@0: callback: function() { michael@0: aLauncher.saveToDisk(null, false); michael@0: Services.obs.notifyObservers(aLauncher.targetFile, "dl-run", "true"); michael@0: } michael@0: }, michael@0: { michael@0: label: saveButtonText, michael@0: accessKey: "", michael@0: callback: function() { michael@0: aLauncher.saveToDisk(null, false); michael@0: Services.obs.notifyObservers(aLauncher.targetFile, "dl-run", "false"); michael@0: } michael@0: }, michael@0: { michael@0: label: cancelButtonText, michael@0: accessKey: "", michael@0: callback: function() { aLauncher.cancel(Cr.NS_BINDING_ABORTED); } michael@0: } michael@0: ]; michael@0: michael@0: let window = Services.wm.getMostRecentWindow("navigator:browser"); michael@0: let chromeWin = this._getChromeWindow(window).wrappedJSObject; michael@0: let notificationBox = chromeWin.Browser.getNotificationBox(); michael@0: let document = notificationBox.ownerDocument; michael@0: let downloadSize = this._getDownloadSize(aLauncher.contentLength); michael@0: michael@0: let msg = browserBundle.GetStringFromName("alertDownloadSave2"); michael@0: michael@0: let fragment = ContentUtil.populateFragmentFromString( michael@0: document.createDocumentFragment(), michael@0: msg, michael@0: { michael@0: text: aLauncher.suggestedFileName, michael@0: className: "download-filename-text" michael@0: }, michael@0: { michael@0: text: downloadSize, michael@0: className: "download-size-text" michael@0: }, michael@0: { michael@0: text: aLauncher.source.host, michael@0: className: "download-host-text" michael@0: } michael@0: ); michael@0: let newBar = notificationBox.appendNotification("", michael@0: "save-download", michael@0: URI_GENERIC_ICON_DOWNLOAD, michael@0: notificationBox.PRIORITY_WARNING_HIGH, michael@0: buttons); michael@0: let messageContainer = document.getAnonymousElementByAttribute(newBar, "anonid", "messageText"); michael@0: messageContainer.appendChild(fragment); michael@0: }, michael@0: michael@0: promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) { michael@0: throw new Components.Exception("Async version must be used", Cr.NS_ERROR_NOT_AVAILABLE); michael@0: }, michael@0: michael@0: promptForSaveToFileAsync: function hald_promptForSaveToFileAsync(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) { michael@0: let file = null; michael@0: let prefs = Services.prefs; michael@0: michael@0: Task.spawn(function() { michael@0: if (!aForcePrompt) { michael@0: // Check to see if the user wishes to auto save to the default download michael@0: // folder without prompting. Note that preference might not be set. michael@0: let autodownload = true; michael@0: try { michael@0: autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR); michael@0: } catch (e) { } michael@0: michael@0: if (autodownload) { michael@0: // Retrieve the user's preferred download directory michael@0: let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); michael@0: let defaultFolder = new FileUtils.File(preferredDir); michael@0: michael@0: try { michael@0: file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt); michael@0: } michael@0: catch (e) { michael@0: } michael@0: michael@0: // Check to make sure we have a valid directory, otherwise, prompt michael@0: if (file) { michael@0: aLauncher.saveDestinationAvailable(file); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Use file picker to show dialog. michael@0: let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); michael@0: let windowTitle = ""; michael@0: let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); michael@0: picker.init(parent, windowTitle, Ci.nsIFilePicker.modeSave); michael@0: picker.defaultString = aDefaultFile; michael@0: michael@0: if (aSuggestedFileExt) { michael@0: // aSuggestedFileExtension includes the period, so strip it michael@0: picker.defaultExtension = aSuggestedFileExt.substring(1); michael@0: } michael@0: else { michael@0: try { michael@0: picker.defaultExtension = aLauncher.MIMEInfo.primaryExtension; michael@0: } michael@0: catch (e) { } michael@0: } michael@0: michael@0: let wildCardExtension = "*"; michael@0: if (aSuggestedFileExt) { michael@0: wildCardExtension += aSuggestedFileExt; michael@0: picker.appendFilter(aLauncher.MIMEInfo.description, wildCardExtension); michael@0: } michael@0: michael@0: picker.appendFilters(Ci.nsIFilePicker.filterAll); michael@0: michael@0: // Default to lastDir if it is valid, otherwise use the user's preferred michael@0: // downloads directory. getPreferredDownloadsDirectory should always michael@0: // return a valid directory string, so we can safely default to it. michael@0: let preferredDir = yield Downloads.getPreferredDownloadsDirectory(); michael@0: picker.displayDirectory = new FileUtils.File(preferredDir); michael@0: michael@0: // The last directory preference may not exist, which will throw. michael@0: try { michael@0: let lastDir = prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile); michael@0: if (isUsableDirectory(lastDir)) michael@0: picker.displayDirectory = lastDir; michael@0: } michael@0: catch (e) { } michael@0: michael@0: picker.open(function(aResult) { michael@0: if (aResult == Ci.nsIFilePicker.returnCancel) { michael@0: // null result means user cancelled. michael@0: aLauncher.saveDestinationAvailable(null); michael@0: return; michael@0: } michael@0: michael@0: // Be sure to save the directory the user chose through the Save As... michael@0: // dialog as the new browser.download.dir since the old one michael@0: // didn't exist. michael@0: file = picker.file; michael@0: michael@0: if (file) { michael@0: try { michael@0: // Remove the file so that it's not there when we ensure non-existence later; michael@0: // this is safe because for the file to exist, the user would have had to michael@0: // confirm that he wanted the file overwritten. michael@0: if (file.exists()) michael@0: file.remove(false); michael@0: } michael@0: catch (e) { } michael@0: let newDir = file.parent.QueryInterface(Ci.nsILocalFile); michael@0: prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir); michael@0: file = this.validateLeafName(newDir, file.leafName, null); michael@0: } michael@0: aLauncher.saveDestinationAvailable(file); michael@0: }.bind(this)); michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: validateLeafName: function hald_validateLeafName(aLocalFile, aLeafName, aFileExt) { michael@0: if (!(aLocalFile && this.isUsableDirectory(aLocalFile))) michael@0: return null; michael@0: michael@0: // Remove any leading periods, since we don't want to save hidden files michael@0: // automatically. michael@0: aLeafName = aLeafName.replace(/^\.+/, ""); michael@0: michael@0: if (aLeafName == "") michael@0: aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : ""); michael@0: aLocalFile.append(aLeafName); michael@0: michael@0: this.makeFileUnique(aLocalFile); michael@0: return aLocalFile; michael@0: }, michael@0: michael@0: makeFileUnique: function hald_makeFileUnique(aLocalFile) { michael@0: try { michael@0: // Note - this code is identical to that in michael@0: // toolkit/content/contentAreaUtils.js. michael@0: // If you are updating this code, update that code too! We can't share code michael@0: // here since this is called in a js component. michael@0: var collisionCount = 0; michael@0: while (aLocalFile.exists()) { michael@0: collisionCount++; michael@0: if (collisionCount == 1) { michael@0: // Append "(2)" before the last dot in (or at the end of) the filename michael@0: // special case .ext.gz etc files so we don't wind up with .tar(2).gz michael@0: if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i)) michael@0: aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&"); michael@0: else michael@0: aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&"); michael@0: } michael@0: else { michael@0: // replace the last (n) in the filename with (n+1) michael@0: aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")"); michael@0: } michael@0: } michael@0: aLocalFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); michael@0: } michael@0: catch (e) { michael@0: dump("*** exception in validateLeafName: " + e + "\n"); michael@0: michael@0: if (e.result == Cr.NS_ERROR_FILE_ACCESS_DENIED) michael@0: throw e; michael@0: michael@0: if (aLocalFile.leafName == "" || aLocalFile.isDirectory()) { michael@0: aLocalFile.append("unnamed"); michael@0: if (aLocalFile.exists()) michael@0: aLocalFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: isUsableDirectory: function hald_isUsableDirectory(aDirectory) { michael@0: return aDirectory.exists() && aDirectory.isDirectory() && aDirectory.isWritable(); michael@0: }, michael@0: }; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HelperAppLauncherDialog]);