diff options
Diffstat (limited to 'toolkit/mozapps/downloads/DownloadLastDir.jsm')
-rw-r--r-- | toolkit/mozapps/downloads/DownloadLastDir.jsm | 194 |
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); + } + }, +}; |