--- a/toolkit/components/jsdownloads/src/DownloadCore.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadCore.jsm
@@ -53,20 +53,19 @@ this.EXPORTED_SYMBOLS = [
////////////////////////////////////////////////////////////////////////////////
//// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
+Cu.import("resource://gre/modules/Integration.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DownloadIntegration",
- "resource://gre/modules/DownloadIntegration.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm")
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
@@ -85,16 +84,19 @@ XPCOMUtils.defineLazyServiceGetter(this,
Ci.nsPIExternalAppLauncher);
XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
"@mozilla.org/uriloader/external-helper-app-service;1",
Ci.nsIExternalHelperAppService);
XPCOMUtils.defineLazyServiceGetter(this, "gPrintSettingsService",
"@mozilla.org/gfx/printsettings-service;1",
Ci.nsIPrintSettingsService);
+Integration.downloads.defineModuleGetter(this, "DownloadIntegration",
+ "resource://gre/modules/DownloadIntegration.jsm");
+
const BackgroundFileSaverStreamListener = Components.Constructor(
"@mozilla.org/network/background-file-saver;1?mode=streamlistener",
"nsIBackgroundFileSaver");
/**
* Returns true if the given value is a primitive string or a String object.
*/
function isString(aValue) {
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -18,16 +18,17 @@ this.EXPORTED_SYMBOLS = [
////////////////////////////////////////////////////////////////////////////////
//// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
+Cu.import("resource://gre/modules/Integration.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
@@ -84,16 +85,22 @@ XPCOMUtils.defineLazyGetter(this, "gPare
XPCOMUtils.defineLazyServiceGetter(this, "gApplicationReputationService",
"@mozilla.org/downloads/application-reputation-service;1",
Ci.nsIApplicationReputationService);
XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
"@mozilla.org/telephony/volume-service;1",
"nsIVolumeService");
+// We have to use the gCombinedDownloadIntegration identifier because, in this
+// module only, the DownloadIntegration identifier refers to the base version.
+Integration.downloads.defineModuleGetter(this, "gCombinedDownloadIntegration",
+ "resource://gre/modules/DownloadIntegration.jsm",
+ "DownloadIntegration");
+
const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
"initWithCallback");
/**
* Indicates the delay between a change to the downloads data and the related
* save operation.
*
* For best efficiency, this value should be high enough that the input/output
@@ -142,197 +149,170 @@ const kVerdictMap = {
////////////////////////////////////////////////////////////////////////////////
//// DownloadIntegration
/**
* Provides functions to integrate with the host application, handling for
* example the global prompts on shutdown.
*/
this.DownloadIntegration = {
- // For testing only
- _testMode: false,
- testPromptDownloads: 0,
- dontLoadList: false,
- dontLoadObservers: false,
- dontCheckParentalControls: false,
- shouldBlockInTest: false,
- dontCheckRuntimePermissions: false,
- shouldBlockInTestForRuntimePermissions: false,
-#ifdef MOZ_URL_CLASSIFIER
- dontCheckApplicationReputation: false,
-#else
- dontCheckApplicationReputation: true,
-#endif
- shouldBlockInTestForApplicationReputation: false,
- verdictInTestForApplicationReputation: "",
- shouldKeepBlockedDataInTest: false,
- dontOpenFileAndFolder: false,
- downloadDoneCalled: false,
- _deferTestOpenFile: null,
- _deferTestShowDir: null,
- _deferTestClearPrivateList: null,
-
/**
* Main DownloadStore object for loading and saving the list of persistent
* downloads, or null if the download list was never requested and thus it
* doesn't need to be persisted.
*/
_store: null,
/**
- * Gets and sets test mode
- */
- get testMode() {
- return this._testMode;
- },
- set testMode(mode) {
- this._downloadsDirectory = null;
- return (this._testMode = mode);
- },
-
- /**
* Returns whether data for blocked downloads should be kept on disk.
* Implementations which support unblocking downloads may return true to
* keep the blocked download on disk until its fate is decided.
*
* If a download is blocked and the partial data is kept the Download's
* 'hasBlockedData' property will be true. In this state Download.unblock()
* or Download.confirmBlock() may be used to either unblock the download or
* remove the downloaded data respectively.
*
* Even if shouldKeepBlockedData returns true, if the download did not use a
* partFile the blocked data will be removed - preventing the complete
* download from existing on disk with its final filename.
*
* @return boolean True if data should be kept.
*/
- shouldKeepBlockedData: function() {
- if (this.shouldBlockInTestForApplicationReputation) {
- return this.shouldKeepBlockedDataInTest;
- }
-
+ shouldKeepBlockedData() {
const FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
return Services.appinfo.ID == FIREFOX_ID;
},
/**
* Performs initialization of the list of persistent downloads, before its
* first use by the host application. This function may be called only once
* during the entire lifetime of the application.
*
- * @param aList
+ * @param list
+ * DownloadList object to be initialized.
+ *
+ * @return {Promise}
+ * @resolves When the list has been initialized.
+ * @rejects JavaScript exception.
+ */
+ initializePublicDownloadList: Task.async(function* (list) {
+ try {
+ yield this.loadPublicDownloadListFromStore(list);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ // After the list of persistent downloads has been loaded, we can add the
+ // history observers, even if the load operation failed. This object is kept
+ // alive by the history service.
+ new DownloadHistoryObserver(list);
+ }),
+
+ /**
+ * Called by initializePublicDownloadList to load the list of persistent
+ * downloads, before its first use by the host application. This function may
+ * be called only once during the entire lifetime of the application.
+ *
+ * @param list
* DownloadList object to be populated with the download objects
* serialized from the previous session. This list will be persisted
* to disk during the session lifetime.
*
* @return {Promise}
* @resolves When the list has been populated.
* @rejects JavaScript exception.
*/
- initializePublicDownloadList: function(aList) {
- return Task.spawn(function task_DI_initializePublicDownloadList() {
- if (this.dontLoadList) {
- // In tests, only register the history observer. This object is kept
- // alive by the history service, so we don't keep a reference to it.
- new DownloadHistoryObserver(aList);
- return;
- }
+ loadPublicDownloadListFromStore: Task.async(function* (list) {
+ if (this._store) {
+ throw new Error("Initialization may be performed only once.");
+ }
- if (this._store) {
- throw new Error("initializePublicDownloadList may be called only once.");
- }
+ this._store = new DownloadStore(list, OS.Path.join(
+ OS.Constants.Path.profileDir,
+ "downloads.json"));
+ this._store.onsaveitem = this.shouldPersistDownload.bind(this);
- this._store = new DownloadStore(aList, OS.Path.join(
- OS.Constants.Path.profileDir,
- "downloads.json"));
- this._store.onsaveitem = this.shouldPersistDownload.bind(this);
-
+ try {
if (this._importedFromSqlite) {
- try {
- yield this._store.load();
- } catch (ex) {
- Cu.reportError(ex);
- }
+ yield this._store.load();
} else {
let sqliteDBpath = OS.Path.join(OS.Constants.Path.profileDir,
"downloads.sqlite");
if (yield OS.File.exists(sqliteDBpath)) {
- let sqliteImport = new DownloadImport(aList, sqliteDBpath);
+ let sqliteImport = new DownloadImport(list, sqliteDBpath);
yield sqliteImport.import();
- let importCount = (yield aList.getAll()).length;
+ let importCount = (yield list.getAll()).length;
if (importCount > 0) {
try {
yield this._store.save();
} catch (ex) { }
}
// No need to wait for the file removal.
OS.File.remove(sqliteDBpath).then(null, Cu.reportError);
}
Services.prefs.setBoolPref(kPrefImportedFromSqlite, true);
// Don't even report error here because this file is pre Firefox 3
// and most likely doesn't exist.
OS.File.remove(OS.Path.join(OS.Constants.Path.profileDir,
- "downloads.rdf"));
+ "downloads.rdf")).catch(() => {});
}
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
- // After the list of persistent downloads has been loaded, add the
- // DownloadAutoSaveView and the DownloadHistoryObserver (even if the load
- // operation failed). These objects are kept alive by the underlying
- // DownloadList and by the history service respectively. We wait for a
- // complete initialization of the view used for detecting changes to
- // downloads to be persisted, before other callers get a chance to modify
- // the list without being detected.
- yield new DownloadAutoSaveView(aList, this._store).initialize();
- new DownloadHistoryObserver(aList);
- }.bind(this));
- },
+ // Add the view used for detecting changes to downloads to be persisted.
+ // We must do this after the list of persistent downloads has been loaded,
+ // even if the load operation failed. We wait for a complete initialization
+ // so other callers cannot modify the list without being detected. The
+ // DownloadAutoSaveView is kept alive by the underlying DownloadList.
+ yield new DownloadAutoSaveView(list, this._store).initialize();
+ }),
#ifdef MOZ_WIDGET_GONK
/**
* Finds the default download directory which can be either in the
* internal storage or on the sdcard.
*
* @return {Promise}
* @resolves The downloads directory string path.
*/
- _getDefaultDownloadDirectory: function() {
- return Task.spawn(function() {
- let directoryPath;
- let win = Services.wm.getMostRecentWindow("navigator:browser");
- let storages = win.navigator.getDeviceStorages("sdcard");
- let preferredStorageName;
- // Use the first one or the default storage.
- storages.forEach((aStorage) => {
- if (aStorage.default || !preferredStorageName) {
- preferredStorageName = aStorage.storageName;
- }
- });
-
- // Now get the path for this storage area.
- if (preferredStorageName) {
- let volume = volumeService.getVolumeByName(preferredStorageName);
- if (volume && volume.state === Ci.nsIVolume.STATE_MOUNTED){
- directoryPath = OS.Path.join(volume.mountPoint, "downloads");
- yield OS.File.makeDir(directoryPath, { ignoreExisting: true });
- }
- }
- if (directoryPath) {
- throw new Task.Result(directoryPath);
- } else {
- throw new Components.Exception("No suitable storage for downloads.",
- Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH);
+ _getDefaultDownloadDirectory: Task.async(function* () {
+ let directoryPath;
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let storages = win.navigator.getDeviceStorages("sdcard");
+ let preferredStorageName;
+ // Use the first one or the default storage.
+ storages.forEach((aStorage) => {
+ if (aStorage.default || !preferredStorageName) {
+ preferredStorageName = aStorage.storageName;
}
});
- },
+
+ // Now get the path for this storage area.
+ if (preferredStorageName) {
+ let volume = volumeService.getVolumeByName(preferredStorageName);
+ if (volume && volume.state === Ci.nsIVolume.STATE_MOUNTED){
+ directoryPath = OS.Path.join(volume.mountPoint, "downloads");
+ yield OS.File.makeDir(directoryPath, { ignoreExisting: true });
+ }
+ }
+ if (directoryPath) {
+ return directoryPath;
+ } else {
+ throw new Components.Exception("No suitable storage for downloads.",
+ Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH);
+ }
+ }),
#endif
/**
* Determines if a Download object from the list of persistent downloads
* should be saved into a file, so that it can be restored across sessions.
*
* This function allows filtering out downloads that the host application is
* not interested in persisting across sessions, for example downloads that
@@ -342,18 +322,17 @@ this.DownloadIntegration = {
* The Download object to be inspected. This is originally taken from
* the global DownloadList object for downloads that were not started
* from a private browsing window. The item may have been removed
* from the list since the save operation started, though in this case
* the save operation will be repeated later.
*
* @return True to save the download, false otherwise.
*/
- shouldPersistDownload: function (aDownload)
- {
+ shouldPersistDownload(aDownload) {
// On all platforms, we save all the downloads currently in progress, as
// well as stopped downloads for which we retained partially downloaded
// data or we have blocked data.
if (!aDownload.stopped || aDownload.hasPartialData ||
aDownload.hasBlockedData) {
return true;
}
#ifdef MOZ_B2G
@@ -372,147 +351,134 @@ this.DownloadIntegration = {
},
/**
* Returns the system downloads directory asynchronously.
*
* @return {Promise}
* @resolves The downloads directory string path.
*/
- getSystemDownloadsDirectory: function DI_getSystemDownloadsDirectory() {
- return Task.spawn(function() {
- if (this._downloadsDirectory) {
- // This explicitly makes this function a generator for Task.jsm. We
- // need this because calls to the "yield" operator below may be
- // preprocessed out on some platforms.
- yield undefined;
- throw new Task.Result(this._downloadsDirectory);
- }
+ getSystemDownloadsDirectory: Task.async(function* () {
+ if (this._downloadsDirectory) {
+ return this._downloadsDirectory;
+ }
- let directoryPath = null;
+ let directoryPath = null;
#ifdef XP_MACOSX
- directoryPath = this._getDirectory("DfltDwnld");
+ directoryPath = this._getDirectory("DfltDwnld");
#elifdef XP_WIN
- // For XP/2K, use My Documents/Downloads. Other version uses
- // the default Downloads directory.
- let version = parseFloat(Services.sysinfo.getProperty("version"));
- if (version < 6) {
- directoryPath = yield this._createDownloadsDirectory("Pers");
- } else {
- directoryPath = this._getDirectory("DfltDwnld");
- }
+ // For XP/2K, use My Documents/Downloads. Other version uses
+ // the default Downloads directory.
+ let version = parseFloat(Services.sysinfo.getProperty("version"));
+ if (version < 6) {
+ directoryPath = yield this._createDownloadsDirectory("Pers");
+ } else {
+ directoryPath = this._getDirectory("DfltDwnld");
+ }
#elifdef XP_UNIX
#ifdef MOZ_WIDGET_ANDROID
- // Android doesn't have a $HOME directory, and by default we only have
- // write access to /data/data/org.mozilla.{$APP} and /sdcard
- directoryPath = gEnvironment.get("DOWNLOADS_DIRECTORY");
- if (!directoryPath) {
- throw new Components.Exception("DOWNLOADS_DIRECTORY is not set.",
- Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH);
- }
+ // Android doesn't have a $HOME directory, and by default we only have
+ // write access to /data/data/org.mozilla.{$APP} and /sdcard
+ directoryPath = gEnvironment.get("DOWNLOADS_DIRECTORY");
+ if (!directoryPath) {
+ throw new Components.Exception("DOWNLOADS_DIRECTORY is not set.",
+ Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH);
+ }
#elifdef MOZ_WIDGET_GONK
- directoryPath = this._getDefaultDownloadDirectory();
+ directoryPath = this._getDefaultDownloadDirectory();
#else
- // For Linux, use XDG download dir, with a fallback to Home/Downloads
- // if the XDG user dirs are disabled.
- try {
- directoryPath = this._getDirectory("DfltDwnld");
- } catch(e) {
- directoryPath = yield this._createDownloadsDirectory("Home");
- }
+ // For Linux, use XDG download dir, with a fallback to Home/Downloads
+ // if the XDG user dirs are disabled.
+ try {
+ directoryPath = this._getDirectory("DfltDwnld");
+ } catch(e) {
+ directoryPath = yield this._createDownloadsDirectory("Home");
+ }
#endif
#else
- directoryPath = yield this._createDownloadsDirectory("Home");
+ directoryPath = yield this._createDownloadsDirectory("Home");
#endif
- this._downloadsDirectory = directoryPath;
- throw new Task.Result(this._downloadsDirectory);
- }.bind(this));
- },
+
+ this._downloadsDirectory = directoryPath;
+ return this._downloadsDirectory;
+ }),
_downloadsDirectory: null,
/**
* Returns the user downloads directory asynchronously.
*
* @return {Promise}
* @resolves The downloads directory string path.
*/
- getPreferredDownloadsDirectory: function DI_getPreferredDownloadsDirectory() {
- return Task.spawn(function() {
- let directoryPath = null;
+ getPreferredDownloadsDirectory: Task.async(function* () {
+ let directoryPath = null;
#ifdef MOZ_WIDGET_GONK
- directoryPath = this._getDefaultDownloadDirectory();
+ directoryPath = this._getDefaultDownloadDirectory();
#else
- let prefValue = 1;
+ let prefValue = 1;
+
+ try {
+ prefValue = Services.prefs.getIntPref("browser.download.folderList");
+ } catch(e) {}
- try {
- prefValue = Services.prefs.getIntPref("browser.download.folderList");
- } catch(e) {}
-
- switch(prefValue) {
- case 0: // Desktop
- directoryPath = this._getDirectory("Desk");
- break;
- case 1: // Downloads
+ switch(prefValue) {
+ case 0: // Desktop
+ directoryPath = this._getDirectory("Desk");
+ break;
+ case 1: // Downloads
+ directoryPath = yield this.getSystemDownloadsDirectory();
+ break;
+ case 2: // Custom
+ try {
+ let directory = Services.prefs.getComplexValue("browser.download.dir",
+ Ci.nsIFile);
+ directoryPath = directory.path;
+ yield OS.File.makeDir(directoryPath, { ignoreExisting: true });
+ } catch(ex) {
+ // Either the preference isn't set or the directory cannot be created.
directoryPath = yield this.getSystemDownloadsDirectory();
- break;
- case 2: // Custom
- try {
- let directory = Services.prefs.getComplexValue("browser.download.dir",
- Ci.nsIFile);
- directoryPath = directory.path;
- yield OS.File.makeDir(directoryPath, { ignoreExisting: true });
- } catch(ex) {
- // Either the preference isn't set or the directory cannot be created.
- directoryPath = yield this.getSystemDownloadsDirectory();
- }
- break;
- default:
- directoryPath = yield this.getSystemDownloadsDirectory();
- }
+ }
+ break;
+ default:
+ directoryPath = yield this.getSystemDownloadsDirectory();
+ }
#endif
- throw new Task.Result(directoryPath);
- }.bind(this));
- },
+ return directoryPath;
+ }),
/**
* Returns the temporary downloads directory asynchronously.
*
* @return {Promise}
* @resolves The downloads directory string path.
*/
- getTemporaryDownloadsDirectory: function DI_getTemporaryDownloadsDirectory() {
- return Task.spawn(function() {
- let directoryPath = null;
+ getTemporaryDownloadsDirectory: Task.async(function* () {
+ let directoryPath = null;
#ifdef XP_MACOSX
- directoryPath = yield this.getPreferredDownloadsDirectory();
+ directoryPath = yield this.getPreferredDownloadsDirectory();
#elifdef MOZ_WIDGET_ANDROID
- directoryPath = yield this.getSystemDownloadsDirectory();
+ directoryPath = yield this.getSystemDownloadsDirectory();
#elifdef MOZ_WIDGET_GONK
- directoryPath = yield this.getSystemDownloadsDirectory();
+ directoryPath = yield this.getSystemDownloadsDirectory();
#else
- directoryPath = this._getDirectory("TmpD");
+ directoryPath = this._getDirectory("TmpD");
#endif
- throw new Task.Result(directoryPath);
- }.bind(this));
- },
+ return directoryPath;
+ }),
/**
* Checks to determine whether to block downloads for parental controls.
*
* aParam aDownload
* The download object.
*
* @return {Promise}
* @resolves The boolean indicates to block downloads or not.
*/
- shouldBlockForParentalControls: function DI_shouldBlockForParentalControls(aDownload) {
- if (this.dontCheckParentalControls) {
- return Promise.resolve(this.shouldBlockInTest);
- }
-
+ shouldBlockForParentalControls(aDownload) {
let isEnabled = gParentalControlsService &&
gParentalControlsService.parentalControlsEnabled;
let shouldBlock = isEnabled &&
gParentalControlsService.blockFileDownloadsEnabled;
// Log the event if required by parental controls settings.
if (isEnabled && gParentalControlsService.loggingEnabled) {
gParentalControlsService.log(gParentalControlsService.ePCLog_FileDownload,
@@ -524,21 +490,17 @@ this.DownloadIntegration = {
},
/**
* Checks to determine whether to block downloads for not granted runtime permissions.
*
* @return {Promise}
* @resolves The boolean indicates to block downloads or not.
*/
- shouldBlockForRuntimePermissions: function DI_shouldBlockForRuntimePermissions() {
- if (this.dontCheckRuntimePermissions) {
- return Promise.resolve(this.shouldBlockInTestForRuntimePermissions);
- }
-
+ shouldBlockForRuntimePermissions() {
#ifdef MOZ_WIDGET_ANDROID
return RuntimePermissions.waitForPermissions(RuntimePermissions.WRITE_EXTERNAL_STORAGE)
.then(permissionGranted => !permissionGranted);
#else
return Promise.resolve(false);
#endif
},
@@ -553,23 +515,23 @@ this.DownloadIntegration = {
* @resolves Object with the following properties:
* {
* shouldBlock: Whether the download should be blocked.
* verdict: Detailed reason for the block, according to the
* "Downloads.Error.BLOCK_VERDICT_" constants, or empty
* string if the reason is unknown.
* }
*/
- shouldBlockForReputationCheck: function (aDownload) {
- if (this.dontCheckApplicationReputation) {
- return Promise.resolve({
- shouldBlock: this.shouldBlockInTestForApplicationReputation,
- verdict: this.verdictInTestForApplicationReputation,
- });
- }
+ shouldBlockForReputationCheck(aDownload) {
+#ifndef MOZ_URL_CLASSIFIER
+ return Promise.resolve({
+ shouldBlock: false,
+ verdict: "",
+ });
+#else
let hash;
let sigInfo;
let channelRedirects;
try {
hash = aDownload.saver.getSha256Hash();
sigInfo = aDownload.saver.getSignatureInfo();
channelRedirects = aDownload.saver.getRedirects();
} catch (ex) {
@@ -600,26 +562,27 @@ this.DownloadIntegration = {
redirects: channelRedirects },
function onComplete(aShouldBlock, aRv, aVerdict) {
deferred.resolve({
shouldBlock: aShouldBlock,
verdict: (aShouldBlock && kVerdictMap[aVerdict]) || "",
});
});
return deferred.promise;
+#endif
},
#ifdef XP_WIN
/**
* Checks whether downloaded files should be marked as coming from
* Internet Zone.
*
* @return true if files should be marked
*/
- _shouldSaveZoneInformation: function() {
+ _shouldSaveZoneInformation() {
let key = Cc["@mozilla.org/windows-registry-key;1"]
.createInstance(Ci.nsIWindowsRegKey);
try {
key.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Attachments",
Ci.nsIWindowsRegKey.ACCESS_QUERY_VALUE);
try {
return key.readIntValue("SaveZoneInformation") != 1;
@@ -638,106 +601,103 @@ this.DownloadIntegration = {
*
* aParam aDownload
* The Download object.
*
* @return {Promise}
* @resolves When all the operations completed successfully.
* @rejects JavaScript exception if any of the operations failed.
*/
- downloadDone: function(aDownload) {
- return Task.spawn(function () {
+ downloadDone: Task.async(function* (aDownload) {
#ifdef XP_WIN
- // On Windows, we mark any file saved to the NTFS file system as coming
- // from the Internet security zone unless Group Policy disables the
- // feature. We do this by writing to the "Zone.Identifier" Alternate
- // Data Stream directly, because the Save method of the
- // IAttachmentExecute interface would trigger operations that may cause
- // the application to hang, or other performance issues.
- // The stream created in this way is forward-compatible with all the
- // current and future versions of Windows.
- if (this._shouldSaveZoneInformation()) {
- let zone;
- try {
- zone = gDownloadPlatform.mapUrlToZone(aDownload.source.url);
- } catch (e) {
- // Default to Internet Zone if mapUrlToZone failed for
- // whatever reason.
- zone = Ci.mozIDownloadPlatform.ZONE_INTERNET;
- }
- try {
- // Don't write zone IDs for Local, Intranet, or Trusted sites
- // to match Windows behavior.
- if (zone >= Ci.mozIDownloadPlatform.ZONE_INTERNET) {
- let streamPath = aDownload.target.path + ":Zone.Identifier";
- let stream = yield OS.File.open(streamPath, { create: true });
- try {
- yield stream.write(new TextEncoder().encode("[ZoneTransfer]\r\nZoneId=" + zone + "\r\n"));
- } finally {
- yield stream.close();
- }
- }
- } catch (ex) {
- // If writing to the stream fails, we ignore the error and continue.
- // The Windows API error 123 (ERROR_INVALID_NAME) is expected to
- // occur when working on a file system that does not support
- // Alternate Data Streams, like FAT32, thus we don't report this
- // specific error.
- if (!(ex instanceof OS.File.Error) || ex.winLastError != 123) {
- Cu.reportError(ex);
+ // On Windows, we mark any file saved to the NTFS file system as coming
+ // from the Internet security zone unless Group Policy disables the
+ // feature. We do this by writing to the "Zone.Identifier" Alternate
+ // Data Stream directly, because the Save method of the
+ // IAttachmentExecute interface would trigger operations that may cause
+ // the application to hang, or other performance issues.
+ // The stream created in this way is forward-compatible with all the
+ // current and future versions of Windows.
+ if (this._shouldSaveZoneInformation()) {
+ let zone;
+ try {
+ zone = gDownloadPlatform.mapUrlToZone(aDownload.source.url);
+ } catch (e) {
+ // Default to Internet Zone if mapUrlToZone failed for
+ // whatever reason.
+ zone = Ci.mozIDownloadPlatform.ZONE_INTERNET;
+ }
+ try {
+ // Don't write zone IDs for Local, Intranet, or Trusted sites
+ // to match Windows behavior.
+ if (zone >= Ci.mozIDownloadPlatform.ZONE_INTERNET) {
+ let streamPath = aDownload.target.path + ":Zone.Identifier";
+ let stream = yield OS.File.open(streamPath, { create: true });
+ try {
+ yield stream.write(new TextEncoder().encode("[ZoneTransfer]\r\nZoneId=" + zone + "\r\n"));
+ } finally {
+ yield stream.close();
}
}
- }
-#endif
-
- // The file with the partially downloaded data has restrictive permissions
- // that don't allow other users on the system to access it. Now that the
- // download is completed, we need to adjust permissions based on whether
- // this is a permanently downloaded file or a temporary download to be
- // opened read-only with an external application.
- try {
- // The following logic to determine whether this is a temporary download
- // is due to the fact that "deleteTempFileOnExit" is false on Mac, where
- // downloads to be opened with external applications are preserved in
- // the "Downloads" folder like normal downloads.
- let isTemporaryDownload =
- aDownload.launchWhenSucceeded && (aDownload.source.isPrivate ||
- Services.prefs.getBoolPref("browser.helperApps.deleteTempFileOnExit"));
- // Permanently downloaded files are made accessible by other users on
- // this system, while temporary downloads are marked as read-only.
- let options = {};
- if (isTemporaryDownload) {
- options.unixMode = 0o400;
- options.winAttributes = {readOnly: true};
- } else {
- options.unixMode = 0o666;
- }
- // On Unix, the umask of the process is respected.
- yield OS.File.setPermissions(aDownload.target.path, options);
} catch (ex) {
- // We should report errors with making the permissions less restrictive
- // or marking the file as read-only on Unix and Mac, but this should not
- // prevent the download from completing.
- // The setPermissions API error EPERM is expected to occur when working
- // on a file system that does not support file permissions, like FAT32,
- // thus we don't report this error.
- if (!(ex instanceof OS.File.Error) || ex.unixErrno != OS.Constants.libc.EPERM) {
+ // If writing to the stream fails, we ignore the error and continue.
+ // The Windows API error 123 (ERROR_INVALID_NAME) is expected to
+ // occur when working on a file system that does not support
+ // Alternate Data Streams, like FAT32, thus we don't report this
+ // specific error.
+ if (!(ex instanceof OS.File.Error) || ex.winLastError != 123) {
Cu.reportError(ex);
}
}
+ }
+#endif
- gDownloadPlatform.downloadDone(NetUtil.newURI(aDownload.source.url),
- new FileUtils.File(aDownload.target.path),
- aDownload.contentType,
- aDownload.source.isPrivate);
- this.downloadDoneCalled = true;
- }.bind(this));
- },
+ // The file with the partially downloaded data has restrictive permissions
+ // that don't allow other users on the system to access it. Now that the
+ // download is completed, we need to adjust permissions based on whether
+ // this is a permanently downloaded file or a temporary download to be
+ // opened read-only with an external application.
+ try {
+ // The following logic to determine whether this is a temporary download
+ // is due to the fact that "deleteTempFileOnExit" is false on Mac, where
+ // downloads to be opened with external applications are preserved in
+ // the "Downloads" folder like normal downloads.
+ let isTemporaryDownload =
+ aDownload.launchWhenSucceeded && (aDownload.source.isPrivate ||
+ Services.prefs.getBoolPref("browser.helperApps.deleteTempFileOnExit"));
+ // Permanently downloaded files are made accessible by other users on
+ // this system, while temporary downloads are marked as read-only.
+ let options = {};
+ if (isTemporaryDownload) {
+ options.unixMode = 0o400;
+ options.winAttributes = {readOnly: true};
+ } else {
+ options.unixMode = 0o666;
+ }
+ // On Unix, the umask of the process is respected.
+ yield OS.File.setPermissions(aDownload.target.path, options);
+ } catch (ex) {
+ // We should report errors with making the permissions less restrictive
+ // or marking the file as read-only on Unix and Mac, but this should not
+ // prevent the download from completing.
+ // The setPermissions API error EPERM is expected to occur when working
+ // on a file system that does not support file permissions, like FAT32,
+ // thus we don't report this error.
+ if (!(ex instanceof OS.File.Error) || ex.unixErrno != OS.Constants.libc.EPERM) {
+ Cu.reportError(ex);
+ }
+ }
- /*
+ gDownloadPlatform.downloadDone(NetUtil.newURI(aDownload.source.url),
+ new FileUtils.File(aDownload.target.path),
+ aDownload.contentType,
+ aDownload.source.isPrivate);
+ }),
+
+ /**
* Launches a file represented by the target of a download. This can
* open the file with the default application for the target MIME type
* or file extension, or with a custom application if
* aDownload.launcherPath is set.
*
* @param aDownload
* A Download object that contains the necessary information
* to launch the file. The relevant properties are: the target
@@ -747,231 +707,199 @@ this.DownloadIntegration = {
* @return {Promise}
* @resolves When the instruction to launch the file has been
* successfully given to the operating system. Note that
* the OS might still take a while until the file is actually
* launched.
* @rejects JavaScript exception if there was an error trying to launch
* the file.
*/
- launchDownload: function (aDownload) {
- let deferred = Task.spawn(function DI_launchDownload_task() {
- let file = new FileUtils.File(aDownload.target.path);
+ launchDownload: Task.async(function* (aDownload) {
+ let file = new FileUtils.File(aDownload.target.path);
#ifndef XP_WIN
- // Ask for confirmation if the file is executable, except on Windows where
- // the operating system will show the prompt based on the security zone.
- // We do this here, instead of letting the caller handle the prompt
- // separately in the user interface layer, for two reasons. The first is
- // because of its security nature, so that add-ons cannot forget to do
- // this check. The second is that the system-level security prompt would
- // be displayed at launch time in any case.
- if (file.isExecutable() && !this.dontOpenFileAndFolder) {
- // We don't anchor the prompt to a specific window intentionally, not
- // only because this is the same behavior as the system-level prompt,
- // but also because the most recently active window is the right choice
- // in basically all cases.
- let shouldLaunch = yield DownloadUIHelper.getPrompter()
- .confirmLaunchExecutable(file.path);
- if (!shouldLaunch) {
- return;
- }
- }
+ // Ask for confirmation if the file is executable, except on Windows where
+ // the operating system will show the prompt based on the security zone.
+ // We do this here, instead of letting the caller handle the prompt
+ // separately in the user interface layer, for two reasons. The first is
+ // because of its security nature, so that add-ons cannot forget to do
+ // this check. The second is that the system-level security prompt would
+ // be displayed at launch time in any case.
+ if (file.isExecutable() &&
+ !(yield this.confirmLaunchExecutable(file.path))) {
+ return;
+ }
#endif
- // In case of a double extension, like ".tar.gz", we only
- // consider the last one, because the MIME service cannot
- // handle multiple extensions.
- let fileExtension = null, mimeInfo = null;
- let match = file.leafName.match(/\.([^.]+)$/);
- if (match) {
- fileExtension = match[1];
+ // In case of a double extension, like ".tar.gz", we only
+ // consider the last one, because the MIME service cannot
+ // handle multiple extensions.
+ let fileExtension = null, mimeInfo = null;
+ let match = file.leafName.match(/\.([^.]+)$/);
+ if (match) {
+ fileExtension = match[1];
+ }
+
+ try {
+ // The MIME service might throw if contentType == "" and it can't find
+ // a MIME type for the given extension, so we'll treat this case as
+ // an unknown mimetype.
+ mimeInfo = gMIMEService.getFromTypeAndExtension(aDownload.contentType,
+ fileExtension);
+ } catch (e) { }
+
+ if (aDownload.launcherPath) {
+ if (!mimeInfo) {
+ // This should not happen on normal circumstances because launcherPath
+ // is only set when we had an instance of nsIMIMEInfo to retrieve
+ // the custom application chosen by the user.
+ throw new Error(
+ "Unable to create nsIMIMEInfo to launch a custom application");
}
+ // Custom application chosen
+ let localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
+ .createInstance(Ci.nsILocalHandlerApp);
+ localHandlerApp.executable = new FileUtils.File(aDownload.launcherPath);
+
+ mimeInfo.preferredApplicationHandler = localHandlerApp;
+ mimeInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;
+
+ this.launchFile(file, mimeInfo);
+ return;
+ }
+
+ // No custom application chosen, let's launch the file with the default
+ // handler. First, let's try to launch it through the MIME service.
+ if (mimeInfo) {
+ mimeInfo.preferredAction = Ci.nsIMIMEInfo.useSystemDefault;
+
try {
- // The MIME service might throw if contentType == "" and it can't find
- // a MIME type for the given extension, so we'll treat this case as
- // an unknown mimetype.
- mimeInfo = gMIMEService.getFromTypeAndExtension(aDownload.contentType,
- fileExtension);
- } catch (e) { }
-
- if (aDownload.launcherPath) {
- if (!mimeInfo) {
- // This should not happen on normal circumstances because launcherPath
- // is only set when we had an instance of nsIMIMEInfo to retrieve
- // the custom application chosen by the user.
- throw new Error(
- "Unable to create nsIMIMEInfo to launch a custom application");
- }
-
- // Custom application chosen
- let localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
- .createInstance(Ci.nsILocalHandlerApp);
- localHandlerApp.executable = new FileUtils.File(aDownload.launcherPath);
-
- mimeInfo.preferredApplicationHandler = localHandlerApp;
- mimeInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;
-
- // In test mode, allow the test to verify the nsIMIMEInfo instance.
- if (this.dontOpenFileAndFolder) {
- throw new Task.Result(mimeInfo);
- }
-
- mimeInfo.launchWithFile(file);
- return;
- }
-
- // No custom application chosen, let's launch the file with the default
- // handler. In test mode, we indicate this with a null value.
- if (this.dontOpenFileAndFolder) {
- throw new Task.Result(null);
- }
-
- // First let's try to launch it through the MIME service application
- // handler
- if (mimeInfo) {
- mimeInfo.preferredAction = Ci.nsIMIMEInfo.useSystemDefault;
-
- try {
- mimeInfo.launchWithFile(file);
- return;
- } catch (ex) { }
- }
-
- // If it didn't work or if there was no MIME info available,
- // let's try to directly launch the file.
- try {
- file.launch();
+ this.launchFile(file, mimeInfo);
return;
} catch (ex) { }
-
- // If our previous attempts failed, try sending it through
- // the system's external "file:" URL handler.
- gExternalProtocolService.loadUrl(NetUtil.newURI(file));
- yield undefined;
- }.bind(this));
-
- if (this.dontOpenFileAndFolder) {
- deferred.then((value) => { this._deferTestOpenFile.resolve(value); },
- (error) => { this._deferTestOpenFile.reject(error); });
}
- return deferred;
+ // If it didn't work or if there was no MIME info available,
+ // let's try to directly launch the file.
+ try {
+ this.launchFile(file);
+ return;
+ } catch (ex) { }
+
+ // If our previous attempts failed, try sending it through
+ // the system's external "file:" URL handler.
+ gExternalProtocolService.loadUrl(NetUtil.newURI(file));
+ }),
+
+ /**
+ * Asks for confirmation for launching the specified executable file. This
+ * can be overridden by regression tests to avoid the interactive prompt.
+ */
+ confirmLaunchExecutable: Task.async(function* (path) {
+ // We don't anchor the prompt to a specific window intentionally, not
+ // only because this is the same behavior as the system-level prompt,
+ // but also because the most recently active window is the right choice
+ // in basically all cases.
+ return yield DownloadUIHelper.getPrompter().confirmLaunchExecutable(path);
+ }),
+
+ /**
+ * Launches the specified file, unless overridden by regression tests.
+ */
+ launchFile(file, mimeInfo) {
+ if (mimeInfo) {
+ mimeInfo.launchWithFile(file);
+ } else {
+ file.launch();
+ }
},
- /*
+ /**
* Shows the containing folder of a file.
*
- * @param aFilePath
- * The path to the file.
+ * @param aFilePath
+ * The path to the file.
*
* @return {Promise}
* @resolves When the instruction to open the containing folder has been
* successfully given to the operating system. Note that
* the OS might still take a while until the folder is actually
* opened.
* @rejects JavaScript exception if there was an error trying to open
* the containing folder.
*/
- showContainingDirectory: function (aFilePath) {
- let deferred = Task.spawn(function DI_showContainingDirectory_task() {
- let file = new FileUtils.File(aFilePath);
-
- if (this.dontOpenFileAndFolder) {
- return;
- }
-
- try {
- // Show the directory containing the file and select the file.
- file.reveal();
- return;
- } catch (ex) { }
-
- // If reveal fails for some reason (e.g., it's not implemented on unix
- // or the file doesn't exist), try using the parent if we have it.
- let parent = file.parent;
- if (!parent) {
- throw new Error(
- "Unexpected reference to a top-level directory instead of a file");
- }
+ showContainingDirectory: Task.async(function* (aFilePath) {
+ let file = new FileUtils.File(aFilePath);
- try {
- // Open the parent directory to show where the file should be.
- parent.launch();
- return;
- } catch (ex) { }
+ try {
+ // Show the directory containing the file and select the file.
+ file.reveal();
+ return;
+ } catch (ex) { }
- // If launch also fails (probably because it's not implemented), let
- // the OS handler try to open the parent.
- gExternalProtocolService.loadUrl(NetUtil.newURI(parent));
- yield undefined;
- }.bind(this));
-
- if (this.dontOpenFileAndFolder) {
- deferred.then((value) => { this._deferTestShowDir.resolve("success"); },
- (error) => {
- // Ensure that _deferTestShowDir has at least one consumer
- // for the error, otherwise the error will be reported as
- // uncaught.
- this._deferTestShowDir.promise.then(null, function() {});
- this._deferTestShowDir.reject(error);
- });
+ // If reveal fails for some reason (e.g., it's not implemented on unix
+ // or the file doesn't exist), try using the parent if we have it.
+ let parent = file.parent;
+ if (!parent) {
+ throw new Error(
+ "Unexpected reference to a top-level directory instead of a file");
}
- return deferred;
- },
+ try {
+ // Open the parent directory to show where the file should be.
+ parent.launch();
+ return;
+ } catch (ex) { }
+
+ // If launch also fails (probably because it's not implemented), let
+ // the OS handler try to open the parent.
+ gExternalProtocolService.loadUrl(NetUtil.newURI(parent));
+ }),
/**
* Calls the directory service, create a downloads directory and returns an
* nsIFile for the downloads directory.
*
* @return {Promise}
* @resolves The directory string path.
*/
- _createDownloadsDirectory: function DI_createDownloadsDirectory(aName) {
+ _createDownloadsDirectory(aName) {
// We read the name of the directory from the list of translated strings
// that is kept by the UI helper module, even if this string is not strictly
// displayed in the user interface.
let directoryPath = OS.Path.join(this._getDirectory(aName),
DownloadUIHelper.strings.downloadsFolder);
// Create the Downloads folder and ignore if it already exists.
- return OS.File.makeDir(directoryPath, { ignoreExisting: true }).
- then(function() {
- return directoryPath;
- });
+ return OS.File.makeDir(directoryPath, { ignoreExisting: true })
+ .then(() => directoryPath);
},
/**
- * Calls the directory service and returns an nsIFile for the requested
- * location name.
- *
- * @return The directory string path.
+ * Returns the string path for the given directory service location name. This
+ * can be overridden by regression tests to return the path of the system
+ * temporary directory in all cases.
*/
- _getDirectory: function DI_getDirectory(aName) {
- return Services.dirsvc.get(this.testMode ? "TmpD" : aName, Ci.nsIFile).path;
+ _getDirectory(name) {
+ return Services.dirsvc.get(name, Ci.nsIFile).path;
},
/**
* Register the downloads interruption observers.
*
* @param aList
* The public or private downloads list.
* @param aIsPrivate
* True if the list is private, false otherwise.
*
* @return {Promise}
* @resolves When the views and observers are added.
*/
- addListObservers: function DI_addListObservers(aList, aIsPrivate) {
- if (this.dontLoadObservers) {
- return Promise.resolve();
- }
-
+ addListObservers(aList, aIsPrivate) {
DownloadObserver.registerView(aList, aIsPrivate);
if (!DownloadObserver.observersAdded) {
DownloadObserver.observersAdded = true;
for (let topic of kObserverTopics) {
Services.obs.addObserver(DownloadObserver, topic, false);
}
}
return Promise.resolve();
@@ -979,17 +907,17 @@ this.DownloadIntegration = {
/**
* Force a save on _store if it exists. Used to ensure downloads do not
* persist after being sanitized on Android.
*
* @return {Promise}
* @resolves When _store.save() completes.
*/
- forceSave: function DI_forceSave() {
+ forceSave() {
if (this._store) {
return this._store.save();
}
return Promise.resolve();
},
/**
* Checks if we have already imported (or attempted to import)
@@ -1096,18 +1024,18 @@ this.DownloadObserver = {
*/
_confirmCancelDownloads: function DO_confirmCancelDownload(
aCancel, aDownloadsCount, aPrompter, aPromptType) {
// If user has already dismissed the request, then do nothing.
if ((aCancel instanceof Ci.nsISupportsPRBool) && aCancel.data) {
return;
}
// Handle test mode
- if (DownloadIntegration.testMode) {
- DownloadIntegration.testPromptDownloads = aDownloadsCount;
+ if (gCombinedDownloadIntegration._testPromptDownloads) {
+ gCombinedDownloadIntegration._testPromptDownloads = aDownloadsCount;
return;
}
aCancel.data = aPrompter.confirmCancelDownloads(aDownloadsCount, aPromptType);
},
/**
* Resume all downloads that were paused when going offline, used when waking
@@ -1139,30 +1067,31 @@ this.DownloadObserver = {
this._confirmCancelDownloads(aSubject, downloadsCount, p, p.ON_OFFLINE);
break;
case "last-pb-context-exiting":
downloadsCount = this._privateInProgressDownloads.size;
this._confirmCancelDownloads(aSubject, downloadsCount, p,
p.ON_LEAVE_PRIVATE_BROWSING);
break;
case "last-pb-context-exited":
- let deferred = Task.spawn(function() {
+ let promise = Task.spawn(function() {
let list = yield Downloads.getList(Downloads.PRIVATE);
let downloads = yield list.getAll();
// We can remove the downloads and finalize them in parallel.
for (let download of downloads) {
list.remove(download).then(null, Cu.reportError);
download.finalize(true).then(null, Cu.reportError);
}
});
// Handle test mode
- if (DownloadIntegration.testMode) {
- deferred.then((value) => { DownloadIntegration._deferTestClearPrivateList.resolve("success"); },
- (error) => { DownloadIntegration._deferTestClearPrivateList.reject(error); });
+ if (gCombinedDownloadIntegration._testResolveClearPrivateList) {
+ gCombinedDownloadIntegration._testResolveClearPrivateList(promise);
+ } else {
+ promise.catch(ex => Cu.reportError(ex));
}
break;
case "sleep_notification":
case "suspend_process_notification":
case "network:offline-about-to-go-offline":
for (let download of this._publicInProgressDownloads) {
download.cancel();
this._canceledOfflineDownloads.add(download);
@@ -1344,27 +1273,27 @@ this.DownloadAutoSaveView.prototype = {
this._writer.arm();
},
//////////////////////////////////////////////////////////////////////////////
//// DownloadList view
onDownloadAdded: function (aDownload)
{
- if (DownloadIntegration.shouldPersistDownload(aDownload)) {
+ if (gCombinedDownloadIntegration.shouldPersistDownload(aDownload)) {
this._downloadsMap.set(aDownload, aDownload.getSerializationHash());
if (this._initialized) {
this.saveSoon();
}
}
},
onDownloadChanged: function (aDownload)
{
- if (!DownloadIntegration.shouldPersistDownload(aDownload)) {
+ if (!gCombinedDownloadIntegration.shouldPersistDownload(aDownload)) {
if (this._downloadsMap.has(aDownload)) {
this._downloadsMap.delete(aDownload);
this.saveSoon();
}
return;
}
let hash = aDownload.getSerializationHash();
--- a/toolkit/components/jsdownloads/src/Downloads.jsm
+++ b/toolkit/components/jsdownloads/src/Downloads.jsm
@@ -17,34 +17,36 @@ this.EXPORTED_SYMBOLS = [
////////////////////////////////////////////////////////////////////////////////
//// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
+Cu.import("resource://gre/modules/Integration.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/DownloadCore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadCombinedList",
"resource://gre/modules/DownloadList.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DownloadIntegration",
- "resource://gre/modules/DownloadIntegration.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadList",
"resource://gre/modules/DownloadList.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadSummary",
"resource://gre/modules/DownloadList.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
"resource://gre/modules/DownloadUIHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
+Integration.downloads.defineModuleGetter(this, "DownloadIntegration",
+ "resource://gre/modules/DownloadIntegration.jsm");
+
////////////////////////////////////////////////////////////////////////////////
//// Downloads
/**
* This object is exposed directly to the consumers of this JavaScript module,
* and provides the only entry point to get references to back-end objects.
*/
this.Downloads = {
--- a/toolkit/components/jsdownloads/test/unit/common_test_Download.js
+++ b/toolkit/components/jsdownloads/test/unit/common_test_Download.js
@@ -125,16 +125,55 @@ function promisePartFileReady(aDownload)
*/
var promiseVerifyTarget = Task.async(function* (downloadTarget,
expectedContents) {
yield promiseVerifyContents(downloadTarget.path, expectedContents);
do_check_true(downloadTarget.exists);
do_check_eq(downloadTarget.size, expectedContents.length);
});
+/**
+ * Waits for an attempt to launch a file, and returns the nsIMIMEInfo used for
+ * the launch, or null if the file was launched with the default handler.
+ */
+function waitForFileLaunched() {
+ return new Promise(resolve => {
+ let waitFn = base => ({
+ launchFile(file, mimeInfo) {
+ Integration.downloads.unregister(waitFn);
+ if (!mimeInfo ||
+ mimeInfo.preferredAction == Ci.nsIMIMEInfo.useSystemDefault) {
+ resolve(null);
+ } else {
+ resolve(mimeInfo);
+ }
+ return Promise.resolve();
+ },
+ });
+ Integration.downloads.register(waitFn);
+ });
+}
+
+/**
+ * Waits for an attempt to show the directory where a file is located, and
+ * returns the path of the file.
+ */
+function waitForDirectoryShown() {
+ return new Promise(resolve => {
+ let waitFn = base => ({
+ showContainingDirectory(path) {
+ Integration.downloads.unregister(waitFn);
+ resolve(path);
+ return Promise.resolve();
+ },
+ });
+ Integration.downloads.register(waitFn);
+ });
+}
+
////////////////////////////////////////////////////////////////////////////////
//// Tests
/**
* Executes a download and checks its basic properties after construction.
* The download is started by constructing the simplest Download object with
* the "copy" saver, or using the legacy nsITransfer interface.
*/
@@ -1570,21 +1609,25 @@ add_task(function* test_cancel_midway_re
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
});
/**
* Download with parental controls enabled.
*/
add_task(function* test_blocked_parental_controls()
{
+ let blockFn = base => ({
+ shouldBlockForParentalControls: () => Promise.resolve(true),
+ });
+
+ Integration.downloads.register(blockFn);
function cleanup() {
- DownloadIntegration.shouldBlockInTest = false;
+ Integration.downloads.unregister(blockFn);
}
do_register_cleanup(cleanup);
- DownloadIntegration.shouldBlockInTest = true;
let download;
try {
if (!gUseLegacySaver) {
// When testing DownloadCopySaver, we want to check that the promise
// returned by the "start" method is rejected.
download = yield promiseNewDownload();
yield download.start();
@@ -1640,21 +1683,25 @@ add_task(function* test_blocked_parental
do_check_false(yield OS.File.exists(download.target.path));
});
/**
* Download with runtime permissions
*/
add_task(function* test_blocked_runtime_permissions()
{
+ let blockFn = base => ({
+ shouldBlockForRuntimePermissions: () => Promise.resolve(true),
+ });
+
+ Integration.downloads.register(blockFn);
function cleanup() {
- DownloadIntegration.shouldBlockInTestForRuntimePermissions = false;
+ Integration.downloads.unregister(blockFn);
}
do_register_cleanup(cleanup);
- DownloadIntegration.shouldBlockInTestForRuntimePermissions = true;
let download;
try {
if (!gUseLegacySaver) {
// When testing DownloadCopySaver, we want to check that the promise
// returned by the "start" method is rejected.
download = yield promiseNewDownload();
yield download.start();
@@ -1704,33 +1751,34 @@ add_task(function* test_getSha256Hash()
* keepPartialData: bool,
* keepBlockedData: bool,
* }
* @return {Promise}
* @resolves The reputation blocked download.
* @rejects JavaScript exception.
*/
var promiseBlockedDownload = Task.async(function* (options) {
+ let blockFn = base => ({
+ shouldBlockForReputationCheck: () => Promise.resolve({
+ shouldBlock: true,
+ verdict: Downloads.Error.BLOCK_VERDICT_UNCOMMON,
+ }),
+ shouldKeepBlockedData: () => Promise.resolve(options.keepBlockedData),
+ });
+
+ Integration.downloads.register(blockFn);
function cleanup() {
- DownloadIntegration.shouldBlockInTestForApplicationReputation = false;
- DownloadIntegration.verdictInTestForApplicationReputation = "";
- DownloadIntegration.shouldKeepBlockedDataInTest = false;
+ Integration.downloads.unregister(blockFn);
}
do_register_cleanup(cleanup);
- let {keepPartialData, keepBlockedData} = options;
- DownloadIntegration.shouldBlockInTestForApplicationReputation = true;
- DownloadIntegration.verdictInTestForApplicationReputation =
- Downloads.Error.BLOCK_VERDICT_UNCOMMON;
- DownloadIntegration.shouldKeepBlockedDataInTest = keepBlockedData;
-
let download;
try {
- if (keepPartialData) {
+ if (options.keepPartialData) {
download = yield promiseStartDownload_tryToKeepPartialData();
continueResponses();
} else if (gUseLegacySaver) {
download = yield promiseStartLegacyDownload();
} else {
download = yield promiseNewDownload();
yield download.start();
do_throw("The download should have blocked.");
@@ -1939,27 +1987,28 @@ add_task(function* test_blocked_applicat
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
});
/**
* download.showContainingDirectory() action
*/
add_task(function* test_showContainingDirectory() {
- DownloadIntegration._deferTestShowDir = Promise.defer();
-
let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
let download = yield Downloads.createDownload({
source: { url: httpUrl("source.txt") },
target: ""
});
+ let promiseDirectoryShown = waitForDirectoryShown();
+ yield download.showContainingDirectory();
+ let path = yield promiseDirectoryShown;
try {
- yield download.showContainingDirectory();
+ new FileUtils.File(path);
do_throw("Should have failed because of an invalid path.");
} catch (ex) {
if (!(ex instanceof Components.Exception)) {
throw ex;
}
// Invalid paths on Windows are reported with NS_ERROR_FAILURE,
// but with NS_ERROR_FILE_UNRECOGNIZED_PATH on Mac/Linux
let validResult = ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH ||
@@ -1967,21 +2016,19 @@ add_task(function* test_showContainingDi
do_check_true(validResult);
}
download = yield Downloads.createDownload({
source: { url: httpUrl("source.txt") },
target: targetPath
});
-
- DownloadIntegration._deferTestShowDir = Promise.defer();
+ promiseDirectoryShown = waitForDirectoryShown();
download.showContainingDirectory();
- let result = yield DownloadIntegration._deferTestShowDir.promise;
- do_check_eq(result, "success");
+ yield promiseDirectoryShown;
});
/**
* download.launch() action
*/
add_task(function* test_launch() {
let customLauncher = getTempFile("app-launcher");
@@ -2014,19 +2061,19 @@ add_task(function* test_launch() {
httpUrl("source.txt"),
{ launcherPath: launcherPath,
launchWhenSucceeded: true });
yield promiseDownloadStopped(download);
}
do_check_true(download.launchWhenSucceeded);
- DownloadIntegration._deferTestOpenFile = Promise.defer();
+ let promiseFileLaunched = waitForFileLaunched();
download.launch();
- let result = yield DownloadIntegration._deferTestOpenFile.promise;
+ let result = yield promiseFileLaunched;
// Verify that the results match the test case.
if (!launcherPath) {
// This indicates that the default handler has been chosen.
do_check_true(result === null);
} else {
// Check the nsIMIMEInfo instance that would have been used for launching.
do_check_eq(result.preferredAction, Ci.nsIMIMEInfo.useHelperApp);
@@ -2042,21 +2089,33 @@ add_task(function* test_launch() {
*/
add_task(function* test_launcherPath_invalid() {
let download = yield Downloads.createDownload({
source: { url: httpUrl("source.txt") },
target: { path: getTempFile(TEST_TARGET_FILE_NAME).path },
launcherPath: " "
});
- DownloadIntegration._deferTestOpenFile = Promise.defer();
+ let promiseDownloadLaunched = new Promise(resolve => {
+ let waitFn = base => ({
+ __proto__: base,
+ launchDownload() {
+ Integration.downloads.unregister(waitFn);
+ let superPromise = super.launchDownload(...arguments);
+ resolve(superPromise);
+ return superPromise;
+ },
+ });
+ Integration.downloads.register(waitFn);
+ });
+
yield download.start();
try {
download.launch();
- result = yield DownloadIntegration._deferTestOpenFile.promise;
+ yield promiseDownloadLaunched;
do_throw("Can't launch file with invalid custom launcher")
} catch (ex) {
if (!(ex instanceof Components.Exception)) {
throw ex;
}
// Invalid paths on Windows are reported with NS_ERROR_FAILURE,
// but with NS_ERROR_FILE_UNRECOGNIZED_PATH on Mac/Linux
let validResult = ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH ||
@@ -2069,17 +2128,17 @@ add_task(function* test_launcherPath_inv
* Tests that download.launch() is automatically called after
* the download finishes if download.launchWhenSucceeded = true
*/
add_task(function* test_launchWhenSucceeded() {
let customLauncher = getTempFile("app-launcher");
// Test both with and without setting a custom application.
for (let launcherPath of [null, customLauncher.path]) {
- DownloadIntegration._deferTestOpenFile = Promise.defer();
+ let promiseFileLaunched = waitForFileLaunched();
if (!gUseLegacySaver) {
let download = yield Downloads.createDownload({
source: httpUrl("source.txt"),
target: getTempFile(TEST_TARGET_FILE_NAME).path,
launchWhenSucceeded: true,
launcherPath: launcherPath,
});
@@ -2087,17 +2146,17 @@ add_task(function* test_launchWhenSuccee
} else {
let download = yield promiseStartLegacyDownload(
httpUrl("source.txt"),
{ launcherPath: launcherPath,
launchWhenSucceeded: true });
yield promiseDownloadStopped(download);
}
- let result = yield DownloadIntegration._deferTestOpenFile.promise;
+ let result = yield promiseFileLaunched;
// Verify that the results match the test case.
if (!launcherPath) {
// This indicates that the default handler has been chosen.
do_check_true(result === null);
} else {
// Check the nsIMIMEInfo instance that would have been used for launching.
do_check_eq(result.preferredAction, Ci.nsIMIMEInfo.useHelperApp);
@@ -2156,27 +2215,39 @@ add_task(function* test_platform_integra
observe: function(subject, topic, data) {
do_check_eq(topic, "download-watcher-notify");
do_check_eq(data, "modified");
downloadWatcherNotified = true;
}
}
Services.obs.addObserver(observer, "download-watcher-notify", false);
Services.prefs.setBoolPref("device.storage.enabled", true);
+ let downloadDoneCalled = false;
+ let monitorFn = base => ({
+ __proto__: base,
+ downloadDone() {
+ return super.downloadDone(...arguments).then(() => {
+ downloadDoneCalled = true;
+ });
+ },
+ });
+ Integration.downloads.register(monitorFn);
+ DownloadIntegration.allowDirectories = true;
function cleanup() {
for (let file of downloadFiles) {
file.remove(true);
}
Services.obs.removeObserver(observer, "download-watcher-notify");
Services.prefs.setBoolPref("device.storage.enabled", oldDeviceStorageEnabled);
+ Integration.downloads.unregister(monitorFn);
+ DownloadIntegration.allowDirectories = false;
}
- do_register_cleanup(cleanup);
for (let isPrivate of [false, true]) {
- DownloadIntegration.downloadDoneCalled = false;
+ downloadDoneCalled = false;
// Some platform specific operations only operate on files outside the
// temporary directory or in the Downloads directory (such as setting
// the Windows searchable attribute, and the Mac Downloads icon bouncing),
// so use the system Downloads directory for the target file.
let targetFilePath = yield DownloadIntegration.getSystemDownloadsDirectory();
targetFilePath = OS.Path.join(targetFilePath,
"test" + (Math.floor(Math.random() * 1000000)));
@@ -2194,25 +2265,27 @@ add_task(function* test_platform_integra
target: targetFile,
});
download.start().catch(() => {});
}
// Wait for the whenSucceeded promise to be resolved first.
// downloadDone should be called before the whenSucceeded promise is resolved.
yield download.whenSucceeded().then(function () {
- do_check_true(DownloadIntegration.downloadDoneCalled);
+ do_check_true(downloadDoneCalled);
do_check_true(downloadWatcherNotified);
});
// Then, wait for the promise returned by "start" to be resolved.
yield promiseDownloadStopped(download);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
}
+
+ cleanup();
});
/**
* Checks that downloads are added to browsing history when they start.
*/
add_task(function* test_history()
{
mustInterruptResponses();
--- a/toolkit/components/jsdownloads/test/unit/head.js
+++ b/toolkit/components/jsdownloads/test/unit/head.js
@@ -12,22 +12,21 @@
////////////////////////////////////////////////////////////////////////////////
//// Globals
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;
+Cu.import("resource://gre/modules/Integration.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
"resource://gre/modules/DownloadPaths.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DownloadIntegration",
- "resource://gre/modules/DownloadIntegration.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
"resource://testing-common/httpd.js");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
@@ -45,16 +44,19 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MockRegistrar",
"resource://testing-common/MockRegistrar.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
"@mozilla.org/uriloader/external-helper-app-service;1",
Ci.nsIExternalHelperAppService);
+Integration.downloads.defineModuleGetter(this, "DownloadIntegration",
+ "resource://gre/modules/DownloadIntegration.jsm");
+
const ServerSocket = Components.Constructor(
"@mozilla.org/network/server-socket;1",
"nsIServerSocket",
"init");
const BinaryOutputStream = Components.Constructor(
"@mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream",
"setOutputStream")
@@ -782,33 +784,49 @@ add_task(function test_common_initialize
// This URL will emulate being blocked by Windows Parental controls
gHttpServer.registerPathHandler("/parentalblocked.zip",
function (aRequest, aResponse) {
aResponse.setStatusLine(aRequest.httpVersion, 450,
"Blocked by Windows Parental Controls");
});
- // Disable integration with the host application requiring profile access.
- DownloadIntegration.dontLoadList = true;
- DownloadIntegration.dontLoadObservers = true;
- // Disable the parental controls checking.
- DownloadIntegration.dontCheckParentalControls = true;
- // Disable application reputation checks.
- DownloadIntegration.dontCheckApplicationReputation = true;
- // Disable the calls to the OS to launch files and open containing folders
- DownloadIntegration.dontOpenFileAndFolder = true;
- DownloadIntegration._deferTestOpenFile = Promise.defer();
- DownloadIntegration._deferTestShowDir = Promise.defer();
- // Disable checking runtime permissions.
- DownloadIntegration.dontCheckRuntimePermissions = true;
-
- // Avoid leaking uncaught promise errors
- DownloadIntegration._deferTestOpenFile.promise.then(null, () => undefined);
- DownloadIntegration._deferTestShowDir.promise.then(null, () => undefined);
+ // During unit tests, most of the functions that require profile access or
+ // operating system features will be disabled. Individual tests may override
+ // them again to check for specific behaviors.
+ Integration.downloads.register(base => ({
+ __proto__: base,
+ loadPublicDownloadListFromStore: () => Promise.resolve(),
+ shouldKeepBlockedData: () => Promise.resolve(false),
+ shouldBlockForParentalControls: () => Promise.resolve(false),
+ shouldBlockForRuntimePermissions: () => Promise.resolve(false),
+ shouldBlockForReputationCheck: () => Promise.resolve({
+ shouldBlock: false,
+ verdict: "",
+ }),
+ confirmLaunchExecutable: () => Promise.resolve(),
+ launchFile: () => Promise.resolve(),
+ showContainingDirectory: () => Promise.resolve(),
+ // This flag allows re-enabling the default observers during their tests.
+ allowObservers: false,
+ addListObservers() {
+ return this.allowObservers ? super.addListObservers(...arguments)
+ : Promise.resolve();
+ },
+ // This flag allows re-enabling the download directory logic for its tests.
+ _allowDirectories: false,
+ set allowDirectories(value) {
+ this._allowDirectories = value;
+ // We have to invalidate the previously computed directory path.
+ this._downloadsDirectory = null;
+ },
+ _getDirectory(name) {
+ return super._getDirectory(this._allowDirectories ? name : "TmpD");
+ },
+ }));
// Get a reference to nsIComponentRegistrar, and ensure that is is freed
// before the XPCOM shutdown.
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
do_register_cleanup(() => registrar = null);
// Make sure that downloads started using nsIExternalHelperAppService are
// saved to disk without asking for a destination interactively.
--- a/toolkit/components/jsdownloads/test/unit/test_DownloadIntegration.js
+++ b/toolkit/components/jsdownloads/test/unit/test_DownloadIntegration.js
@@ -6,84 +6,77 @@
*/
"use strict";
////////////////////////////////////////////////////////////////////////////////
//// Globals
/**
- * Enable test mode for the _confirmCancelDownloads method to return
- * the number of downloads instead of showing the prompt to cancel or not.
- */
-function enableObserversTestMode() {
- DownloadIntegration.testMode = true;
- DownloadIntegration.dontLoadObservers = false;
- function cleanup() {
- DownloadIntegration.testMode = false;
- DownloadIntegration.dontLoadObservers = true;
- }
- do_register_cleanup(cleanup);
-}
-
-/**
* Notifies the prompt observers and verify the expected downloads count.
*
* @param aIsPrivate
* Flag to know is test private observers.
* @param aExpectedCount
* the expected downloads count for quit and offline observers.
* @param aExpectedPBCount
* the expected downloads count for private browsing observer.
*/
function notifyPromptObservers(aIsPrivate, aExpectedCount, aExpectedPBCount) {
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
createInstance(Ci.nsISupportsPRBool);
// Notify quit application requested observer.
- DownloadIntegration.testPromptDownloads = -1;
+ DownloadIntegration._testPromptDownloads = -1;
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null);
- do_check_eq(DownloadIntegration.testPromptDownloads, aExpectedCount);
+ do_check_eq(DownloadIntegration._testPromptDownloads, aExpectedCount);
// Notify offline requested observer.
- DownloadIntegration.testPromptDownloads = -1;
+ DownloadIntegration._testPromptDownloads = -1;
Services.obs.notifyObservers(cancelQuit, "offline-requested", null);
- do_check_eq(DownloadIntegration.testPromptDownloads, aExpectedCount);
+ do_check_eq(DownloadIntegration._testPromptDownloads, aExpectedCount);
if (aIsPrivate) {
// Notify last private browsing requested observer.
- DownloadIntegration.testPromptDownloads = -1;
+ DownloadIntegration._testPromptDownloads = -1;
Services.obs.notifyObservers(cancelQuit, "last-pb-context-exiting", null);
- do_check_eq(DownloadIntegration.testPromptDownloads, aExpectedPBCount);
+ do_check_eq(DownloadIntegration._testPromptDownloads, aExpectedPBCount);
}
+
+ delete DownloadIntegration._testPromptDownloads;
}
////////////////////////////////////////////////////////////////////////////////
//// Tests
+/**
+ * Allows re-enabling the real download directory logic during one test.
+ */
+function allowDirectoriesInTest() {
+ DownloadIntegration.allowDirectories = true;
+ function cleanup() {
+ DownloadIntegration.allowDirectories = false;
+ }
+ do_register_cleanup(cleanup);
+ return cleanup;
+}
+
XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
return Services.strings.
createBundle("chrome://mozapps/locale/downloads/downloads.properties");
});
/**
- * Tests that the getSystemDownloadsDirectory returns a valid download
- * directory string path.
+ * Tests that getSystemDownloadsDirectory returns an existing directory or
+ * creates a new directory depending on the platform. Instead of the real
+ * directory, this test is executed in the temporary directory so we can safely
+ * delete the created folder to check whether it is created again.
*/
-add_task(function* test_getSystemDownloadsDirectory()
+add_task(function* test_getSystemDownloadsDirectory_exists_or_creates()
{
- // Enable test mode for the getSystemDownloadsDirectory method to return
- // temp directory instead so we can check whether the desired directory
- // is created or not.
- DownloadIntegration.testMode = true;
- function cleanup() {
- DownloadIntegration.testMode = false;
- }
- do_register_cleanup(cleanup);
-
let tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
let downloadDir;
// OSX / Linux / Windows but not XP/2k
if (Services.appinfo.OS == "Darwin" ||
Services.appinfo.OS == "Linux" ||
(Services.appinfo.OS == "WINNT" &&
parseFloat(Services.sysinfo.getProperty("version")) >= 6)) {
@@ -102,36 +95,49 @@ add_task(function* test_getSystemDownloa
downloadDir = yield DownloadIntegration.getSystemDownloadsDirectory();
do_check_eq(downloadDir, targetPath);
do_check_true(yield OS.File.exists(downloadDir));
let info = yield OS.File.stat(downloadDir);
do_check_true(info.isDir);
yield OS.File.removeEmptyDir(targetPath);
}
+});
- let downloadDirBefore = yield DownloadIntegration.getSystemDownloadsDirectory();
+/**
+ * Tests that the real directory returned by getSystemDownloadsDirectory is not
+ * the one that is used during unit tests. Since this is the actual downloads
+ * directory of the operating system, we don't try to delete it afterwards.
+ */
+add_task(function* test_getSystemDownloadsDirectory_real()
+{
+ let fakeDownloadDir = yield DownloadIntegration.getSystemDownloadsDirectory();
+
+ let cleanup = allowDirectoriesInTest();
+ let realDownloadDir = yield DownloadIntegration.getSystemDownloadsDirectory();
cleanup();
- let downloadDirAfter = yield DownloadIntegration.getSystemDownloadsDirectory();
- do_check_neq(downloadDirBefore, downloadDirAfter);
+
+ do_check_neq(fakeDownloadDir, realDownloadDir);
});
/**
* Tests that the getPreferredDownloadsDirectory returns a valid download
* directory string path.
*/
add_task(function* test_getPreferredDownloadsDirectory()
{
+ let cleanupDirectories = allowDirectoriesInTest();
+
let folderListPrefName = "browser.download.folderList";
let dirPrefName = "browser.download.dir";
- function cleanup() {
+ function cleanupPrefs() {
Services.prefs.clearUserPref(folderListPrefName);
Services.prefs.clearUserPref(dirPrefName);
}
- do_register_cleanup(cleanup);
+ do_register_cleanup(cleanupPrefs);
// Should return the system downloads directory.
Services.prefs.setIntPref(folderListPrefName, 1);
let systemDir = yield DownloadIntegration.getSystemDownloadsDirectory();
let downloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
do_check_neq(downloadDir, "");
do_check_eq(downloadDir, systemDir);
@@ -169,48 +175,65 @@ add_task(function* test_getPreferredDown
do_check_eq(downloadDir, systemDir);
// Should return the system downloads directory because the folderList
// preference is invalid
Services.prefs.setIntPref(folderListPrefName, 999);
downloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
do_check_eq(downloadDir, systemDir);
- cleanup();
+ cleanupPrefs();
+ cleanupDirectories();
});
/**
* Tests that the getTemporaryDownloadsDirectory returns a valid download
* directory string path.
*/
add_task(function* test_getTemporaryDownloadsDirectory()
{
+ let cleanup = allowDirectoriesInTest();
+
let downloadDir = yield DownloadIntegration.getTemporaryDownloadsDirectory();
do_check_neq(downloadDir, "");
if ("nsILocalFileMac" in Ci) {
let preferredDownloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
do_check_eq(downloadDir, preferredDownloadDir);
} else {
let tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
do_check_eq(downloadDir, tempDir.path);
}
+
+ cleanup();
});
////////////////////////////////////////////////////////////////////////////////
//// Tests DownloadObserver
/**
+ * Re-enables the default observers for the following tests.
+ *
+ * This takes effect the first time a DownloadList object is created, and lasts
+ * until this test file has completed.
+ */
+add_task(function* test_observers_setup()
+{
+ DownloadIntegration.allowObservers = true;
+ do_register_cleanup(function () {
+ DownloadIntegration.allowObservers = false;
+ });
+});
+
+/**
* Tests notifications prompts when observers are notified if there are public
* and private active downloads.
*/
add_task(function* test_notifications()
{
- enableObserversTestMode();
-
for (let isPrivate of [false, true]) {
mustInterruptResponses();
let list = yield promiseNewList(isPrivate);
let download1 = yield promiseNewDownload(httpUrl("interruptible.txt"));
let download2 = yield promiseNewDownload(httpUrl("interruptible.txt"));
let download3 = yield promiseNewDownload(httpUrl("interruptible.txt"));
let promiseAttempt1 = download1.start();
@@ -239,18 +262,16 @@ add_task(function* test_notifications()
});
/**
* Tests that notifications prompts observers are not notified if there are no
* public or private active downloads.
*/
add_task(function* test_no_notifications()
{
- enableObserversTestMode();
-
for (let isPrivate of [false, true]) {
let list = yield promiseNewList(isPrivate);
let download1 = yield promiseNewDownload(httpUrl("interruptible.txt"));
let download2 = yield promiseNewDownload(httpUrl("interruptible.txt"));
download1.start().catch(() => {});
download2.start().catch(() => {});
// Add downloads to list.
@@ -269,17 +290,16 @@ add_task(function* test_no_notifications
});
/**
* Tests notifications prompts when observers are notified if there are public
* and private active downloads at the same time.
*/
add_task(function* test_mix_notifications()
{
- enableObserversTestMode();
mustInterruptResponses();
let publicList = yield promiseNewList();
let privateList = yield Downloads.getList(Downloads.PRIVATE);
let download1 = yield promiseNewDownload(httpUrl("interruptible.txt"));
let download2 = yield promiseNewDownload(httpUrl("interruptible.txt"));
let promiseAttempt1 = download1.start();
let promiseAttempt2 = download2.start();
@@ -301,18 +321,16 @@ add_task(function* test_mix_notification
});
/**
* Tests suspending and resuming as well as going offline and then online again.
* The downloads should stop when suspending and start again when resuming.
*/
add_task(function* test_suspend_resume()
{
- enableObserversTestMode();
-
// The default wake delay is 10 seconds, so set the wake delay to be much
// faster for these tests.
Services.prefs.setIntPref("browser.download.manager.resumeOnWakeDelay", 5);
let addDownload = function(list)
{
return Task.spawn(function* () {
let download = yield promiseNewDownload(httpUrl("interruptible.txt"));
@@ -382,17 +400,16 @@ add_task(function* test_suspend_resume()
});
/**
* Tests both the downloads list and the in-progress downloads are clear when
* private browsing observer is notified.
*/
add_task(function* test_exit_private_browsing()
{
- enableObserversTestMode();
mustInterruptResponses();
let privateList = yield promiseNewList(true);
let download1 = yield promiseNewDownload(httpUrl("source.txt"));
let download2 = yield promiseNewDownload(httpUrl("interruptible.txt"));
let promiseAttempt1 = download1.start();
let promiseAttempt2 = download2.start();
@@ -401,17 +418,18 @@ add_task(function* test_exit_private_bro
yield privateList.add(download2);
// Complete the download.
yield promiseAttempt1;
do_check_eq((yield privateList.getAll()).length, 2);
// Simulate exiting the private browsing.
- DownloadIntegration._deferTestClearPrivateList = Promise.defer();
- Services.obs.notifyObservers(null, "last-pb-context-exited", null);
- let result = yield DownloadIntegration._deferTestClearPrivateList.promise;
+ yield new Promise(resolve => {
+ DownloadIntegration._testResolveClearPrivateList = resolve;
+ Services.obs.notifyObservers(null, "last-pb-context-exited", null);
+ });
+ delete DownloadIntegration._testResolveClearPrivateList;
- do_check_eq(result, "success");
do_check_eq((yield privateList.getAll()).length, 0);
continueResponses();
});