Source code

Revision control

Other Tools

/* -*- 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(
);
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);
}
},
};