summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/downloads/DownloadLastDir.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/downloads/DownloadLastDir.jsm')
-rw-r--r--toolkit/mozapps/downloads/DownloadLastDir.jsm194
1 files changed, 194 insertions, 0 deletions
diff --git a/toolkit/mozapps/downloads/DownloadLastDir.jsm b/toolkit/mozapps/downloads/DownloadLastDir.jsm
new file mode 100644
index 0000000000..d7a59b2219
--- /dev/null
+++ b/toolkit/mozapps/downloads/DownloadLastDir.jsm
@@ -0,0 +1,194 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * The behavior implemented by gDownloadLastDir is documented here.
+ *
+ * In normal browsing sessions, gDownloadLastDir uses the browser.download.lastDir
+ * preference to store the last used download directory. The first time the user
+ * switches into the private browsing mode, the last download directory is
+ * preserved to the pref value, but if the user switches to another directory
+ * during the private browsing mode, that directory is not stored in the pref,
+ * and will be merely kept in memory. When leaving the private browsing mode,
+ * this in-memory value will be discarded, and the last download directory
+ * will be reverted to the pref value.
+ *
+ * Both the pref and the in-memory value will be cleared when clearing the
+ * browsing history. This effectively changes the last download directory
+ * to the default download directory on each platform.
+ *
+ * If passed a URI, the last used directory is also stored with that URI in the
+ * content preferences database. This can be disabled by setting the pref
+ * browser.download.lastDir.savePerSite to false.
+ */
+
+const LAST_DIR_PREF = "browser.download.lastDir";
+const SAVE_PER_SITE_PREF = LAST_DIR_PREF + ".savePerSite";
+const nsIFile = Ci.nsIFile;
+
+var EXPORTED_SYMBOLS = ["DownloadLastDir"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { PrivateBrowsingUtils } = ChromeUtils.import(
+ "resource://gre/modules/PrivateBrowsingUtils.jsm"
+);
+
+let nonPrivateLoadContext = Cu.createLoadContext();
+let privateLoadContext = Cu.createPrivateLoadContext();
+
+var observer = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsISupportsWeakReference",
+ ]),
+
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "last-pb-context-exited":
+ gDownloadLastDirFile = null;
+ break;
+ case "browser:purge-session-history":
+ gDownloadLastDirFile = null;
+ if (Services.prefs.prefHasUserValue(LAST_DIR_PREF)) {
+ Services.prefs.clearUserPref(LAST_DIR_PREF);
+ }
+ // Ensure that purging session history causes both the session-only PB cache
+ // and persistent prefs to be cleared.
+ let cps2 = Cc["@mozilla.org/content-pref/service;1"].getService(
+ Ci.nsIContentPrefService2
+ );
+
+ cps2.removeByName(LAST_DIR_PREF, nonPrivateLoadContext);
+ cps2.removeByName(LAST_DIR_PREF, privateLoadContext);
+ break;
+ }
+ },
+};
+
+Services.obs.addObserver(observer, "last-pb-context-exited", true);
+Services.obs.addObserver(observer, "browser:purge-session-history", true);
+
+function readLastDirPref() {
+ try {
+ return Services.prefs.getComplexValue(LAST_DIR_PREF, nsIFile);
+ } catch (e) {
+ return null;
+ }
+}
+
+function isContentPrefEnabled() {
+ try {
+ return Services.prefs.getBoolPref(SAVE_PER_SITE_PREF);
+ } catch (e) {
+ return true;
+ }
+}
+
+var gDownloadLastDirFile = readLastDirPref();
+
+// aForcePrivate is only used when aWindow is null.
+function DownloadLastDir(aWindow, aForcePrivate) {
+ let isPrivate = false;
+ if (aWindow === null) {
+ isPrivate = aForcePrivate || PrivateBrowsingUtils.permanentPrivateBrowsing;
+ } else {
+ let loadContext = aWindow.docShell.QueryInterface(Ci.nsILoadContext);
+ isPrivate = loadContext.usePrivateBrowsing;
+ }
+
+ // We always use a fake load context because we may not have one (i.e.,
+ // in the aWindow == null case) and because the load context associated
+ // with aWindow may disappear by the time we need it. This approach is
+ // safe because we only care about the private browsing state. All the
+ // rest of the load context isn't of interest to the content pref service.
+ this.fakeContext = isPrivate ? privateLoadContext : nonPrivateLoadContext;
+}
+
+DownloadLastDir.prototype = {
+ isPrivate: function DownloadLastDir_isPrivate() {
+ return this.fakeContext.usePrivateBrowsing;
+ },
+ // compat shims
+ get file() {
+ return this._getLastFile();
+ },
+ set file(val) {
+ this.setFile(null, val);
+ },
+ cleanupPrivateFile() {
+ gDownloadLastDirFile = null;
+ },
+
+ _getLastFile() {
+ if (gDownloadLastDirFile && !gDownloadLastDirFile.exists()) {
+ gDownloadLastDirFile = null;
+ }
+
+ if (this.isPrivate()) {
+ if (!gDownloadLastDirFile) {
+ gDownloadLastDirFile = readLastDirPref();
+ }
+ return gDownloadLastDirFile;
+ }
+ return readLastDirPref();
+ },
+
+ getFileAsync(aURI, aCallback) {
+ let plainPrefFile = this._getLastFile();
+ if (!aURI || !isContentPrefEnabled()) {
+ Services.tm.dispatchToMainThread(() => aCallback(plainPrefFile));
+ return;
+ }
+
+ let uri = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
+ let cps2 = Cc["@mozilla.org/content-pref/service;1"].getService(
+ Ci.nsIContentPrefService2
+ );
+ let result = null;
+ cps2.getByDomainAndName(uri, LAST_DIR_PREF, this.fakeContext, {
+ handleResult: aResult => (result = aResult),
+ handleCompletion(aReason) {
+ let file = plainPrefFile;
+ if (
+ aReason == Ci.nsIContentPrefCallback2.COMPLETE_OK &&
+ result instanceof Ci.nsIContentPref
+ ) {
+ try {
+ file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(result.value);
+ } catch (e) {
+ file = plainPrefFile;
+ }
+ }
+ aCallback(file);
+ },
+ });
+ },
+
+ setFile(aURI, aFile) {
+ if (aURI && isContentPrefEnabled()) {
+ let uri = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
+ let cps2 = Cc["@mozilla.org/content-pref/service;1"].getService(
+ Ci.nsIContentPrefService2
+ );
+ if (aFile instanceof Ci.nsIFile) {
+ cps2.set(uri, LAST_DIR_PREF, aFile.path, this.fakeContext);
+ } else {
+ cps2.removeByDomainAndName(uri, LAST_DIR_PREF, this.fakeContext);
+ }
+ }
+ if (this.isPrivate()) {
+ if (aFile instanceof Ci.nsIFile) {
+ gDownloadLastDirFile = aFile.clone();
+ } else {
+ gDownloadLastDirFile = null;
+ }
+ } else if (aFile instanceof Ci.nsIFile) {
+ Services.prefs.setComplexValue(LAST_DIR_PREF, nsIFile, aFile);
+ } else if (Services.prefs.prefHasUserValue(LAST_DIR_PREF)) {
+ Services.prefs.clearUserPref(LAST_DIR_PREF);
+ }
+ },
+};