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.
1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
8 const APK_MIME_TYPE = "application/vnd.android.package-archive";
9 const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
10 const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download";
12 Cu.import("resource://gre/modules/FileUtils.jsm");
13 Cu.import("resource://gre/modules/HelperApps.jsm");
14 Cu.import("resource://gre/modules/Prompt.jsm");
15 Cu.import("resource://gre/modules/Services.jsm");
16 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
18 // -----------------------------------------------------------------------
19 // HelperApp Launcher Dialog
20 // -----------------------------------------------------------------------
22 function HelperAppLauncherDialog() { }
24 HelperAppLauncherDialog.prototype = {
25 classID: Components.ID("{e9d277a0-268a-4ec2-bb8c-10fdf3e44611}"),
26 QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
28 getNativeWindow: function () {
29 try {
30 let win = Services.wm.getMostRecentWindow("navigator:browser");
31 if (win && win.NativeWindow) {
32 return win.NativeWindow;
33 }
34 } catch (e) {
35 }
36 return null;
37 },
39 /**
40 * Returns false if `url` represents a local or special URL that we don't
41 * wish to ever download.
42 *
43 * Returns true otherwise.
44 */
45 _canDownload: function (url, alreadyResolved=false) {
46 // The common case.
47 if (url.schemeIs("http") ||
48 url.schemeIs("https") ||
49 url.schemeIs("ftp")) {
50 return true;
51 }
53 // The less-common opposite case.
54 if (url.schemeIs("chrome") ||
55 url.schemeIs("jar") ||
56 url.schemeIs("resource") ||
57 url.schemeIs("wyciwyg")) {
58 return false;
59 }
61 // For all other URIs, try to resolve them to an inner URI, and check that.
62 if (!alreadyResolved) {
63 let ioSvc = Cc["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
64 let innerURI = ioSvc.newChannelFromURI(url).URI;
65 if (!url.equals(innerURI)) {
66 return this._canDownload(innerURI, true);
67 }
68 }
70 if (url.schemeIs("file")) {
71 // If it's in our app directory or profile directory, we never ever
72 // want to do anything with it, including saving to disk or passing the
73 // file to another application.
74 let file = url.QueryInterface(Ci.nsIFileURL).file;
76 // Normalize the nsILocalFile in-place. This will ensure that paths
77 // can be correctly compared via `contains`, below.
78 file.normalize();
80 // TODO: pref blacklist?
82 let appRoot = FileUtils.getFile("XREExeF", []);
83 if (appRoot.contains(file, true)) {
84 return false;
85 }
87 let profileRoot = FileUtils.getFile("ProfD", []);
88 if (profileRoot.contains(file, true)) {
89 return false;
90 }
92 return true;
93 }
95 // Anything else is fine to download.
96 return true;
97 },
99 /**
100 * Returns true if `launcher` represents a download for which we wish
101 * to prompt.
102 */
103 _shouldPrompt: function (launcher) {
104 let mimeType = this._getMimeTypeFromLauncher(launcher);
106 // Straight equality: nsIMIMEInfo normalizes.
107 return APK_MIME_TYPE == mimeType;
108 },
110 show: function hald_show(aLauncher, aContext, aReason) {
111 if (!this._canDownload(aLauncher.source)) {
112 aLauncher.cancel(Cr.NS_BINDING_ABORTED);
114 let win = this.getNativeWindow();
115 if (!win) {
116 // Oops.
117 Services.console.logStringMessage("Refusing download, but can't show a toast.");
118 return;
119 }
121 Services.console.logStringMessage("Refusing download of non-downloadable file.");
122 let bundle = Services.strings.createBundle("chrome://browser/locale/handling.properties");
123 let failedText = bundle.GetStringFromName("protocol.failed");
124 win.toast.show(failedText, "long");
126 return;
127 }
129 let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
131 let defaultHandler = new Object();
132 let apps = HelperApps.getAppsForUri(aLauncher.source, {
133 mimeType: aLauncher.MIMEInfo.MIMEType,
134 });
136 // Add a fake intent for save to disk at the top of the list.
137 apps.unshift({
138 name: bundle.GetStringFromName("helperapps.saveToDisk"),
139 packageName: "org.mozilla.gecko.Download",
140 iconUri: "drawable://icon",
141 launch: function() {
142 // Reset the preferredAction here.
143 aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.saveToDisk;
144 aLauncher.saveToDisk(null, false);
145 return true;
146 }
147 });
149 // See if the user already marked something as the default for this mimetype,
150 // and if that app is still installed.
151 let preferredApp = this._getPreferredApp(aLauncher);
152 if (preferredApp) {
153 let pref = apps.filter(function(app) {
154 return app.packageName === preferredApp;
155 });
157 if (pref.length > 0) {
158 pref[0].launch(aLauncher.source);
159 return;
160 }
161 }
163 let callback = function(app) {
164 aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;
165 if (!app.launch(aLauncher.source)) {
166 aLauncher.cancel(Cr.NS_BINDING_ABORTED);
167 }
168 }
170 // If there's only one choice, and we don't want to prompt, go right ahead
171 // and choose that app automatically.
172 if (!this._shouldPrompt(aLauncher) && (apps.length === 1)) {
173 callback(apps[0]);
174 return;
175 }
177 // Otherwise, let's go through the prompt.
178 HelperApps.prompt(apps, {
179 title: bundle.GetStringFromName("helperapps.pick"),
180 buttons: [
181 bundle.GetStringFromName("helperapps.alwaysUse"),
182 bundle.GetStringFromName("helperapps.useJustOnce")
183 ]
184 }, (data) => {
185 if (data.button < 0) {
186 return;
187 }
189 callback(apps[data.icongrid0]);
191 if (data.button === 0) {
192 this._setPreferredApp(aLauncher, apps[data.icongrid0]);
193 }
194 });
195 },
197 _getPrefName: function getPrefName(mimetype) {
198 return "browser.download.preferred." + mimetype.replace("\\", ".");
199 },
201 _getMimeTypeFromLauncher: function (launcher) {
202 let mime = launcher.MIMEInfo.MIMEType;
203 if (!mime)
204 mime = ContentAreaUtils.getMIMETypeForURI(launcher.source) || "";
205 return mime;
206 },
208 _getPreferredApp: function getPreferredApp(launcher) {
209 let mime = this._getMimeTypeFromLauncher(launcher);
210 if (!mime)
211 return;
213 try {
214 return Services.prefs.getCharPref(this._getPrefName(mime));
215 } catch(ex) {
216 Services.console.logStringMessage("Error getting pref for " + mime + ".");
217 }
218 return null;
219 },
221 _setPreferredApp: function setPreferredApp(launcher, app) {
222 let mime = this._getMimeTypeFromLauncher(launcher);
223 if (!mime)
224 return;
226 if (app)
227 Services.prefs.setCharPref(this._getPrefName(mime), app.packageName);
228 else
229 Services.prefs.clearUserPref(this._getPrefName(mime));
230 },
232 promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) {
233 // Retrieve the user's default download directory
234 let dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
235 let defaultFolder = dnldMgr.userDownloadsDirectory;
237 try {
238 file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt);
239 } catch (e) { }
241 return file;
242 },
244 validateLeafName: function hald_validateLeafName(aLocalFile, aLeafName, aFileExt) {
245 if (!(aLocalFile && this.isUsableDirectory(aLocalFile)))
246 return null;
248 // Remove any leading periods, since we don't want to save hidden files
249 // automatically.
250 aLeafName = aLeafName.replace(/^\.+/, "");
252 if (aLeafName == "")
253 aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
254 aLocalFile.append(aLeafName);
256 this.makeFileUnique(aLocalFile);
257 return aLocalFile;
258 },
260 makeFileUnique: function hald_makeFileUnique(aLocalFile) {
261 try {
262 // Note - this code is identical to that in
263 // toolkit/content/contentAreaUtils.js.
264 // If you are updating this code, update that code too! We can't share code
265 // here since this is called in a js component.
266 let collisionCount = 0;
267 while (aLocalFile.exists()) {
268 collisionCount++;
269 if (collisionCount == 1) {
270 // Append "(2)" before the last dot in (or at the end of) the filename
271 // special case .ext.gz etc files so we don't wind up with .tar(2).gz
272 if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i))
273 aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
274 else
275 aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&");
276 }
277 else {
278 // replace the last (n) in the filename with (n+1)
279 aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")");
280 }
281 }
282 aLocalFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
283 }
284 catch (e) {
285 dump("*** exception in validateLeafName: " + e + "\n");
287 if (e.result == Cr.NS_ERROR_FILE_ACCESS_DENIED)
288 throw e;
290 if (aLocalFile.leafName == "" || aLocalFile.isDirectory()) {
291 aLocalFile.append("unnamed");
292 if (aLocalFile.exists())
293 aLocalFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
294 }
295 }
296 },
298 isUsableDirectory: function hald_isUsableDirectory(aDirectory) {
299 return aDirectory.exists() && aDirectory.isDirectory() && aDirectory.isWritable();
300 },
301 };
303 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HelperAppLauncherDialog]);