michael@0: /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * The behavior implemented by gDownloadLastDir is documented here. michael@0: * michael@0: * In normal browsing sessions, gDownloadLastDir uses the browser.download.lastDir michael@0: * preference to store the last used download directory. The first time the user michael@0: * switches into the private browsing mode, the last download directory is michael@0: * preserved to the pref value, but if the user switches to another directory michael@0: * during the private browsing mode, that directory is not stored in the pref, michael@0: * and will be merely kept in memory. When leaving the private browsing mode, michael@0: * this in-memory value will be discarded, and the last download directory michael@0: * will be reverted to the pref value. michael@0: * michael@0: * Both the pref and the in-memory value will be cleared when clearing the michael@0: * browsing history. This effectively changes the last download directory michael@0: * to the default download directory on each platform. michael@0: * michael@0: * If passed a URI, the last used directory is also stored with that URI in the michael@0: * content preferences database. This can be disabled by setting the pref michael@0: * browser.download.lastDir.savePerSite to false. michael@0: */ michael@0: michael@0: const LAST_DIR_PREF = "browser.download.lastDir"; michael@0: const SAVE_PER_SITE_PREF = LAST_DIR_PREF + ".savePerSite"; michael@0: const nsIFile = Components.interfaces.nsIFile; michael@0: michael@0: this.EXPORTED_SYMBOLS = [ "DownloadLastDir" ]; michael@0: michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); michael@0: michael@0: let observer = { michael@0: QueryInterface: function (aIID) { michael@0: if (aIID.equals(Components.interfaces.nsIObserver) || michael@0: aIID.equals(Components.interfaces.nsISupports) || michael@0: aIID.equals(Components.interfaces.nsISupportsWeakReference)) michael@0: return this; michael@0: throw Components.results.NS_NOINTERFACE; michael@0: }, michael@0: observe: function (aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "last-pb-context-exited": michael@0: gDownloadLastDirFile = null; michael@0: break; michael@0: case "browser:purge-session-history": michael@0: gDownloadLastDirFile = null; michael@0: if (Services.prefs.prefHasUserValue(LAST_DIR_PREF)) michael@0: Services.prefs.clearUserPref(LAST_DIR_PREF); michael@0: // Ensure that purging session history causes both the session-only PB cache michael@0: // and persistent prefs to be cleared. michael@0: let cps2 = Components.classes["@mozilla.org/content-pref/service;1"]. michael@0: getService(Components.interfaces.nsIContentPrefService2); michael@0: michael@0: cps2.removeByName(LAST_DIR_PREF, {usePrivateBrowsing: false}); michael@0: cps2.removeByName(LAST_DIR_PREF, {usePrivateBrowsing: true}); michael@0: break; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: let os = Components.classes["@mozilla.org/observer-service;1"] michael@0: .getService(Components.interfaces.nsIObserverService); michael@0: os.addObserver(observer, "last-pb-context-exited", true); michael@0: os.addObserver(observer, "browser:purge-session-history", true); michael@0: michael@0: function readLastDirPref() { michael@0: try { michael@0: return Services.prefs.getComplexValue(LAST_DIR_PREF, nsIFile); michael@0: } michael@0: catch (e) { michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: function isContentPrefEnabled() { michael@0: try { michael@0: return Services.prefs.getBoolPref(SAVE_PER_SITE_PREF); michael@0: } michael@0: catch (e) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: let gDownloadLastDirFile = readLastDirPref(); michael@0: michael@0: this.DownloadLastDir = function DownloadLastDir(aWindow) { michael@0: this.window = aWindow; michael@0: } michael@0: michael@0: DownloadLastDir.prototype = { michael@0: isPrivate: function DownloadLastDir_isPrivate() { michael@0: return PrivateBrowsingUtils.isWindowPrivate(this.window); michael@0: }, michael@0: // compat shims michael@0: get file() this._getLastFile(), michael@0: set file(val) { this.setFile(null, val); }, michael@0: cleanupPrivateFile: function () { michael@0: gDownloadLastDirFile = null; michael@0: }, michael@0: // This function is now deprecated as it uses the sync nsIContentPrefService michael@0: // interface. New consumers should use the getFileAsync function. michael@0: getFile: function (aURI) { michael@0: let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated; michael@0: Deprecated.warning("DownloadLastDir.getFile is deprecated. Please use getFileAsync instead.", michael@0: "https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/DownloadLastDir.jsm", michael@0: Components.stack.caller); michael@0: michael@0: if (aURI && isContentPrefEnabled()) { michael@0: let loadContext = this.window michael@0: .QueryInterface(Components.interfaces.nsIInterfaceRequestor) michael@0: .getInterface(Components.interfaces.nsIWebNavigation) michael@0: .QueryInterface(Components.interfaces.nsILoadContext); michael@0: let lastDir = Services.contentPrefs.getPref(aURI, LAST_DIR_PREF, loadContext); michael@0: if (lastDir) { michael@0: var lastDirFile = Components.classes["@mozilla.org/file/local;1"] michael@0: .createInstance(Components.interfaces.nsIFile); michael@0: lastDirFile.initWithPath(lastDir); michael@0: return lastDirFile; michael@0: } michael@0: } michael@0: return this._getLastFile(); michael@0: }, michael@0: michael@0: _getLastFile: function () { michael@0: if (gDownloadLastDirFile && !gDownloadLastDirFile.exists()) michael@0: gDownloadLastDirFile = null; michael@0: michael@0: if (this.isPrivate()) { michael@0: if (!gDownloadLastDirFile) michael@0: gDownloadLastDirFile = readLastDirPref(); michael@0: return gDownloadLastDirFile; michael@0: } michael@0: return readLastDirPref(); michael@0: }, michael@0: michael@0: getFileAsync: function(aURI, aCallback) { michael@0: let plainPrefFile = this._getLastFile(); michael@0: if (!aURI || !isContentPrefEnabled()) { michael@0: Services.tm.mainThread.dispatch(function() aCallback(plainPrefFile), michael@0: Components.interfaces.nsIThread.DISPATCH_NORMAL); michael@0: return; michael@0: } michael@0: michael@0: let uri = aURI instanceof Components.interfaces.nsIURI ? aURI.spec : aURI; michael@0: let cps2 = Components.classes["@mozilla.org/content-pref/service;1"] michael@0: .getService(Components.interfaces.nsIContentPrefService2); michael@0: let loadContext = this.window michael@0: .QueryInterface(Components.interfaces.nsIInterfaceRequestor) michael@0: .getInterface(Components.interfaces.nsIWebNavigation) michael@0: .QueryInterface(Components.interfaces.nsILoadContext); michael@0: let result = null; michael@0: cps2.getByDomainAndName(uri, LAST_DIR_PREF, loadContext, { michael@0: handleResult: function(aResult) result = aResult, michael@0: handleCompletion: function(aReason) { michael@0: let file = plainPrefFile; michael@0: if (aReason == Components.interfaces.nsIContentPrefCallback2.COMPLETE_OK && michael@0: result instanceof Components.interfaces.nsIContentPref) { michael@0: file = Components.classes["@mozilla.org/file/local;1"] michael@0: .createInstance(Components.interfaces.nsIFile); michael@0: file.initWithPath(result.value); michael@0: } michael@0: aCallback(file); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: setFile: function (aURI, aFile) { michael@0: if (aURI && isContentPrefEnabled()) { michael@0: let uri = aURI instanceof Components.interfaces.nsIURI ? aURI.spec : aURI; michael@0: let cps2 = Components.classes["@mozilla.org/content-pref/service;1"] michael@0: .getService(Components.interfaces.nsIContentPrefService2); michael@0: let loadContext = this.window michael@0: .QueryInterface(Components.interfaces.nsIInterfaceRequestor) michael@0: .getInterface(Components.interfaces.nsIWebNavigation) michael@0: .QueryInterface(Components.interfaces.nsILoadContext); michael@0: if (aFile instanceof Components.interfaces.nsIFile) michael@0: cps2.set(uri, LAST_DIR_PREF, aFile.path, loadContext); michael@0: else michael@0: cps2.removeByDomainAndName(uri, LAST_DIR_PREF, loadContext); michael@0: } michael@0: if (this.isPrivate()) { michael@0: if (aFile instanceof Components.interfaces.nsIFile) michael@0: gDownloadLastDirFile = aFile.clone(); michael@0: else michael@0: gDownloadLastDirFile = null; michael@0: } else { michael@0: if (aFile instanceof Components.interfaces.nsIFile) michael@0: Services.prefs.setComplexValue(LAST_DIR_PREF, nsIFile, aFile); michael@0: else if (Services.prefs.prefHasUserValue(LAST_DIR_PREF)) michael@0: Services.prefs.clearUserPref(LAST_DIR_PREF); michael@0: } michael@0: } michael@0: };