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