зеркало из https://github.com/mozilla/gecko-dev.git
253 строки
7.7 KiB
JavaScript
253 строки
7.7 KiB
JavaScript
/* -*- 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;
|
|
|
|
import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
XPCOMUtils.defineLazyServiceGetter(
|
|
lazy,
|
|
"cps2",
|
|
"@mozilla.org/content-pref/service;1",
|
|
"nsIContentPrefService2"
|
|
);
|
|
|
|
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 promises = [
|
|
new Promise(resolve =>
|
|
lazy.cps2.removeByName(LAST_DIR_PREF, nonPrivateLoadContext, {
|
|
handleCompletion: resolve,
|
|
})
|
|
),
|
|
new Promise(resolve =>
|
|
lazy.cps2.removeByName(LAST_DIR_PREF, privateLoadContext, {
|
|
handleCompletion: resolve,
|
|
})
|
|
),
|
|
];
|
|
// This is for testing purposes.
|
|
if (aSubject && typeof subject == "object") {
|
|
aSubject.promise = Promise.all(promises);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"isContentPrefEnabled",
|
|
SAVE_PER_SITE_PREF,
|
|
true
|
|
);
|
|
|
|
var gDownloadLastDirFile = readLastDirPref();
|
|
|
|
export class DownloadLastDir {
|
|
// aForcePrivate is only used when aWindow is null.
|
|
constructor(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;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
async getFileAsync(aURI) {
|
|
let plainPrefFile = this.#getLastFile();
|
|
if (!aURI || !lazy.isContentPrefEnabled) {
|
|
return plainPrefFile;
|
|
}
|
|
|
|
return new Promise(resolve => {
|
|
lazy.cps2.getByDomainAndName(
|
|
this.#cpsGroupFromURL(aURI),
|
|
LAST_DIR_PREF,
|
|
this.fakeContext,
|
|
{
|
|
_result: null,
|
|
handleResult(aResult) {
|
|
this._result = aResult;
|
|
},
|
|
handleCompletion(aReason) {
|
|
let file = plainPrefFile;
|
|
if (
|
|
aReason == Ci.nsIContentPrefCallback2.COMPLETE_OK &&
|
|
this._result instanceof Ci.nsIContentPref
|
|
) {
|
|
try {
|
|
file = Cc["@mozilla.org/file/local;1"].createInstance(
|
|
Ci.nsIFile
|
|
);
|
|
file.initWithPath(this._result.value);
|
|
} catch (e) {
|
|
file = plainPrefFile;
|
|
}
|
|
}
|
|
resolve(file);
|
|
},
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
setFile(aURI, aFile) {
|
|
if (aURI && lazy.isContentPrefEnabled) {
|
|
if (aFile instanceof Ci.nsIFile) {
|
|
lazy.cps2.set(
|
|
this.#cpsGroupFromURL(aURI),
|
|
LAST_DIR_PREF,
|
|
aFile.path,
|
|
this.fakeContext
|
|
);
|
|
} else {
|
|
lazy.cps2.removeByDomainAndName(
|
|
this.#cpsGroupFromURL(aURI),
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pre-processor to extract a domain name to be used with the content-prefs
|
|
* service. This specially handles data and file URIs so that the download
|
|
* dirs are recalled in a more consistent way:
|
|
* - all file:/// URIs share the same folder
|
|
* - data: URIs share a folder per mime-type. If a mime-type is not
|
|
* specified text/plain is assumed.
|
|
* In any other case the original URL is returned as a string and ContentPrefs
|
|
* will do its usual parsing.
|
|
*
|
|
* @param {string|nsIURI|URL} url The URL to parse
|
|
* @returns {string} the domain name to use, or the original url.
|
|
*/
|
|
#cpsGroupFromURL(url) {
|
|
if (typeof url == "string") {
|
|
url = new URL(url);
|
|
} else if (url instanceof Ci.nsIURI) {
|
|
url = URL.fromURI(url);
|
|
}
|
|
if (!URL.isInstance(url)) {
|
|
return url;
|
|
}
|
|
if (url.protocol == "data:") {
|
|
return url.href.match(/^data:[^;,]*/i)[0].replace(/:$/, ":text/plain");
|
|
}
|
|
if (url.protocol == "file:") {
|
|
return "file:///";
|
|
}
|
|
return url.href;
|
|
}
|
|
}
|