diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index d386360d919..9253f6c1515 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -728,18 +728,6 @@ const gFormSubmitObserver = { var gBrowserInit = { onLoad: function() { - // window.arguments[0]: URI to load (string), or an nsISupportsArray of - // nsISupportsStrings to load, or a xul:tab of - // a tabbrowser, which will be replaced by this - // window (for this case, all other arguments are - // ignored). - // [1]: character set (string) - // [2]: referrer (nsIURI) - // [3]: postData (nsIInputStream) - // [4]: allowThirdPartyFixup (bool) - if ("arguments" in window && window.arguments[0]) - var uriToLoad = window.arguments[0]; - gMultiProcessBrowser = gPrefService.getBoolPref("browser.tabs.remote"); var mustLoadSidebar = false; @@ -780,6 +768,7 @@ var gBrowserInit = { new nsBrowserAccess(); // set default character set if provided + // window.arguments[1]: character set (string) if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) { if (window.arguments[1].startsWith("charset=")) { var arrayArgComponents = window.arguments[1].split("="); @@ -946,7 +935,7 @@ var gBrowserInit = { retrieveToolbarIconsizesFromTheme(); // Wait until chrome is painted before executing code not critical to making the window visible - this._boundDelayedStartup = this._delayedStartup.bind(this, uriToLoad, mustLoadSidebar); + this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar); window.addEventListener("MozAfterPaint", this._boundDelayedStartup); this._loadHandled = true; @@ -957,7 +946,7 @@ var gBrowserInit = { this._boundDelayedStartup = null; }, - _delayedStartup: function(uriToLoad, mustLoadSidebar) { + _delayedStartup: function(mustLoadSidebar) { let tmp = {}; Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", tmp); let TelemetryTimestamps = tmp.TelemetryTimestamps; @@ -976,6 +965,7 @@ var gBrowserInit = { socialBrowser.addEventListener("MozApplicationManifest", OfflineApps, false); + let uriToLoad = this._getUriToLoad(); var isLoadingBlank = isBlankPageURL(uriToLoad); // This pageshow listener needs to be registered before we may call @@ -1012,6 +1002,9 @@ var gBrowserInit = { gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad); } + // window.arguments[2]: referrer (nsIURI) + // [3]: postData (nsIInputStream) + // [4]: allowThirdPartyFixup (bool) else if (window.arguments.length >= 3) { loadURI(uriToLoad, window.arguments[2], window.arguments[3] || null, window.arguments[4] || false); @@ -1293,6 +1286,31 @@ var gBrowserInit = { TelemetryTimestamps.add("delayedStartupFinished"); }, + // Returns the URI(s) to load at startup. + _getUriToLoad: function () { + // window.arguments[0]: URI to load (string), or an nsISupportsArray of + // nsISupportsStrings to load, or a xul:tab of + // a tabbrowser, which will be replaced by this + // window (for this case, all other arguments are + // ignored). + if (!window.arguments || !window.arguments[0]) + return null; + + let uri = window.arguments[0]; + let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"] + .getService(Ci.nsISessionStartup); + let defaultArgs = Cc["@mozilla.org/browser/clh;1"] + .getService(Ci.nsIBrowserHandler) + .defaultArgs; + + // If the given URI matches defaultArgs (the default homepage) we want + // to block its load if we're going to restore a session anyway. + if (uri == defaultArgs && sessionStartup.willOverrideHomepage) + return null; + + return uri; + }, + onUnload: function() { // In certain scenarios it's possible for unload to be fired before onload, // (e.g. if the window is being closed after browser.js loads but before the diff --git a/browser/components/sessionstore/nsISessionStartup.idl b/browser/components/sessionstore/nsISessionStartup.idl index d9833cb4dcb..a8e786d03e9 100644 --- a/browser/components/sessionstore/nsISessionStartup.idl +++ b/browser/components/sessionstore/nsISessionStartup.idl @@ -10,7 +10,7 @@ * - and allows to restore everything into one window. */ -[scriptable, uuid(35235b39-7098-4b3b-8e28-cd004a88b06f)] +[scriptable, uuid(51f4b9f0-f3d2-11e2-bb62-2c24dd830245)] interface nsISessionStartup: nsISupports { /** @@ -23,10 +23,24 @@ interface nsISessionStartup: nsISupports readonly attribute jsval state; /** - * Determine if session should be restored + * Determines whether there is a pending session restore and makes sure that + * we're initialized before returning. If we're not yet this will read the + * session file synchronously. */ boolean doRestore(); + /** + * Returns whether we will restore a session that ends up replacing the + * homepage. The browser uses this to not start loading the homepage if + * we're going to stop its load anyway shortly after. + * + * This is meant to be an optimization for the average case that loading the + * session file finishes before we may want to start loading the default + * homepage. Should this be called before the session file has been read it + * will just return false. + */ + readonly attribute bool willOverrideHomepage; + /** * What type of session we're restoring. * NO_SESSION There is no data available from the previous session diff --git a/browser/components/sessionstore/src/nsSessionStartup.js b/browser/components/sessionstore/src/nsSessionStartup.js index 6d8c7925bf1..52de14cac9b 100644 --- a/browser/components/sessionstore/src/nsSessionStartup.js +++ b/browser/components/sessionstore/src/nsSessionStartup.js @@ -162,15 +162,6 @@ SessionStartup.prototype = { else this._initialState = null; // reset the state - // wait for the first browser window to open - // Don't reset the initial window's default args (i.e. the home page(s)) - // if all stored tabs are pinned. - if (this.doRestore() && - (!this._initialState.windows || - !this._initialState.windows.every(function (win) - win.tabs.every(function (tab) tab.pinned)))) - Services.obs.addObserver(this, "domwindowopened", true); - Services.obs.addObserver(this, "sessionstore-windows-restored", true); if (this._sessionType != Ci.nsISessionStartup.NO_SESSION) @@ -204,14 +195,6 @@ SessionStartup.prototype = { if (this._sessionType != Ci.nsISessionStartup.NO_SESSION) Services.obs.removeObserver(this, "browser:purge-session-history"); break; - case "domwindowopened": - var window = aSubject; - var self = this; - window.addEventListener("load", function() { - self._onWindowOpened(window); - window.removeEventListener("load", arguments.callee, false); - }, false); - break; case "sessionstore-windows-restored": Services.obs.removeObserver(this, "sessionstore-windows-restored"); // free _initialState after nsSessionStore is done with it @@ -225,43 +208,6 @@ SessionStartup.prototype = { } }, - /** - * Removes the default arguments from the first browser window - * (and removes the "domwindowopened" observer afterwards). - */ - _onWindowOpened: function sss_onWindowOpened(aWindow) { - var wType = aWindow.document.documentElement.getAttribute("windowtype"); - if (wType != "navigator:browser") - return; - - /** - * Note: this relies on the fact that nsBrowserContentHandler will return - * a different value the first time its getter is called after an update, - * due to its needHomePageOverride() logic. We don't want to remove the - * default arguments in the update case, since they include the "What's - * New" page. - * - * Since we're garanteed to be at least the second caller of defaultArgs - * (nsBrowserContentHandler calls it to determine which arguments to pass - * at startup), we know that if the window's arguments don't match the - * current defaultArguments, we're either in the update case, or we're - * launching a non-default browser window, so we shouldn't remove the - * window's arguments. - */ - var defaultArgs = Cc["@mozilla.org/browser/clh;1"]. - getService(Ci.nsIBrowserHandler).defaultArgs; - if (aWindow.arguments && aWindow.arguments[0] && - aWindow.arguments[0] == defaultArgs) - aWindow.arguments[0] = null; - - try { - Services.obs.removeObserver(this, "domwindowopened"); - } catch (e) { - // This might throw if we're removing the observer multiple times, - // but this is safe to ignore. - } - }, - /* ........ Public API ................*/ get onceInitialized() { @@ -277,15 +223,47 @@ SessionStartup.prototype = { }, /** - * Determine whether there is a pending session restore. + * Determines whether there is a pending session restore and makes sure that + * we're initialized before returning. If we're not yet this will read the + * session file synchronously. * @returns bool */ doRestore: function sss_doRestore() { this._ensureInitialized(); + return this._willRestore(); + }, + + /** + * Determines whether there is a pending session restore. + * @returns bool + */ + _willRestore: function () { return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION || this._sessionType == Ci.nsISessionStartup.RESUME_SESSION; }, + /** + * Returns whether we will restore a session that ends up replacing the + * homepage. The browser uses this to not start loading the homepage if + * we're going to stop its load anyway shortly after. + * + * This is meant to be an optimization for the average case that loading the + * session file finishes before we may want to start loading the default + * homepage. Should this be called before the session file has been read it + * will just return false. + * + * @returns bool + */ + get willOverrideHomepage() { + if (this._initialState && this._willRestore()) { + let windows = this._initialState.windows || null; + // If there are valid windows with not only pinned tabs, signal that we + // will override the default homepage by restoring a session. + return windows && windows.some(w => w.tabs.some(t => !t.pinned)); + } + return false; + }, + /** * Get the type of pending session store, if any. */ diff --git a/netwerk/protocol/http/UserAgentOverrides.jsm b/netwerk/protocol/http/UserAgentOverrides.jsm index d05f5569300..3ce9a2f5733 100644 --- a/netwerk/protocol/http/UserAgentOverrides.jsm +++ b/netwerk/protocol/http/UserAgentOverrides.jsm @@ -18,7 +18,7 @@ const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"] const MAX_OVERRIDE_FOR_HOST_CACHE_SIZE = 250; var gPrefBranch; -var gOverrides; +var gOverrides = new Map; var gOverrideForHostCache = new Map; var gInitialized = false; var gOverrideFunctions = [ @@ -63,10 +63,10 @@ this.UserAgentOverrides = { override = null; - for (let domain in gOverrides) { + for (let [domain, userAgent] of gOverrides) { if (host == domain || host.endsWith("." + domain)) { - override = gOverrides[domain]; + override = userAgent; break; } } @@ -93,23 +93,30 @@ this.UserAgentOverrides = { }; function buildOverrides() { - gOverrides = {}; + gOverrides.clear(); gOverrideForHostCache.clear(); if (!Services.prefs.getBoolPref(PREF_OVERRIDES_ENABLED)) return; + let builtUAs = new Map; let domains = gPrefBranch.getChildList(""); for (let domain of domains) { let override = gPrefBranch.getCharPref(domain); + let userAgent = builtUAs.get(override); - let [search, replace] = override.split("#", 2); - if (search && replace) { - gOverrides[domain] = DEFAULT_UA.replace(new RegExp(search, "g"), replace); - } else { - gOverrides[domain] = override; + if (userAgent === undefined) { + let [search, replace] = override.split("#", 2); + if (search && replace) { + userAgent = DEFAULT_UA.replace(new RegExp(search, "g"), replace); + } else { + userAgent = override; + } + builtUAs.set(override, userAgent); } + + gOverrides.set(domain, userAgent); } } diff --git a/toolkit/components/jsdownloads/src/DownloadCore.jsm b/toolkit/components/jsdownloads/src/DownloadCore.jsm index 0d6e7d4763d..d9017aca644 100644 --- a/toolkit/components/jsdownloads/src/DownloadCore.jsm +++ b/toolkit/components/jsdownloads/src/DownloadCore.jsm @@ -56,6 +56,8 @@ 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", @@ -69,6 +71,15 @@ 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) { + // We cannot use the "instanceof" operator reliably across module boundaries. + return (typeof aValue == "string") || + (typeof aValue == "object" && "charAt" in aValue); +} + //////////////////////////////////////////////////////////////////////////////// //// Download @@ -422,6 +433,71 @@ Download.prototype = { } this._notifyChange(); }, + + /** + * Returns a static representation of the current object state. + * + * @return A JavaScript object that can be serialized to JSON. + */ + toSerializable: function () + { + let serializable = { + source: this.source.toSerializable(), + target: this.target.toSerializable(), + }; + + // Simplify the representation for the most common saver type. If the saver + // is an object instead of a simple string, we can't simplify it because we + // need to persist all its properties, not only "type". This may happen for + // savers of type "copy" as well as other types. + let saver = this.saver.toSerializable(); + if (saver !== "copy") { + serializable.saver = saver; + } + + return serializable; + }, +}; + +/** + * Creates a new Download object from a serializable representation. This + * function is used by the createDownload method of Downloads.jsm when a new + * Download object is requested, thus some properties may refer to live objects + * in place of their serializable representations. + * + * @param aSerializable + * An object with the following fields: + * { + * source: DownloadSource object, or its serializable representation. + * See DownloadSource.fromSerializable for details. + * target: DownloadTarget object, or its serializable representation. + * See DownloadTarget.fromSerializable for details. + * saver: Serializable representation of a DownloadSaver object. See + * DownloadSaver.fromSerializable for details. If omitted, + * defaults to "copy". + * } + * + * @return The newly created Download object. + */ +Download.fromSerializable = function (aSerializable) { + let download = new Download(); + if (aSerializable.source instanceof DownloadSource) { + download.source = aSerializable.source; + } else { + download.source = DownloadSource.fromSerializable(aSerializable.source); + } + if (aSerializable.target instanceof DownloadTarget) { + download.target = aSerializable.target; + } else { + download.target = DownloadTarget.fromSerializable(aSerializable.target); + } + if ("saver" in aSerializable) { + download.saver = DownloadSaver.fromSerializable(aSerializable.saver); + } else { + download.saver = DownloadSaver.fromSerializable("copy"); + } + download.saver.download = download; + return download; }; //////////////////////////////////////////////////////////////////////////////// @@ -434,20 +510,20 @@ function DownloadSource() { } DownloadSource.prototype = { /** - * The nsIURI for the download source. + * String containing the URI for the download source. */ - uri: null, + url: null, /** * Indicates whether the download originated from a private window. This - * determines the context of the network request that is made to retrieve the + * determines the context of the network request that is made to retrieve the * resource. */ isPrivate: false, /** - * The nsIURI for the referrer of the download source, or null if no referrer - * should be sent or the download source is not HTTP. + * String containing the referrer URI of the download source, or null if no + * referrer should be sent or the download source is not HTTP. */ referrer: null, @@ -456,19 +532,60 @@ DownloadSource.prototype = { * * @return A JavaScript object that can be serialized to JSON. */ - serialize: function DS_serialize() + toSerializable: function () { - let serialized = { uri: this.uri.spec }; + // Simplify the representation if we don't have other details. + if (!this.isPrivate && !this.referrer) { + return this.url; + } + + let serializable = { url: this.url }; if (this.isPrivate) { - serialized.isPrivate = true; + serializable.isPrivate = true; } if (this.referrer) { - serialized.referrer = this.referrer.spec; + serializable.referrer = this.referrer; } - return serialized; + return serializable; }, }; +/** + * Creates a new DownloadSource object from its serializable representation. + * + * @param aSerializable + * Serializable representation of a DownloadSource object. This may be a + * string containing the URI for the download source, an nsIURI, or an + * object with the following properties: + * { + * url: String containing the URI for the download source. + * isPrivate: Indicates whether the download originated from a private + * window. If omitted, the download is public. + * referrer: String containing the referrer URI of the download source. + * Can be omitted or null if no referrer should be sent or + * the download source is not HTTP. + * } + * + * @return The newly created DownloadSource object. + */ +DownloadSource.fromSerializable = function (aSerializable) { + let source = new DownloadSource(); + if (isString(aSerializable)) { + source.url = aSerializable; + } else if (aSerializable instanceof Ci.nsIURI) { + source.url = aSerializable.spec; + } else { + source.url = aSerializable.url; + if ("isPrivate" in aSerializable) { + source.isPrivate = aSerializable.isPrivate; + } + if ("referrer" in aSerializable) { + source.referrer = aSerializable.referrer; + } + } + return source; +}; + //////////////////////////////////////////////////////////////////////////////// //// DownloadTarget @@ -480,21 +597,50 @@ function DownloadTarget() { } DownloadTarget.prototype = { /** - * The nsIFile for the download target. + * String containing the path of the target file. */ - file: null, + path: null, /** * Returns a static representation of the current object state. * * @return A JavaScript object that can be serialized to JSON. */ - serialize: function DT_serialize() + toSerializable: function () { - return { file: this.file.path }; + // Simplify the representation since we don't have other details for now. + return this.path; }, }; +/** + * Creates a new DownloadTarget object from its serializable representation. + * + * @param aSerializable + * Serializable representation of a DownloadTarget object. This may be a + * string containing the path of the target file, an nsIFile, or an + * object with the following properties: + * { + * path: String containing the path of the target file. + * } + * + * @return The newly created DownloadTarget object. + */ +DownloadTarget.fromSerializable = function (aSerializable) { + let target = new DownloadTarget(); + if (isString(aSerializable)) { + target.path = aSerializable; + } else if (aSerializable instanceof Ci.nsIFile) { + // Read the "path" property of nsIFile after checking the object type. + target.path = aSerializable.path; + } else { + // Read the "path" property of the serializable DownloadTarget + // representation. + target.path = aSerializable.path; + } + return target; +}; + //////////////////////////////////////////////////////////////////////////////// //// DownloadError @@ -610,12 +756,39 @@ DownloadSaver.prototype = { * * @return A JavaScript object that can be serialized to JSON. */ - serialize: function DS_serialize() + toSerializable: function () { throw new Error("Not implemented."); }, }; +/** + * Creates a new DownloadSaver object from its serializable representation. + * + * @param aSerializable + * Serializable representation of a DownloadSaver object. If no initial + * state information for the saver object is needed, can be a string + * representing the class of the download operation, for example "copy". + * + * @return The newly created DownloadSaver object. + */ +DownloadSaver.fromSerializable = function (aSerializable) { + let serializable = isString(aSerializable) ? { type: aSerializable } + : aSerializable; + let saver; + switch (serializable.type) { + case "copy": + saver = DownloadCopySaver.fromSerializable(serializable); + break; + case "legacy": + saver = DownloadLegacySaver.fromSerializable(serializable); + break; + default: + throw new Error("Unrecoginzed download saver type."); + } + return saver; +}; + //////////////////////////////////////////////////////////////////////////////// //// DownloadCopySaver @@ -665,15 +838,16 @@ DownloadCopySaver.prototype = { }; // Set the target file, that will be deleted if the download fails. - backgroundFileSaver.setTarget(download.target.file, false); + backgroundFileSaver.setTarget(new FileUtils.File(download.target.path), + false); // Create a channel from the source, and listen to progress notifications. - let channel = NetUtil.newChannel(download.source.uri); + let channel = NetUtil.newChannel(NetUtil.newURI(download.source.url)); if (channel instanceof Ci.nsIPrivateBrowsingChannel) { channel.setPrivate(download.source.isPrivate); } - if (channel instanceof Ci.nsIHttpChannel) { - channel.referrer = download.source.referrer; + if (channel instanceof Ci.nsIHttpChannel && download.source.referrer) { + channel.referrer = NetUtil.newURI(download.source.referrer); } channel.notificationCallbacks = { @@ -747,14 +921,29 @@ DownloadCopySaver.prototype = { }, /** - * Implements "DownloadSaver.serialize". + * Implements "DownloadSaver.toSerializable". */ - serialize: function DCS_serialize() + toSerializable: function () { - return { type: "copy" }; + // Simplify the representation since we don't have other details for now. + return "copy"; }, }; +/** + * Creates a new DownloadCopySaver object, with its initial state derived from + * its serializable representation. + * + * @param aSerializable + * Serializable representation of a DownloadCopySaver object. + * + * @return The newly created DownloadCopySaver object. + */ +DownloadCopySaver.fromSerializable = function (aSerializable) { + // We don't have other state details for now. + return new DownloadCopySaver(); +}; + //////////////////////////////////////////////////////////////////////////////// //// DownloadLegacySaver @@ -872,7 +1061,7 @@ DownloadLegacySaver.prototype = { // empty file is created as expected. try { // This atomic operation is more efficient than an existence check. - let file = yield OS.File.open(this.download.target.file.path, + let file = yield OS.File.open(this.download.target.path, { create: true }); yield file.close(); } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) { } @@ -898,3 +1087,12 @@ DownloadLegacySaver.prototype = { "Download canceled.")); }, }; + +/** + * Returns a new DownloadLegacySaver object. This saver type has a + * deserializable form only when creating a new object in memory, because it + * cannot be serialized to disk. + */ +DownloadLegacySaver.fromSerializable = function () { + return new DownloadLegacySaver(); +}; diff --git a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm index b6f57eaf0a0..358ec549046 100644 --- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm +++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm @@ -29,6 +29,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DownloadStore", "resource://gre/modules/DownloadStore.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", @@ -254,7 +256,8 @@ this.DownloadIntegration = { // Log the event if required by parental controls settings. if (isEnabled && gParentalControlsService.loggingEnabled) { gParentalControlsService.log(gParentalControlsService.ePCLog_FileDownload, - shouldBlock, aDownload.source.uri, null); + shouldBlock, + NetUtil.newURI(aDownload.source.url), null); } return Promise.resolve(shouldBlock); diff --git a/toolkit/components/jsdownloads/src/DownloadLegacy.js b/toolkit/components/jsdownloads/src/DownloadLegacy.js index 91665855ee4..fad46a5066a 100644 --- a/toolkit/components/jsdownloads/src/DownloadLegacy.js +++ b/toolkit/components/jsdownloads/src/DownloadLegacy.js @@ -161,9 +161,9 @@ DownloadLegacyTransfer.prototype = { // wait for it to be available. This operation may cause the entire // download system to initialize before the object is created. Downloads.createDownload({ - source: { uri: aSource, isPrivate: aIsPrivate }, - target: { file: aTarget.QueryInterface(Ci.nsIFileURL).file }, - saver: { type: "legacy" }, + source: { url: aSource.spec, isPrivate: aIsPrivate }, + target: aTarget.QueryInterface(Ci.nsIFileURL).file, + saver: "legacy", }).then(function DLT_I_onDownload(aDownload) { // Now that the saver is available, hook up the cancellation handler. aDownload.saver.deferCanceled.promise.then(() => { diff --git a/toolkit/components/jsdownloads/src/DownloadList.jsm b/toolkit/components/jsdownloads/src/DownloadList.jsm index 2f011a4664a..b9fd386ff74 100644 --- a/toolkit/components/jsdownloads/src/DownloadList.jsm +++ b/toolkit/components/jsdownloads/src/DownloadList.jsm @@ -25,6 +25,8 @@ const Cr = Components.results; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", @@ -234,7 +236,8 @@ DownloadList.prototype = { //// nsINavHistoryObserver onDeleteURI: function DL_onDeleteURI(aURI, aGUID) { - this._removeWhere(download => aURI.equals(download.source.uri)); + this._removeWhere(download => aURI.equals(NetUtil.newURI( + download.source.url))); }, onClearHistory: function DL_onClearHistory() { diff --git a/toolkit/components/jsdownloads/src/DownloadStore.jsm b/toolkit/components/jsdownloads/src/DownloadStore.jsm index cd956c4a0a1..6b3c9bab487 100644 --- a/toolkit/components/jsdownloads/src/DownloadStore.jsm +++ b/toolkit/components/jsdownloads/src/DownloadStore.jsm @@ -7,6 +7,25 @@ /** * Handles serialization of Download objects and persistence into a file, so * that the state of downloads can be restored across sessions. + * + * The file is stored in JSON format, without indentation. With indentation + * applied, the file would look like this: + * + * { + * "list": [ + * { + * "source": "http://www.example.com/download.txt", + * "target": "/home/user/Downloads/download.txt" + * }, + * { + * "source": { + * "url": "http://www.example.com/download.txt", + * "referrer": "http://www.example.com/referrer.html" + * }, + * "target": "/home/user/Downloads/download-2.txt" + * } + * ] + * } */ "use strict"; @@ -27,16 +46,11 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm") XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); -const LocalFile = Components.Constructor("@mozilla.org/file/local;1", - "nsIFile", "initWithPath"); - XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function () { return new TextDecoder(); }); @@ -95,19 +109,9 @@ DownloadStore.prototype = { let storeData = JSON.parse(gTextDecoder.decode(bytes)); // Create live downloads based on the static snapshot. - for (let downloadData of storeData) { + for (let downloadData of storeData.list) { try { - let source = { uri: NetUtil.newURI(downloadData.source.uri) }; - if ("referrer" in downloadData.source) { - source.referrer = NetUtil.newURI(downloadData.source.referrer); - } - let download = yield Downloads.createDownload({ - source: source, - target: { file: new LocalFile(downloadData.target.file) }, - saver: downloadData.saver, - }); - - this.list.add(download); + this.list.add(yield Downloads.createDownload(downloadData)); } catch (ex) { // If an item is unrecognized, don't prevent others from being loaded. Cu.reportError(ex); @@ -131,19 +135,15 @@ DownloadStore.prototype = { let downloads = yield this.list.getAll(); // Take a static snapshot of the current state of all the downloads. - let storeData = []; + let storeData = { list: [] }; let atLeastOneDownload = false; for (let download of downloads) { try { - storeData.push({ - source: download.source.serialize(), - target: download.target.serialize(), - saver: download.saver.serialize(), - }); + storeData.list.push(download.toSerializable()); atLeastOneDownload = true; } catch (ex) { - // If an item cannot be serialized, don't prevent others from being - // saved. + // If an item cannot be converted to a serializable form, don't + // prevent others from being saved. Cu.reportError(ex); } } diff --git a/toolkit/components/jsdownloads/src/Downloads.jsm b/toolkit/components/jsdownloads/src/Downloads.jsm index 9a7e4be8efe..6edaa2efe4b 100644 --- a/toolkit/components/jsdownloads/src/Downloads.jsm +++ b/toolkit/components/jsdownloads/src/Downloads.jsm @@ -31,14 +31,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DownloadList", "resource://gre/modules/DownloadList.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper", "resource://gre/modules/DownloadUIHelper.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/commonjs/sdk/core/promise.js"); -XPCOMUtils.defineLazyModuleGetter(this, "Services", - "resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); @@ -55,19 +49,29 @@ this.Downloads = { * * @param aProperties * Provides the initial properties for the newly created download. + * This matches the serializable representation of a Download object. + * Some of the most common properties in this object include: * { - * source: { - * uri: The nsIURI for the download source. + * source: String containing the URI for the download source. + * Alternatively, may be an nsIURI, a DownloadSource object, + * or an object with the following properties: + * { + * url: String containing the URI for the download source. * isPrivate: Indicates whether the download originated from a - * private window. + * private window. If omitted, the download is public. + * referrer: String containing the referrer URI of the download + * source. Can be omitted or null if no referrer should + * be sent or the download source is not HTTP. * }, - * target: { - * file: The nsIFile for the download target. - * }, - * saver: { - * type: String representing the class of download operation - * handled by this saver object, for example "copy". + * target: String containing the path of the target file. + * Alternatively, may be an nsIFile, a DownloadTarget object, + * or an object with the following properties: + * { + * path: String containing the path of the target file. * }, + * saver: String representing the class of the download operation. + * If omitted, defaults to "copy". Alternatively, may be the + * serializable representation of a DownloadSaver object. * } * * @return {Promise} @@ -76,31 +80,11 @@ this.Downloads = { */ createDownload: function D_createDownload(aProperties) { - return Task.spawn(function task_D_createDownload() { - let download = new Download(); - - download.source = new DownloadSource(); - download.source.uri = aProperties.source.uri; - if ("isPrivate" in aProperties.source) { - download.source.isPrivate = aProperties.source.isPrivate; - } - if ("referrer" in aProperties.source) { - download.source.referrer = aProperties.source.referrer; - } - download.target = new DownloadTarget(); - download.target.file = aProperties.target.file; - - // Support for different aProperties.saver values isn't implemented yet. - download.saver = aProperties.saver.type == "legacy" - ? new DownloadLegacySaver() - : new DownloadCopySaver(); - download.saver.download = download; - - // This explicitly makes this function a generator for Task.jsm, so that - // exceptions in the above calls can be reported asynchronously. - yield; - throw new Task.Result(download); - }); + try { + return Promise.resolve(Download.fromSerializable(aProperties)); + } catch (ex) { + return Promise.reject(ex); + } }, /** @@ -111,15 +95,17 @@ this.Downloads = { * reference to a Download object using the createDownload function. * * @param aSource - * The nsIURI or string containing the URI spec for the download - * source, or alternative DownloadSource. + * String containing the URI for the download source. Alternatively, + * may be an nsIURI or a DownloadSource object. * @param aTarget - * The nsIFile or string containing the file path, or alternative - * DownloadTarget. + * String containing the path of the target file. Alternatively, may + * be an nsIFile or a DownloadTarget object. * @param aOptions - * The object contains different additional options or null. - * { isPrivate: Indicates whether the download originated from a - * private window. + * An optional object used to control the behavior of this function. + * You may pass an object with a subset of the following fields: + * { + * isPrivate: Indicates whether the download originated from a + * private window. * } * * @return {Promise} @@ -127,31 +113,13 @@ this.Downloads = { * @rejects JavaScript exception if the download failed. */ simpleDownload: function D_simpleDownload(aSource, aTarget, aOptions) { - // Wrap the arguments into simple objects resembling DownloadSource and - // DownloadTarget, if they are not objects of that type already. - if (aSource instanceof Ci.nsIURI) { - aSource = { uri: aSource }; - } else if (typeof aSource == "string" || - (typeof aSource == "object" && "charAt" in aSource)) { - aSource = { uri: NetUtil.newURI(aSource) }; - } - - if (aSource && aOptions && ("isPrivate" in aOptions)) { - aSource.isPrivate = aOptions.isPrivate; - } - if (aTarget instanceof Ci.nsIFile) { - aTarget = { file: aTarget }; - } else if (typeof aTarget == "string" || - (typeof aTarget == "object" && "charAt" in aTarget)) { - aTarget = { file: new FileUtils.File(aTarget) }; - } - - // Create and start the actual download. return this.createDownload({ source: aSource, target: aTarget, - saver: { type: "copy" }, }).then(function D_SD_onSuccess(aDownload) { + if (aOptions && ("isPrivate" in aOptions)) { + aDownload.source.isPrivate = aOptions.isPrivate; + } return aDownload.start(); }); }, diff --git a/toolkit/components/jsdownloads/test/unit/common_test_Download.js b/toolkit/components/jsdownloads/test/unit/common_test_Download.js new file mode 100644 index 00000000000..10f4942c89c --- /dev/null +++ b/toolkit/components/jsdownloads/test/unit/common_test_Download.js @@ -0,0 +1,1105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This script is loaded by "test_DownloadCore.js" and "test_DownloadLegacy.js" + * with different values of the gUseLegacySaver variable, to apply tests to both + * the "copy" and "legacy" saver implementations. + */ + +"use strict"; + +//////////////////////////////////////////////////////////////////////////////// +//// Globals + +/** + * Creates and starts a new download, using either DownloadCopySaver or + * DownloadLegacySaver based on the current test run. + * + * @return {Promise} + * @resolves The newly created Download object. The download may be in progress + * or already finished. The promiseDownloadStopped function can be + * used to wait for completion. + * @rejects JavaScript exception. + */ +function promiseStartDownload(aSourceUrl) { + if (gUseLegacySaver) { + return promiseStartLegacyDownload(aSourceUrl); + } + + return promiseNewDownload(aSourceUrl).then(download => { + download.start(); + return download; + }); +} + +/** + * Waits for a download to finish, in case it has not finished already. + * + * @param aDownload + * The Download object to wait upon. + * + * @return {Promise} + * @resolves When the download has finished successfully. + * @rejects JavaScript exception if the download failed. + */ +function promiseDownloadStopped(aDownload) { + if (!aDownload.stopped) { + // The download is in progress, wait for the current attempt to finish and + // report any errors that may occur. + return aDownload.start(); + } + + if (aDownload.succeeded) { + return Promise.resolve(); + } + + // The download failed or was canceled. + return Promise.reject(aDownload.error || new Error("Download canceled.")); +} + +//////////////////////////////////////////////////////////////////////////////// +//// 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. + */ +add_task(function test_basic() +{ + let targetFile = getTempFile(TEST_TARGET_FILE_NAME); + + let download; + if (!gUseLegacySaver) { + // When testing DownloadCopySaver, we have control over the download, thus + // we can check its basic properties before it starts. + download = yield Downloads.createDownload({ + source: { url: httpUrl("source.txt") }, + target: { path: targetFile.path }, + saver: { type: "copy" }, + }); + + do_check_eq(download.source.url, httpUrl("source.txt")); + do_check_eq(download.target.path, targetFile.path); + + yield download.start(); + } else { + // When testing DownloadLegacySaver, the download is already started when it + // is created, thus we must check its basic properties while in progress. + download = yield promiseStartLegacyDownload(null, + { targetFile: targetFile }); + + do_check_eq(download.source.url, httpUrl("source.txt")); + do_check_eq(download.target.path, targetFile.path); + + yield promiseDownloadStopped(download); + } + + // Check additional properties on the finished download. + do_check_true(download.source.referrer === null); + + yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); +}); + +/** + * Checks the referrer for downloads. + */ +add_task(function test_referrer() +{ + let sourcePath = "/test_referrer.txt"; + let sourceUrl = httpUrl("test_referrer.txt"); + let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path; + + function cleanup() { + gHttpServer.registerPathHandler(sourcePath, null); + } + + do_register_cleanup(cleanup); + + gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) { + aResponse.setHeader("Content-Type", "text/plain", false); + + do_check_true(aRequest.hasHeader("Referer")); + do_check_eq(aRequest.getHeader("Referer"), TEST_REFERRER_URL); + }); + let download = yield Downloads.createDownload({ + source: { url: sourceUrl, referrer: TEST_REFERRER_URL }, + target: targetPath, + }); + do_check_eq(download.source.referrer, TEST_REFERRER_URL); + yield download.start(); + + download = yield Downloads.createDownload({ + source: { url: sourceUrl, referrer: TEST_REFERRER_URL, + isPrivate: true }, + target: targetPath, + }); + do_check_eq(download.source.referrer, TEST_REFERRER_URL); + yield download.start(); + + // Test the download still works for non-HTTP channel with referrer. + sourceUrl = "data:text/html,"; + download = yield Downloads.createDownload({ + source: { url: sourceUrl, referrer: TEST_REFERRER_URL }, + target: targetPath, + }); + do_check_eq(download.source.referrer, TEST_REFERRER_URL); + yield download.start(); + + cleanup(); +}); + +/** + * Checks initial and final state and progress for a successful download. + */ +add_task(function test_initial_final_state() +{ + let download; + if (!gUseLegacySaver) { + // When testing DownloadCopySaver, we have control over the download, thus + // we can check its state before it starts. + download = yield promiseNewDownload(); + + do_check_true(download.stopped); + do_check_false(download.succeeded); + do_check_false(download.canceled); + do_check_true(download.error === null); + do_check_eq(download.progress, 0); + do_check_true(download.startTime === null); + + yield download.start(); + } else { + // When testing DownloadLegacySaver, the download is already started when it + // is created, thus we cannot check its initial state. + download = yield promiseStartLegacyDownload(); + yield promiseDownloadStopped(download); + } + + do_check_true(download.stopped); + do_check_true(download.succeeded); + do_check_false(download.canceled); + do_check_true(download.error === null); + do_check_eq(download.progress, 100); + do_check_true(isValidDate(download.startTime)); +}); + +/** + * Checks the notification of the final download state. + */ +add_task(function test_final_state_notified() +{ + let deferResponse = deferNextResponse(); + + let download = yield promiseStartDownload(httpUrl("interruptible.txt")); + + let onchangeNotified = false; + let lastNotifiedStopped; + let lastNotifiedProgress; + download.onchange = function () { + onchangeNotified = true; + lastNotifiedStopped = download.stopped; + lastNotifiedProgress = download.progress; + }; + + // Allow the download to complete. + let promiseAttempt = download.start(); + deferResponse.resolve(); + yield promiseAttempt; + + // The view should have been notified before the download completes. + do_check_true(onchangeNotified); + do_check_true(lastNotifiedStopped); + do_check_eq(lastNotifiedProgress, 100); +}); + +/** + * Checks intermediate progress for a successful download. + */ +add_task(function test_intermediate_progress() +{ + let deferResponse = deferNextResponse(); + + let download = yield promiseStartDownload(httpUrl("interruptible.txt")); + + let onchange = function () { + if (download.progress == 50) { + do_check_true(download.hasProgress); + do_check_eq(download.currentBytes, TEST_DATA_SHORT.length); + do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2); + + // Continue after the first chunk of data is fully received. + deferResponse.resolve(); + } + }; + + // Register for the notification, but also call the function directly in case + // the download already reached the expected progress. + download.onchange = onchange; + onchange(); + + yield promiseDownloadStopped(download); + + do_check_true(download.stopped); + do_check_eq(download.progress, 100); + + yield promiseVerifyContents(download.target.path, + TEST_DATA_SHORT + TEST_DATA_SHORT); +}); + +/** + * Downloads a file with a "Content-Length" of 0 and checks the progress. + */ +add_task(function test_empty_progress() +{ + let download = yield promiseStartDownload(httpUrl("empty.txt")); + yield promiseDownloadStopped(download); + + do_check_true(download.stopped); + do_check_true(download.hasProgress); + do_check_eq(download.progress, 100); + do_check_eq(download.currentBytes, 0); + do_check_eq(download.totalBytes, 0); + + do_check_eq((yield OS.File.stat(download.target.path)).size, 0); +}); + +/** + * Downloads an empty file with no "Content-Length" and checks the progress. + */ +add_task(function test_empty_noprogress() +{ + let deferResponse = deferNextResponse(); + let promiseEmptyRequestReceived = promiseNextRequestReceived(); + + let download; + if (!gUseLegacySaver) { + // When testing DownloadCopySaver, we have control over the download, thus + // we can hook its onchange callback that will be notified when the + // download starts. + download = yield promiseNewDownload(httpUrl("empty-noprogress.txt")); + + download.onchange = function () { + if (!download.stopped) { + do_check_false(download.hasProgress); + do_check_eq(download.currentBytes, 0); + do_check_eq(download.totalBytes, 0); + } + }; + + download.start(); + } else { + // When testing DownloadLegacySaver, the download is already started when it + // is created, and it may have already made all needed property change + // notifications, thus there is no point in checking the onchange callback. + download = yield promiseStartLegacyDownload( + httpUrl("empty-noprogress.txt")); + } + + // Wait for the request to be received by the HTTP server, but don't allow the + // request to finish yet. Before checking the download state, wait for the + // events to be processed by the client. + yield promiseEmptyRequestReceived; + yield promiseExecuteSoon(); + + // Check that this download has no progress report. + do_check_false(download.stopped); + do_check_false(download.hasProgress); + do_check_eq(download.currentBytes, 0); + do_check_eq(download.totalBytes, 0); + + // Now allow the response to finish. + deferResponse.resolve(); + yield promiseDownloadStopped(download); + + // Verify the state of the completed download. + do_check_true(download.stopped); + do_check_false(download.hasProgress); + do_check_eq(download.progress, 100); + do_check_eq(download.currentBytes, 0); + do_check_eq(download.totalBytes, 0); + + do_check_eq((yield OS.File.stat(download.target.path)).size, 0); +}); + +/** + * Calls the "start" method two times before the download is finished. + */ +add_task(function test_start_twice() +{ + // Ensure that the download cannot complete before start is called twice. + let deferResponse = deferNextResponse(); + + let download; + if (!gUseLegacySaver) { + // When testing DownloadCopySaver, we have control over the download, thus + // we can start the download later during the test. + download = yield promiseNewDownload(httpUrl("interruptible.txt")); + } else { + // When testing DownloadLegacySaver, the download is already started when it + // is created. Effectively, we are starting the download three times. + download = yield promiseStartLegacyDownload(httpUrl("interruptible.txt")); + } + + // Call the start method two times. + let promiseAttempt1 = download.start(); + let promiseAttempt2 = download.start(); + + // Allow the download to finish. + deferResponse.resolve(); + + // Both promises should now be resolved. + yield promiseAttempt1; + yield promiseAttempt2; + + do_check_true(download.stopped); + do_check_true(download.succeeded); + do_check_false(download.canceled); + do_check_true(download.error === null); + + yield promiseVerifyContents(download.target.path, + TEST_DATA_SHORT + TEST_DATA_SHORT); +}); + +/** + * Cancels a download and verifies that its state is reported correctly. + */ +add_task(function test_cancel_midway() +{ + let deferResponse = deferNextResponse(); + + // In this test case, we execute different checks that are only possible with + // DownloadCopySaver or DownloadLegacySaver respectively. + let download; + let options = {}; + if (!gUseLegacySaver) { + download = yield promiseNewDownload(httpUrl("interruptible.txt")); + } else { + download = yield promiseStartLegacyDownload(httpUrl("interruptible.txt"), + options); + } + + try { + // Cancel the download after receiving the first part of the response. + let deferCancel = Promise.defer(); + let onchange = function () { + if (!download.stopped && !download.canceled && download.progress == 50) { + deferCancel.resolve(download.cancel()); + + // The state change happens immediately after calling "cancel", but + // temporary files or part files may still exist at this point. + do_check_true(download.canceled); + } + }; + + // Register for the notification, but also call the function directly in + // case the download already reached the expected progress. This may happen + // when using DownloadLegacySaver. + download.onchange = onchange; + onchange(); + + let promiseAttempt; + if (!gUseLegacySaver) { + promiseAttempt = download.start(); + } + + // Wait on the promise returned by the "cancel" method to ensure that the + // cancellation process finished and temporary files were removed. + yield deferCancel.promise; + + if (gUseLegacySaver) { + // The nsIWebBrowserPersist instance should have been canceled now. + do_check_eq(options.outPersist.result, Cr.NS_ERROR_ABORT); + } + + do_check_true(download.stopped); + do_check_true(download.canceled); + do_check_true(download.error === null); + + do_check_false(yield OS.File.exists(download.target.path)); + + // Progress properties are not reset by canceling. + do_check_eq(download.progress, 50); + do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2); + do_check_eq(download.currentBytes, TEST_DATA_SHORT.length); + + if (!gUseLegacySaver) { + // The promise returned by "start" should have been rejected meanwhile. + try { + yield promiseAttempt; + do_throw("The download should have been canceled."); + } catch (ex if ex instanceof Downloads.Error) { + do_check_false(ex.becauseSourceFailed); + do_check_false(ex.becauseTargetFailed); + } + } + } finally { + deferResponse.resolve(); + } +}); + +/** + * Cancels a download right after starting it. + */ +add_task(function test_cancel_immediately() +{ + // Ensure that the download cannot complete before cancel is called. + let deferResponse = deferNextResponse(); + try { + let download = yield promiseStartDownload(httpUrl("interruptible.txt")); + + let promiseAttempt = download.start(); + do_check_false(download.stopped); + + let promiseCancel = download.cancel(); + do_check_true(download.canceled); + + // At this point, we don't know whether the download has already stopped or + // is still waiting for cancellation. We can wait on the promise returned + // by the "start" method to know for sure. + try { + yield promiseAttempt; + do_throw("The download should have been canceled."); + } catch (ex if ex instanceof Downloads.Error) { + do_check_false(ex.becauseSourceFailed); + do_check_false(ex.becauseTargetFailed); + } + + do_check_true(download.stopped); + do_check_true(download.canceled); + do_check_true(download.error === null); + + do_check_false(yield OS.File.exists(download.target.path)); + + // Check that the promise returned by the "cancel" method has been resolved. + yield promiseCancel; + } finally { + deferResponse.resolve(); + } + + // Even if we canceled the download immediately, the HTTP request might have + // been made, and the internal HTTP handler might be waiting to process it. + // Thus, we process any pending events now, to avoid that the request is + // processed during the tests that follow, interfering with them. + for (let i = 0; i < 5; i++) { + yield promiseExecuteSoon(); + } +}); + +/** + * Cancels and restarts a download sequentially. + */ +add_task(function test_cancel_midway_restart() +{ + // TODO: Enable all the restart tests for DownloadLegacySaver. + if (gUseLegacySaver) { + return; + } + + let download = yield promiseNewDownload(httpUrl("interruptible.txt")); + + // The first time, cancel the download midway. + let deferResponse = deferNextResponse(); + try { + let deferCancel = Promise.defer(); + download.onchange = function () { + if (!download.stopped && !download.canceled && download.progress == 50) { + deferCancel.resolve(download.cancel()); + } + }; + download.start(); + yield deferCancel.promise; + } finally { + deferResponse.resolve(); + } + + do_check_true(download.stopped); + + // The second time, we'll provide the entire interruptible response. + download.onchange = null; + let promiseAttempt = download.start(); + + // Download state should have already been reset. + do_check_false(download.stopped); + do_check_false(download.canceled); + do_check_true(download.error === null); + + // For the following test, we rely on the network layer reporting its progress + // asynchronously. Otherwise, there is nothing stopping the restarted + // download from reaching the same progress as the first request already. + do_check_eq(download.progress, 0); + do_check_eq(download.totalBytes, 0); + do_check_eq(download.currentBytes, 0); + + yield promiseAttempt; + + do_check_true(download.stopped); + do_check_true(download.succeeded); + do_check_false(download.canceled); + do_check_true(download.error === null); + + yield promiseVerifyContents(download.target.path, + TEST_DATA_SHORT + TEST_DATA_SHORT); +}); + +/** + * Cancels a download right after starting it, then restarts it immediately. + */ +add_task(function test_cancel_immediately_restart_immediately() +{ + // TODO: Enable all the restart tests for DownloadLegacySaver. + if (gUseLegacySaver) { + return; + } + + let download = yield promiseNewDownload(httpUrl("interruptible.txt")); + + // Ensure that the download cannot complete before cancel is called. + let deferResponse = deferNextResponse(); + + let promiseAttempt = download.start(); + do_check_false(download.stopped); + + download.cancel(); + do_check_true(download.canceled); + + let promiseRestarted = download.start(); + do_check_false(download.stopped); + do_check_false(download.canceled); + do_check_true(download.error === null); + + // For the following test, we rely on the network layer reporting its progress + // asynchronously. Otherwise, there is nothing stopping the restarted + // download from reaching the same progress as the first request already. + do_check_eq(download.hasProgress, false); + do_check_eq(download.progress, 0); + do_check_eq(download.totalBytes, 0); + do_check_eq(download.currentBytes, 0); + + // Even if we canceled the download immediately, the HTTP request might have + // been made, and the internal HTTP handler might be waiting to process it. + // Thus, we process any pending events now, to avoid that the request is + // processed during the tests that follow, interfering with them. + for (let i = 0; i < 5; i++) { + yield promiseExecuteSoon(); + } + + // Ensure the next request is now allowed to complete, regardless of whether + // the canceled request was received by the server or not. + deferResponse.resolve(); + + try { + yield promiseAttempt; + do_throw("The download should have been canceled."); + } catch (ex if ex instanceof Downloads.Error) { + do_check_false(ex.becauseSourceFailed); + do_check_false(ex.becauseTargetFailed); + } + + yield promiseRestarted; + + do_check_true(download.stopped); + do_check_true(download.succeeded); + do_check_false(download.canceled); + do_check_true(download.error === null); + + yield promiseVerifyContents(download.target.path, + TEST_DATA_SHORT + TEST_DATA_SHORT); +}); + +/** + * Cancels a download midway, then restarts it immediately. + */ +add_task(function test_cancel_midway_restart_immediately() +{ + // TODO: Enable all the restart tests for DownloadLegacySaver. + if (gUseLegacySaver) { + return; + } + + let download = yield promiseNewDownload(httpUrl("interruptible.txt")); + + // The first time, cancel the download midway. + let deferResponse = deferNextResponse(); + + let deferMidway = Promise.defer(); + download.onchange = function () { + if (!download.stopped && !download.canceled && download.progress == 50) { + do_check_eq(download.progress, 50); + deferMidway.resolve(); + } + }; + let promiseAttempt = download.start(); + yield deferMidway.promise; + + download.cancel(); + do_check_true(download.canceled); + + let promiseRestarted = download.start(); + do_check_false(download.stopped); + do_check_false(download.canceled); + do_check_true(download.error === null); + + // For the following test, we rely on the network layer reporting its progress + // asynchronously. Otherwise, there is nothing stopping the restarted + // download from reaching the same progress as the first request already. + do_check_eq(download.hasProgress, false); + do_check_eq(download.progress, 0); + do_check_eq(download.totalBytes, 0); + do_check_eq(download.currentBytes, 0); + + deferResponse.resolve(); + + // The second request is allowed to complete. + try { + yield promiseAttempt; + do_throw("The download should have been canceled."); + } catch (ex if ex instanceof Downloads.Error) { + do_check_false(ex.becauseSourceFailed); + do_check_false(ex.becauseTargetFailed); + } + + yield promiseRestarted; + + do_check_true(download.stopped); + do_check_true(download.succeeded); + do_check_false(download.canceled); + do_check_true(download.error === null); + + yield promiseVerifyContents(download.target.path, + TEST_DATA_SHORT + TEST_DATA_SHORT); +}); + +/** + * Calls the "cancel" method on a successful download. + */ +add_task(function test_cancel_successful() +{ + let download = yield promiseStartDownload(); + yield promiseDownloadStopped(download); + + // The cancel method should succeed with no effect. + yield download.cancel(); + + do_check_true(download.stopped); + do_check_true(download.succeeded); + do_check_false(download.canceled); + do_check_true(download.error === null); + + yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); +}); + +/** + * Calls the "cancel" method two times in a row. + */ +add_task(function test_cancel_twice() +{ + // Ensure that the download cannot complete before cancel is called. + let deferResponse = deferNextResponse(); + try { + let download = yield promiseStartDownload(httpUrl("interruptible.txt")); + + let promiseAttempt = download.start(); + do_check_false(download.stopped); + + let promiseCancel1 = download.cancel(); + do_check_true(download.canceled); + let promiseCancel2 = download.cancel(); + + try { + yield promiseAttempt; + do_throw("The download should have been canceled."); + } catch (ex if ex instanceof Downloads.Error) { + do_check_false(ex.becauseSourceFailed); + do_check_false(ex.becauseTargetFailed); + } + + // Both promises should now be resolved. + yield promiseCancel1; + yield promiseCancel2; + + do_check_true(download.stopped); + do_check_false(download.succeeded); + do_check_true(download.canceled); + do_check_true(download.error === null); + + do_check_false(yield OS.File.exists(download.target.path)); + } finally { + deferResponse.resolve(); + } +}); + +/** + * Checks that whenSucceeded returns a promise that is resolved after a restart. + */ +add_task(function test_whenSucceeded_after_restart() +{ + // TODO: Enable all the restart tests for DownloadLegacySaver. + if (gUseLegacySaver) { + return; + } + + let download = yield promiseNewDownload(httpUrl("interruptible.txt")); + + // Ensure that the download cannot complete before cancel is called. + let deferResponse = deferNextResponse(); + + // Get a reference before the first download attempt. + let promiseSucceeded = download.whenSucceeded(); + + // Cancel the first download attempt. + download.start(); + yield download.cancel(); + + deferResponse.resolve(); + + // The second request is allowed to complete. + download.start(); + + // Wait for the download to finish by waiting on the whenSucceeded promise. + yield promiseSucceeded; + + do_check_true(download.stopped); + do_check_true(download.succeeded); + do_check_false(download.canceled); + do_check_true(download.error === null); + + yield promiseVerifyContents(download.target.path, + TEST_DATA_SHORT + TEST_DATA_SHORT); +}); + +/** + * Ensures download error details are reported on network failures. + */ +add_task(function test_error_source() +{ + let serverSocket = startFakeServer(); + try { + let sourceUrl = "http://localhost:" + serverSocket.port + "/source.txt"; + + 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(sourceUrl); + + do_check_true(download.error === null); + + yield download.start(); + } else { + // When testing DownloadLegacySaver, we cannot be sure whether we are + // testing the promise returned by the "start" method or we are testing + // the "error" property checked by promiseDownloadStopped. This happens + // because we don't have control over when the download is started. + download = yield promiseStartLegacyDownload(sourceUrl); + yield promiseDownloadStopped(download); + } + do_throw("The download should have failed."); + } catch (ex if ex instanceof Downloads.Error && ex.becauseSourceFailed) { + // A specific error object is thrown when reading from the source fails. + } + + // Check the properties now that the download stopped. + do_check_true(download.stopped); + do_check_false(download.canceled); + do_check_true(download.error !== null); + do_check_true(download.error.becauseSourceFailed); + do_check_false(download.error.becauseTargetFailed); + } finally { + serverSocket.close(); + } +}); + +/** + * Ensures download error details are reported on local writing failures. + */ +add_task(function test_error_target() +{ + // Create a file without write access permissions before downloading. + let targetFile = getTempFile(TEST_TARGET_FILE_NAME); + targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0); + try { + let download; + try { + if (!gUseLegacySaver) { + // When testing DownloadCopySaver, we want to check that the promise + // returned by the "start" method is rejected. + download = yield Downloads.createDownload({ + source: httpUrl("source.txt"), + target: targetFile, + }); + yield download.start(); + } else { + // When testing DownloadLegacySaver, we cannot be sure whether we are + // testing the promise returned by the "start" method or we are testing + // the "error" property checked by promiseDownloadStopped. This happens + // because we don't have control over when the download is started. + download = yield promiseStartLegacyDownload(null, + { targetFile: targetFile }); + yield promiseDownloadStopped(download); + } + do_throw("The download should have failed."); + } catch (ex if ex instanceof Downloads.Error && ex.becauseTargetFailed) { + // A specific error object is thrown when writing to the target fails. + } + + // Check the properties now that the download stopped. + do_check_true(download.stopped); + do_check_false(download.canceled); + do_check_true(download.error !== null); + do_check_true(download.error.becauseTargetFailed); + do_check_false(download.error.becauseSourceFailed); + } finally { + // Restore the default permissions to allow deleting the file on Windows. + if (targetFile.exists()) { + targetFile.permissions = FileUtils.PERMS_FILE; + targetFile.remove(false); + } + } +}); + +/** + * Restarts a failed download. + */ +add_task(function test_error_restart() +{ + // TODO: Enable all the restart tests for DownloadLegacySaver. + if (gUseLegacySaver) { + return; + } + + let download = yield promiseNewDownload(); + + do_check_true(download.error === null); + + // Create a file without write access permissions before downloading. + let targetFile = new FileUtils.File(download.target.path); + targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0); + + try { + yield download.start(); + do_throw("The download should have failed."); + } catch (ex if ex instanceof Downloads.Error && ex.becauseTargetFailed) { + // A specific error object is thrown when writing to the target fails. + } finally { + // Restore the default permissions to allow deleting the file on Windows. + if (targetFile.exists()) { + targetFile.permissions = FileUtils.PERMS_FILE; + + // Also for Windows, rename the file before deleting. This makes the + // current file name available immediately for a new file, while deleting + // in place prevents creation of a file with the same name for some time. + targetFile.moveTo(null, targetFile.leafName + ".delete.tmp"); + targetFile.remove(false); + } + } + + // Restart the download and wait for completion. + yield download.start(); + + do_check_true(download.stopped); + do_check_true(download.succeeded); + do_check_false(download.canceled); + do_check_true(download.error === null); + do_check_eq(download.progress, 100); + + yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); +}); + +/** + * Executes download in both public and private modes. + */ +add_task(function test_public_and_private() +{ + let sourcePath = "/test_public_and_private.txt"; + let sourceUrl = httpUrl("test_public_and_private.txt"); + let testCount = 0; + + // Apply pref to allow all cookies. + Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); + + function cleanup() { + Services.prefs.clearUserPref("network.cookie.cookieBehavior"); + Services.cookies.removeAll(); + gHttpServer.registerPathHandler(sourcePath, null); + } + do_register_cleanup(cleanup); + + gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) { + aResponse.setHeader("Content-Type", "text/plain", false); + + if (testCount == 0) { + // No cookies should exist for first public download. + do_check_false(aRequest.hasHeader("Cookie")); + aResponse.setHeader("Set-Cookie", "foobar=1", false); + testCount++; + } else if (testCount == 1) { + // The cookie should exists for second public download. + do_check_true(aRequest.hasHeader("Cookie")); + do_check_eq(aRequest.getHeader("Cookie"), "foobar=1"); + testCount++; + } else if (testCount == 2) { + // No cookies should exist for first private download. + do_check_false(aRequest.hasHeader("Cookie")); + } + }); + + let targetFile = getTempFile(TEST_TARGET_FILE_NAME); + yield Downloads.simpleDownload(sourceUrl, targetFile); + yield Downloads.simpleDownload(sourceUrl, targetFile); + + if (!gUseLegacySaver) { + let download = yield Downloads.createDownload({ + source: { url: sourceUrl, isPrivate: true }, + target: targetFile, + }); + yield download.start(); + } else { + let download = yield promiseStartLegacyDownload(sourceUrl, + { isPrivate: true }); + yield promiseDownloadStopped(download); + } + + cleanup(); +}); + +/** + * Checks the startTime gets updated even after a restart. + */ +add_task(function test_cancel_immediately_restart_and_check_startTime() +{ + // TODO: Enable all the restart tests for DownloadLegacySaver. + if (gUseLegacySaver) { + return; + } + + let download = yield promiseNewDownload(); + + download.start(); + let startTime = download.startTime; + do_check_true(isValidDate(download.startTime)); + + yield download.cancel(); + do_check_eq(download.startTime.getTime(), startTime.getTime()); + + // Wait for a timeout. + yield promiseTimeout(10); + + yield download.start(); + do_check_true(download.startTime.getTime() > startTime.getTime()); +}); + +/** + * Executes download with content-encoding. + */ +add_task(function test_with_content_encoding() +{ + let sourcePath = "/test_with_content_encoding.txt"; + let sourceUrl = httpUrl("test_with_content_encoding.txt"); + + function cleanup() { + gHttpServer.registerPathHandler(sourcePath, null); + } + do_register_cleanup(cleanup); + + gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) { + aResponse.setHeader("Content-Type", "text/plain", false); + aResponse.setHeader("Content-Encoding", "gzip", false); + aResponse.setHeader("Content-Length", + "" + TEST_DATA_SHORT_GZIP_ENCODED.length, false); + + let bos = new BinaryOutputStream(aResponse.bodyOutputStream); + bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED, + TEST_DATA_SHORT_GZIP_ENCODED.length); + }); + + let download = yield promiseStartDownload(sourceUrl); + yield promiseDownloadStopped(download); + + do_check_eq(download.progress, 100); + do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length); + + // Ensure the content matches the decoded test data. + yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); +}); + +/** + * Cancels and restarts a download sequentially with content-encoding. + */ +add_task(function test_cancel_midway_restart_with_content_encoding() +{ + // TODO: Enable all the restart tests for DownloadLegacySaver. + if (gUseLegacySaver) { + return; + } + + let download = yield promiseNewDownload(httpUrl("interruptible_gzip.txt")); + + // The first time, cancel the download midway. + let deferResponse = deferNextResponse(); + try { + let deferCancel = Promise.defer(); + download.onchange = function () { + if (!download.stopped && !download.canceled && + download.currentBytes == TEST_DATA_SHORT_GZIP_ENCODED_FIRST.length) { + deferCancel.resolve(download.cancel()); + } + }; + download.start(); + yield deferCancel.promise; + } finally { + deferResponse.resolve(); + } + + do_check_true(download.stopped); + + // The second time, we'll provide the entire interruptible response. + download.onchange = null; + yield download.start() + + do_check_eq(download.progress, 100); + do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length); + + yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); +}); + +/** + * Download with parental controls enabled. + */ +add_task(function test_blocked_parental_controls() +{ + function cleanup() { + DownloadIntegration.shouldBlockInTest = false; + } + 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(); + } else { + // When testing DownloadLegacySaver, we cannot be sure whether we are + // testing the promise returned by the "start" method or we are testing + // the "error" property checked by promiseDownloadStopped. This happens + // because we don't have control over when the download is started. + download = yield promiseStartLegacyDownload(); + yield promiseDownloadStopped(download); + } + do_throw("The download should have blocked."); + } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) { + do_check_true(ex.becauseBlockedByParentalControls); + } + + // Now that the download stopped, the target file should not exist. + do_check_false(yield OS.File.exists(download.target.path)); + + cleanup(); +}); + diff --git a/toolkit/components/jsdownloads/test/unit/head.js b/toolkit/components/jsdownloads/test/unit/head.js index 295113f5d26..10f43760d0c 100644 --- a/toolkit/components/jsdownloads/test/unit/head.js +++ b/toolkit/components/jsdownloads/test/unit/head.js @@ -51,51 +51,11 @@ const BinaryOutputStream = Components.Constructor( "nsIBinaryOutputStream", "setOutputStream") -Object.defineProperty(this, "HTTP_BASE", {get: function() { - return "http://localhost:" + gHttpServer.identity.primaryPort; -}}); - -Object.defineProperty(this, "FAKE_BASE", {get: function() { - return "http://localhost:" + gFakeServerPort; -}}); - -Object.defineProperty(this, "TEST_REFERRER_URI", {get: function() { - return NetUtil.newURI(HTTP_BASE + "/referrer.html"); -}}); - -Object.defineProperty(this, "TEST_SOURCE_URI", {get: function() { - return NetUtil.newURI(HTTP_BASE + "/source.txt"); -}}); - -Object.defineProperty(this, "TEST_EMPTY_URI", {get: function() { - return NetUtil.newURI(HTTP_BASE + "/empty.txt"); -}}); - -Object.defineProperty(this, "TEST_FAKE_SOURCE_URI", {get: function() { - return NetUtil.newURI(FAKE_BASE + "/source.txt"); -}}); - -const TEST_EMPTY_NOPROGRESS_PATH = "/empty-noprogress.txt"; - -Object.defineProperty(this, "TEST_EMPTY_NOPROGRESS_URI", {get: function() { - return NetUtil.newURI(HTTP_BASE + TEST_EMPTY_NOPROGRESS_PATH); -}}); - -const TEST_INTERRUPTIBLE_PATH = "/interruptible.txt"; - -Object.defineProperty(this, "TEST_INTERRUPTIBLE_URI", {get: function() { - return NetUtil.newURI(HTTP_BASE + TEST_INTERRUPTIBLE_PATH); -}}); - -const TEST_INTERRUPTIBLE_GZIP_PATH = "/interruptible_gzip.txt"; - -Object.defineProperty(this, "TEST_INTERRUPTIBLE_GZIP_URI", {get: function() { - return NetUtil.newURI(HTTP_BASE + TEST_INTERRUPTIBLE_GZIP_PATH); -}}); - const TEST_TARGET_FILE_NAME = "test-download.txt"; const TEST_STORE_FILE_NAME = "test-downloads.json"; +const TEST_REFERRER_URL = "http://www.example.com/referrer.html"; + const TEST_DATA_SHORT = "This test string is downloaded."; // Generate using gzipCompressString in TelemetryPing.js. const TEST_DATA_SHORT_GZIP_ENCODED_FIRST = [ @@ -119,6 +79,20 @@ function run_test() //////////////////////////////////////////////////////////////////////////////// //// Support functions +/** + * HttpServer object initialized before tests start. + */ +let gHttpServer; + +/** + * Given a file name, returns a string containing an URI that points to the file + * on the currently running instance of the test HTTP server. + */ +function httpUrl(aFileName) { + return "http://localhost:" + gHttpServer.identity.primaryPort + "/" + + aFileName; +} + // While the previous test file should have deleted all the temporary files it // used, on Windows these might still be pending deletion on the physical file // system. Thus, start from a new base number every time, to make a collision @@ -190,21 +164,101 @@ function promiseTimeout(aTime) /** * Creates a new Download object, setting a temporary file as the target. * - * @param aSourceURI - * The nsIURI for the download source, or null to use TEST_SOURCE_URI. + * @param aSourceUrl + * String containing the URI for the download source, or null to use + * httpUrl("source.txt"). * * @return {Promise} * @resolves The newly created Download object. * @rejects JavaScript exception. */ -function promiseSimpleDownload(aSourceURI) { +function promiseNewDownload(aSourceUrl) { return Downloads.createDownload({ - source: { uri: aSourceURI || TEST_SOURCE_URI }, - target: { file: getTempFile(TEST_TARGET_FILE_NAME) }, - saver: { type: "copy" }, + source: aSourceUrl || httpUrl("source.txt"), + target: getTempFile(TEST_TARGET_FILE_NAME), }); } +/** + * Starts a new download using the nsIWebBrowserPersist interface, and controls + * it using the legacy nsITransfer interface. + * + * @param aSourceUrl + * String containing the URI for the download source, or null to use + * httpUrl("source.txt"). + * @param aOptions + * An optional object used to control the behavior of this function. + * You may pass an object with a subset of the following fields: + * { + * isPrivate: Boolean indicating whether the download originated from a + * private window. + * targetFile: nsIFile for the target, or null to use a temporary file. + * outPersist: Receives a reference to the created nsIWebBrowserPersist + * instance. + * } + * + * @return {Promise} + * @resolves The Download object created as a consequence of controlling the + * download through the legacy nsITransfer interface. + * @rejects Never. The current test fails in case of exceptions. + */ +function promiseStartLegacyDownload(aSourceUrl, aOptions) { + let sourceURI = NetUtil.newURI(aSourceUrl || httpUrl("source.txt")); + let targetFile = (aOptions && aOptions.targetFile) + || getTempFile(TEST_TARGET_FILE_NAME); + + let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + if (aOptions) { + aOptions.outPersist = persist; + } + + // Apply decoding if required by the "Content-Encoding" header. + persist.persistFlags &= ~Ci.nsIWebBrowserPersist.PERSIST_FLAGS_NO_CONVERSION; + + // We must create the nsITransfer implementation using its class ID because + // the "@mozilla.org/transfer;1" contract is currently implemented in + // "toolkit/components/downloads". When the other folder is not included in + // builds anymore (bug 851471), we'll be able to use the contract ID. + let transfer = + Components.classesByID["{1b4c85df-cbdd-4bb6-b04e-613caece083c}"] + .createInstance(Ci.nsITransfer); + + let deferred = Promise.defer(); + + let isPrivate = aOptions && aOptions.isPrivate; + let promise = isPrivate ? Downloads.getPrivateDownloadList() + : Downloads.getPublicDownloadList(); + promise.then(function (aList) { + // Temporarily register a view that will get notified when the download we + // are controlling becomes visible in the list of downloads. + aList.addView({ + onDownloadAdded: function (aDownload) { + aList.removeView(this); + + // Remove the download to keep the list empty for the next test. This + // also allows the caller to register the "onchange" event directly. + aList.remove(aDownload); + + // When the download object is ready, make it available to the caller. + deferred.resolve(aDownload); + }, + }); + + // Initialize the components so they reference each other. This will cause + // the Download object to be created and added to the public downloads. + transfer.init(sourceURI, NetUtil.newURI(targetFile), null, null, null, null, + persist, isPrivate); + persist.progressListener = transfer; + + // Start the actual download process. + persist.savePrivacyAwareURI(sourceURI, null, null, null, null, targetFile, + isPrivate); + }.bind(this)).then(null, do_report_unexpected_exception); + + return deferred.promise; +} + /** * Returns a new public DownloadList object. * @@ -234,8 +288,9 @@ function promiseNewPrivateDownloadList() { /** * Ensures that the given file contents are equal to the given string. * - * @param aFile - * nsIFile whose contents should be verified. + * @param aPath + * String containing the path of the file whose contents should be + * verified. * @param aExpectedContents * String containing the octets that are expected in the file. * @@ -243,10 +298,11 @@ function promiseNewPrivateDownloadList() { * @resolves When the operation completes. * @rejects Never. */ -function promiseVerifyContents(aFile, aExpectedContents) +function promiseVerifyContents(aPath, aExpectedContents) { let deferred = Promise.defer(); - NetUtil.asyncFetch(aFile, function(aInputStream, aStatus) { + let file = new FileUtils.File(aPath); + NetUtil.asyncFetch(file, function(aInputStream, aStatus) { do_check_true(Components.isSuccessCode(aStatus)); let contents = NetUtil.readInputStreamToString(aInputStream, aInputStream.available()); @@ -265,17 +321,18 @@ function promiseVerifyContents(aFile, aExpectedContents) /** * Adds entry for download. * - * @param aSourceURI - * The nsIURI for the download source, or null to use TEST_SOURCE_URI. + * @param aSourceUrl + * String containing the URI for the download source, or null to use + * httpUrl("source.txt"). * * @return {Promise} * @rejects JavaScript exception. */ -function promiseAddDownloadToHistory(aSourceURI) { +function promiseAddDownloadToHistory(aSourceUrl) { let deferred = Promise.defer(); PlacesUtils.asyncHistory.updatePlaces( { - uri: aSourceURI || TEST_SOURCE_URI, + uri: NetUtil.newURI(aSourceUrl || httpUrl("source.txt")), visits: [{ transitionType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD, visitDate: Date.now() @@ -304,7 +361,6 @@ function promiseAddDownloadToHistory(aSourceURI) { function startFakeServer() { let serverSocket = new ServerSocket(-1, true, -1); - gFakeServerPort = serverSocket.port; serverSocket.asyncListen({ onSocketAccepted: function (aServ, aTransport) { aTransport.close(Cr.NS_BINDING_ABORTED); @@ -326,10 +382,10 @@ function startFakeServer() * handlers, you may call "deferNextResponse" to get a reference to an object * that allows you to control the next request. * - * For example, the handler accessible at the TEST_INTERRUPTIBLE_URI address - * returns the TEST_DATA_SHORT text, then waits until the "resolve" method is - * called on the object returned by the function. At this point, the handler - * sends the TEST_DATA_SHORT text again to complete the response. + * For example, the handler accessible at the httpUri("interruptible.txt") + * address returns the TEST_DATA_SHORT text, then waits until the "resolve" + * method is called on the object returned by the function. At this point, the + * handler sends the TEST_DATA_SHORT text again to complete the response. * * You can also call the "reject" method on the returned object to interrupt the * response midway. Because of how the network layer is implemented, this does @@ -429,9 +485,6 @@ function isValidDate(aDate) { //////////////////////////////////////////////////////////////////////////////// //// Initialization functions common to all tests -let gHttpServer; -let gFakeServerPort; - add_task(function test_common_initialize() { // Start the HTTP server. @@ -439,7 +492,7 @@ add_task(function test_common_initialize() gHttpServer.registerDirectory("/", do_get_file("../data")); gHttpServer.start(-1); - registerInterruptibleHandler(TEST_INTERRUPTIBLE_PATH, + registerInterruptibleHandler("/interruptible.txt", function firstPart(aRequest, aResponse) { aResponse.setHeader("Content-Type", "text/plain", false); aResponse.setHeader("Content-Length", "" + (TEST_DATA_SHORT.length * 2), @@ -449,13 +502,13 @@ add_task(function test_common_initialize() aResponse.write(TEST_DATA_SHORT); }); - registerInterruptibleHandler(TEST_EMPTY_NOPROGRESS_PATH, + registerInterruptibleHandler("/empty-noprogress.txt", function firstPart(aRequest, aResponse) { aResponse.setHeader("Content-Type", "text/plain", false); }, function secondPart(aRequest, aResponse) { }); - registerInterruptibleHandler(TEST_INTERRUPTIBLE_GZIP_PATH, + registerInterruptibleHandler("/interruptible_gzip.txt", function firstPart(aRequest, aResponse) { aResponse.setHeader("Content-Type", "text/plain", false); aResponse.setHeader("Content-Encoding", "gzip", false); diff --git a/toolkit/components/jsdownloads/test/unit/test_DownloadCore.js b/toolkit/components/jsdownloads/test/unit/test_DownloadCore.js index aa57bbc729c..96e1592d7b8 100644 --- a/toolkit/components/jsdownloads/test/unit/test_DownloadCore.js +++ b/toolkit/components/jsdownloads/test/unit/test_DownloadCore.js @@ -4,894 +4,15 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ /** - * Tests the objects defined in the "DownloadCore" module. + * Tests the main download interfaces using DownloadCopySaver. */ "use strict"; //////////////////////////////////////////////////////////////////////////////// -//// Tests +//// Execution of common tests -/** - * Executes a download, started by constructing the simplest Download object. - */ -add_task(function test_download_construction() -{ - let targetFile = getTempFile(TEST_TARGET_FILE_NAME); - - let download = yield Downloads.createDownload({ - source: { uri: TEST_SOURCE_URI }, - target: { file: targetFile }, - saver: { type: "copy" }, - }); - - // Checks the generated DownloadSource and DownloadTarget properties. - do_check_true(download.source.uri.equals(TEST_SOURCE_URI)); - do_check_eq(download.target.file, targetFile); - do_check_true(download.source.referrer === null); - - // Starts the download and waits for completion. - yield download.start(); - - yield promiseVerifyContents(targetFile, TEST_DATA_SHORT); -}); - -/** - * Checks the referrer for downloads. - */ -add_task(function test_download_referrer() -{ - let source_path = "/test_download_referrer.txt"; - let source_uri = NetUtil.newURI(HTTP_BASE + source_path); - let target_uri = getTempFile(TEST_TARGET_FILE_NAME); - - function cleanup() { - gHttpServer.registerPathHandler(source_path, null); - } - - do_register_cleanup(cleanup); - - gHttpServer.registerPathHandler(source_path, function (aRequest, aResponse) { - aResponse.setHeader("Content-Type", "text/plain", false); - - do_check_true(aRequest.hasHeader("Referer")); - do_check_eq(aRequest.getHeader("Referer"), TEST_REFERRER_URI.spec); - }); - let download = yield Downloads.createDownload({ - source: { uri: source_uri, referrer: TEST_REFERRER_URI }, - target: { file: target_uri }, - saver: { type: "copy" }, - }); - do_check_true(download.source.referrer.equals(TEST_REFERRER_URI)); - yield download.start(); - - download = yield Downloads.createDownload({ - source: { uri: source_uri, referrer: TEST_REFERRER_URI, isPrivate: true }, - target: { file: target_uri }, - saver: { type: "copy" }, - }); - do_check_true(download.source.referrer.equals(TEST_REFERRER_URI)); - yield download.start(); - - // Test the download still works for non-HTTP channel with referrer. - source_uri = NetUtil.newURI("data:text/html,"); - download = yield Downloads.createDownload({ - source: { uri: source_uri, referrer: TEST_REFERRER_URI }, - target: { file: target_uri }, - saver: { type: "copy" }, - }); - do_check_true(download.source.referrer.equals(TEST_REFERRER_URI)); - yield download.start(); - - cleanup(); -}); - -/** - * Checks initial and final state and progress for a successful download. - */ -add_task(function test_download_initial_final_state() -{ - let download = yield promiseSimpleDownload(); - - do_check_true(download.stopped); - do_check_false(download.succeeded); - do_check_false(download.canceled); - do_check_true(download.error === null); - do_check_eq(download.progress, 0); - do_check_true(download.startTime === null); - - // Starts the download and waits for completion. - yield download.start(); - - do_check_true(download.stopped); - do_check_true(download.succeeded); - do_check_false(download.canceled); - do_check_true(download.error === null); - do_check_eq(download.progress, 100); - do_check_true(isValidDate(download.startTime)); -}); - -/** - * Checks the notification of the final download state. - */ -add_task(function test_download_final_state_notified() -{ - let download = yield promiseSimpleDownload(); - - let onchangeNotified = false; - let lastNotifiedStopped; - let lastNotifiedProgress; - download.onchange = function () { - onchangeNotified = true; - lastNotifiedStopped = download.stopped; - lastNotifiedProgress = download.progress; - }; - - // Starts the download and waits for completion. - yield download.start(); - - // The view should have been notified before the download completes. - do_check_true(onchangeNotified); - do_check_true(lastNotifiedStopped); - do_check_eq(lastNotifiedProgress, 100); -}); - -/** - * Checks intermediate progress for a successful download. - */ -add_task(function test_download_intermediate_progress() -{ - let deferResponse = deferNextResponse(); - - let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI); - - download.onchange = function () { - if (download.progress == 50) { - do_check_true(download.hasProgress); - do_check_eq(download.currentBytes, TEST_DATA_SHORT.length); - do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2); - - // Continue after the first chunk of data is fully received. - deferResponse.resolve(); - } - }; - - // Starts the download and waits for completion. - yield download.start(); - - do_check_true(download.stopped); - do_check_eq(download.progress, 100); - - yield promiseVerifyContents(download.target.file, - TEST_DATA_SHORT + TEST_DATA_SHORT); -}); - -/** - * Downloads a file with a "Content-Length" of 0 and checks the progress. - */ -add_task(function test_download_empty_progress() -{ - let download = yield promiseSimpleDownload(TEST_EMPTY_URI); - - yield download.start(); - - do_check_true(download.stopped); - do_check_true(download.hasProgress); - do_check_eq(download.progress, 100); - do_check_eq(download.currentBytes, 0); - do_check_eq(download.totalBytes, 0); - - do_check_eq(download.target.file.fileSize, 0); -}); - -/** - * Downloads an empty file with no "Content-Length" and checks the progress. - */ -add_task(function test_download_empty_noprogress() -{ - let deferResponse = deferNextResponse(); - let promiseEmptyRequestReceived = promiseNextRequestReceived(); - - let download = yield promiseSimpleDownload(TEST_EMPTY_NOPROGRESS_URI); - - download.onchange = function () { - if (!download.stopped) { - do_check_false(download.hasProgress); - do_check_eq(download.currentBytes, 0); - do_check_eq(download.totalBytes, 0); - } - }; - - // Start the download, while waiting for the request to be received. - let promiseAttempt = download.start(); - - // Wait for the request to be received by the HTTP server, but don't allow the - // request to finish yet. Before checking the download state, wait for the - // events to be processed by the client. - yield promiseEmptyRequestReceived; - yield promiseExecuteSoon(); - - // Check that this download has no progress report. - do_check_false(download.stopped); - do_check_false(download.hasProgress); - do_check_eq(download.currentBytes, 0); - do_check_eq(download.totalBytes, 0); - - // Now allow the response to finish, and wait for the download to complete. - deferResponse.resolve(); - yield promiseAttempt; - - // Verify the state of the completed download. - do_check_true(download.stopped); - do_check_false(download.hasProgress); - do_check_eq(download.progress, 100); - do_check_eq(download.currentBytes, 0); - do_check_eq(download.totalBytes, 0); - - do_check_eq(download.target.file.fileSize, 0); -}); - -/** - * Calls the "start" method two times before the download is finished. - */ -add_task(function test_download_start_twice() -{ - let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI); - - // Ensure that the download cannot complete before start is called twice. - let deferResponse = deferNextResponse(); - - // Call the start method two times. - let promiseAttempt1 = download.start(); - let promiseAttempt2 = download.start(); - - // Allow the download to finish. - deferResponse.resolve(); - - // Both promises should now be resolved. - yield promiseAttempt1; - yield promiseAttempt2; - - do_check_true(download.stopped); - do_check_true(download.succeeded); - do_check_false(download.canceled); - do_check_true(download.error === null); - - yield promiseVerifyContents(download.target.file, - TEST_DATA_SHORT + TEST_DATA_SHORT); -}); - -/** - * Cancels a download and verifies that its state is reported correctly. - */ -add_task(function test_download_cancel_midway() -{ - let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI); - - let deferResponse = deferNextResponse(); - try { - // Cancel the download after receiving the first part of the response. - let deferCancel = Promise.defer(); - download.onchange = function () { - if (!download.stopped && !download.canceled && download.progress == 50) { - deferCancel.resolve(download.cancel()); - - // The state change happens immediately after calling "cancel", but - // temporary files or part files may still exist at this point. - do_check_true(download.canceled); - } - }; - - let promiseAttempt = download.start(); - - // Wait on the promise returned by the "cancel" method to ensure that the - // cancellation process finished and temporary files were removed. - yield deferCancel.promise; - - do_check_true(download.stopped); - do_check_true(download.canceled); - do_check_true(download.error === null); - - do_check_false(download.target.file.exists()); - - // Progress properties are not reset by canceling. - do_check_eq(download.progress, 50); - do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2); - do_check_eq(download.currentBytes, TEST_DATA_SHORT.length); - - // The promise returned by "start" should have been rejected meanwhile. - try { - yield promiseAttempt; - do_throw("The download should have been canceled."); - } catch (ex if ex instanceof Downloads.Error) { - do_check_false(ex.becauseSourceFailed); - do_check_false(ex.becauseTargetFailed); - } - } finally { - deferResponse.resolve(); - } -}); - -/** - * Cancels a download right after starting it. - */ -add_task(function test_download_cancel_immediately() -{ - // Ensure that the download cannot complete before cancel is called. - let deferResponse = deferNextResponse(); - try { - let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI); - - let promiseAttempt = download.start(); - do_check_false(download.stopped); - - let promiseCancel = download.cancel(); - do_check_true(download.canceled); - - // At this point, we don't know whether the download has already stopped or - // is still waiting for cancellation. We can wait on the promise returned - // by the "start" method to know for sure. - try { - yield promiseAttempt; - do_throw("The download should have been canceled."); - } catch (ex if ex instanceof Downloads.Error) { - do_check_false(ex.becauseSourceFailed); - do_check_false(ex.becauseTargetFailed); - } - - do_check_true(download.stopped); - do_check_true(download.canceled); - do_check_true(download.error === null); - - do_check_false(download.target.file.exists()); - - // Check that the promise returned by the "cancel" method has been resolved. - yield promiseCancel; - } finally { - deferResponse.resolve(); - } - - // Even if we canceled the download immediately, the HTTP request might have - // been made, and the internal HTTP handler might be waiting to process it. - // Thus, we process any pending events now, to avoid that the request is - // processed during the tests that follow, interfering with them. - for (let i = 0; i < 5; i++) { - yield promiseExecuteSoon(); - } -}); - -/** - * Cancels and restarts a download sequentially. - */ -add_task(function test_download_cancel_midway_restart() -{ - let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI); - - // The first time, cancel the download midway. - let deferResponse = deferNextResponse(); - try { - let deferCancel = Promise.defer(); - download.onchange = function () { - if (!download.stopped && !download.canceled && download.progress == 50) { - deferCancel.resolve(download.cancel()); - } - }; - download.start(); - yield deferCancel.promise; - } finally { - deferResponse.resolve(); - } - - do_check_true(download.stopped); - - // The second time, we'll provide the entire interruptible response. - download.onchange = null; - let promiseAttempt = download.start(); - - // Download state should have already been reset. - do_check_false(download.stopped); - do_check_false(download.canceled); - do_check_true(download.error === null); - - // For the following test, we rely on the network layer reporting its progress - // asynchronously. Otherwise, there is nothing stopping the restarted - // download from reaching the same progress as the first request already. - do_check_eq(download.progress, 0); - do_check_eq(download.totalBytes, 0); - do_check_eq(download.currentBytes, 0); - - yield promiseAttempt; - - do_check_true(download.stopped); - do_check_true(download.succeeded); - do_check_false(download.canceled); - do_check_true(download.error === null); - - yield promiseVerifyContents(download.target.file, - TEST_DATA_SHORT + TEST_DATA_SHORT); -}); - -/** - * Cancels a download right after starting it, then restarts it immediately. - */ -add_task(function test_download_cancel_immediately_restart_immediately() -{ - let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI); - - // Ensure that the download cannot complete before cancel is called. - let deferResponse = deferNextResponse(); - - let promiseAttempt = download.start(); - do_check_false(download.stopped); - - download.cancel(); - do_check_true(download.canceled); - - let promiseRestarted = download.start(); - do_check_false(download.stopped); - do_check_false(download.canceled); - do_check_true(download.error === null); - - // For the following test, we rely on the network layer reporting its progress - // asynchronously. Otherwise, there is nothing stopping the restarted - // download from reaching the same progress as the first request already. - do_check_eq(download.hasProgress, false); - do_check_eq(download.progress, 0); - do_check_eq(download.totalBytes, 0); - do_check_eq(download.currentBytes, 0); - - // Even if we canceled the download immediately, the HTTP request might have - // been made, and the internal HTTP handler might be waiting to process it. - // Thus, we process any pending events now, to avoid that the request is - // processed during the tests that follow, interfering with them. - for (let i = 0; i < 5; i++) { - yield promiseExecuteSoon(); - } - - // Ensure the next request is now allowed to complete, regardless of whether - // the canceled request was received by the server or not. - deferResponse.resolve(); - - try { - yield promiseAttempt; - do_throw("The download should have been canceled."); - } catch (ex if ex instanceof Downloads.Error) { - do_check_false(ex.becauseSourceFailed); - do_check_false(ex.becauseTargetFailed); - } - - yield promiseRestarted; - - do_check_true(download.stopped); - do_check_true(download.succeeded); - do_check_false(download.canceled); - do_check_true(download.error === null); - - yield promiseVerifyContents(download.target.file, - TEST_DATA_SHORT + TEST_DATA_SHORT); -}); - -/** - * Cancels a download midway, then restarts it immediately. - */ -add_task(function test_download_cancel_midway_restart_immediately() -{ - let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI); - - // The first time, cancel the download midway. - let deferResponse = deferNextResponse(); - - let deferMidway = Promise.defer(); - download.onchange = function () { - if (!download.stopped && !download.canceled && download.progress == 50) { - do_check_eq(download.progress, 50); - deferMidway.resolve(); - } - }; - let promiseAttempt = download.start(); - yield deferMidway.promise; - - download.cancel(); - do_check_true(download.canceled); - - let promiseRestarted = download.start(); - do_check_false(download.stopped); - do_check_false(download.canceled); - do_check_true(download.error === null); - - // For the following test, we rely on the network layer reporting its progress - // asynchronously. Otherwise, there is nothing stopping the restarted - // download from reaching the same progress as the first request already. - do_check_eq(download.hasProgress, false); - do_check_eq(download.progress, 0); - do_check_eq(download.totalBytes, 0); - do_check_eq(download.currentBytes, 0); - - deferResponse.resolve(); - - // The second request is allowed to complete. - try { - yield promiseAttempt; - do_throw("The download should have been canceled."); - } catch (ex if ex instanceof Downloads.Error) { - do_check_false(ex.becauseSourceFailed); - do_check_false(ex.becauseTargetFailed); - } - - yield promiseRestarted; - - do_check_true(download.stopped); - do_check_true(download.succeeded); - do_check_false(download.canceled); - do_check_true(download.error === null); - - yield promiseVerifyContents(download.target.file, - TEST_DATA_SHORT + TEST_DATA_SHORT); -}); - -/** - * Calls the "cancel" method on a successful download. - */ -add_task(function test_download_cancel_successful() -{ - let download = yield promiseSimpleDownload(); - - // Starts the download and waits for completion. - yield download.start(); - - // The cancel method should succeed with no effect. - yield download.cancel(); - - do_check_true(download.stopped); - do_check_true(download.succeeded); - do_check_false(download.canceled); - do_check_true(download.error === null); - - yield promiseVerifyContents(download.target.file, TEST_DATA_SHORT); -}); - -/** - * Calls the "cancel" method two times in a row. - */ -add_task(function test_download_cancel_twice() -{ - let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI); - - // Ensure that the download cannot complete before cancel is called. - let deferResponse = deferNextResponse(); - try { - let promiseAttempt = download.start(); - do_check_false(download.stopped); - - let promiseCancel1 = download.cancel(); - do_check_true(download.canceled); - let promiseCancel2 = download.cancel(); - - try { - yield promiseAttempt; - do_throw("The download should have been canceled."); - } catch (ex if ex instanceof Downloads.Error) { - do_check_false(ex.becauseSourceFailed); - do_check_false(ex.becauseTargetFailed); - } - - // Both promises should now be resolved. - yield promiseCancel1; - yield promiseCancel2; - - do_check_true(download.stopped); - do_check_false(download.succeeded); - do_check_true(download.canceled); - do_check_true(download.error === null); - - do_check_false(download.target.file.exists()); - } finally { - deferResponse.resolve(); - } -}); - -/** - * Checks that whenSucceeded returns a promise that is resolved after a restart. - */ -add_task(function test_download_whenSucceeded() -{ - let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI); - - // Ensure that the download cannot complete before cancel is called. - let deferResponse = deferNextResponse(); - - // Get a reference before the first download attempt. - let promiseSucceeded = download.whenSucceeded(); - - // Cancel the first download attempt. - download.start(); - yield download.cancel(); - - deferResponse.resolve(); - - // The second request is allowed to complete. - download.start(); - - // Wait for the download to finish by waiting on the whenSucceeded promise. - yield promiseSucceeded; - - do_check_true(download.stopped); - do_check_true(download.succeeded); - do_check_false(download.canceled); - do_check_true(download.error === null); - - yield promiseVerifyContents(download.target.file, - TEST_DATA_SHORT + TEST_DATA_SHORT); -}); - -/** - * Ensures download error details are reported on network failures. - */ -add_task(function test_download_error_source() -{ - let serverSocket = startFakeServer(); - try { - let download = yield promiseSimpleDownload(TEST_FAKE_SOURCE_URI); - - do_check_true(download.error === null); - - try { - yield download.start(); - do_throw("The download should have failed."); - } catch (ex if ex instanceof Downloads.Error && ex.becauseSourceFailed) { - // A specific error object is thrown when reading from the source fails. - } - - do_check_true(download.stopped); - do_check_false(download.canceled); - do_check_true(download.error !== null); - do_check_true(download.error.becauseSourceFailed); - do_check_false(download.error.becauseTargetFailed); - } finally { - serverSocket.close(); - } -}); - -/** - * Ensures download error details are reported on local writing failures. - */ -add_task(function test_download_error_target() -{ - let download = yield promiseSimpleDownload(); - - do_check_true(download.error === null); - - // Create a file without write access permissions before downloading. - download.target.file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0); - try { - try { - yield download.start(); - do_throw("The download should have failed."); - } catch (ex if ex instanceof Downloads.Error && ex.becauseTargetFailed) { - // A specific error object is thrown when writing to the target fails. - } - - do_check_true(download.stopped); - do_check_false(download.canceled); - do_check_true(download.error !== null); - do_check_true(download.error.becauseTargetFailed); - do_check_false(download.error.becauseSourceFailed); - } finally { - // Restore the default permissions to allow deleting the file on Windows. - if (download.target.file.exists()) { - download.target.file.permissions = FileUtils.PERMS_FILE; - download.target.file.remove(false); - } - } -}); - -/** - * Restarts a failed download. - */ -add_task(function test_download_error_restart() -{ - let download = yield promiseSimpleDownload(); - - do_check_true(download.error === null); - - // Create a file without write access permissions before downloading. - download.target.file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0); - - try { - yield download.start(); - do_throw("The download should have failed."); - } catch (ex if ex instanceof Downloads.Error && ex.becauseTargetFailed) { - // A specific error object is thrown when writing to the target fails. - } finally { - // Restore the default permissions to allow deleting the file on Windows. - if (download.target.file.exists()) { - download.target.file.permissions = FileUtils.PERMS_FILE; - - // Also for Windows, rename the file before deleting. This makes the - // current file name available immediately for a new file, while deleting - // in place prevents creation of a file with the same name for some time. - let fileToRemove = download.target.file.clone(); - fileToRemove.moveTo(null, fileToRemove.leafName + ".delete.tmp"); - fileToRemove.remove(false); - } - } - - // Restart the download and wait for completion. - yield download.start(); - - do_check_true(download.stopped); - do_check_true(download.succeeded); - do_check_false(download.canceled); - do_check_true(download.error === null); - do_check_eq(download.progress, 100); - - yield promiseVerifyContents(download.target.file, TEST_DATA_SHORT); -}); - -/** - * Executes download in both public and private modes. - */ -add_task(function test_download_public_and_private() -{ - let source_path = "/test_download_public_and_private.txt"; - let source_uri = NetUtil.newURI(HTTP_BASE + source_path); - let testCount = 0; - - // Apply pref to allow all cookies. - Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); - - function cleanup() { - Services.prefs.clearUserPref("network.cookie.cookieBehavior"); - Services.cookies.removeAll(); - gHttpServer.registerPathHandler(source_path, null); - } - do_register_cleanup(cleanup); - - gHttpServer.registerPathHandler(source_path, function (aRequest, aResponse) { - aResponse.setHeader("Content-Type", "text/plain", false); - - if (testCount == 0) { - // No cookies should exist for first public download. - do_check_false(aRequest.hasHeader("Cookie")); - aResponse.setHeader("Set-Cookie", "foobar=1", false); - testCount++; - } else if (testCount == 1) { - // The cookie should exists for second public download. - do_check_true(aRequest.hasHeader("Cookie")); - do_check_eq(aRequest.getHeader("Cookie"), "foobar=1"); - testCount++; - } else if (testCount == 2) { - // No cookies should exist for first private download. - do_check_false(aRequest.hasHeader("Cookie")); - } - }); - - let targetFile = getTempFile(TEST_TARGET_FILE_NAME); - yield Downloads.simpleDownload(source_uri, targetFile); - yield Downloads.simpleDownload(source_uri, targetFile); - let download = yield Downloads.createDownload({ - source: { uri: source_uri, isPrivate: true }, - target: { file: targetFile }, - saver: { type: "copy" }, - }); - yield download.start(); - - cleanup(); -}); - -/** - * Checks the startTime gets updated even after a restart. - */ -add_task(function test_download_cancel_immediately_restart_and_check_startTime() -{ - let download = yield promiseSimpleDownload(); - - download.start(); - let startTime = download.startTime; - do_check_true(isValidDate(download.startTime)); - - yield download.cancel(); - do_check_eq(download.startTime.getTime(), startTime.getTime()); - - // Wait for a timeout. - yield promiseTimeout(10); - - yield download.start(); - do_check_true(download.startTime.getTime() > startTime.getTime()); -}); - -/** - * Executes download with content-encoding. - */ -add_task(function test_download_with_content_encoding() -{ - let source_path = "/test_download_with_content_encoding.txt"; - let source_uri = NetUtil.newURI(HTTP_BASE + source_path); - - function cleanup() { - gHttpServer.registerPathHandler(source_path, null); - } - do_register_cleanup(cleanup); - - gHttpServer.registerPathHandler(source_path, function (aRequest, aResponse) { - aResponse.setHeader("Content-Type", "text/plain", false); - aResponse.setHeader("Content-Encoding", "gzip", false); - aResponse.setHeader("Content-Length", - "" + TEST_DATA_SHORT_GZIP_ENCODED.length, false); - - let bos = new BinaryOutputStream(aResponse.bodyOutputStream); - bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED, - TEST_DATA_SHORT_GZIP_ENCODED.length); - }); - - let download = yield Downloads.createDownload({ - source: { uri: source_uri }, - target: { file: getTempFile(TEST_TARGET_FILE_NAME) }, - saver: { type: "copy" }, - }); - yield download.start(); - - do_check_eq(download.progress, 100); - do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length); - - // Ensure the content matches the decoded test data. - yield promiseVerifyContents(download.target.file, TEST_DATA_SHORT); -}); - -/** - * Cancels and restarts a download sequentially with content-encoding. - */ -add_task(function test_download_cancel_midway_restart_with_content_encoding() -{ - let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_GZIP_URI); - - // The first time, cancel the download midway. - let deferResponse = deferNextResponse(); - try { - let deferCancel = Promise.defer(); - download.onchange = function () { - if (!download.stopped && !download.canceled && - download.currentBytes == TEST_DATA_SHORT_GZIP_ENCODED_FIRST.length) { - deferCancel.resolve(download.cancel()); - } - }; - download.start(); - yield deferCancel.promise; - } finally { - deferResponse.resolve(); - } - - do_check_true(download.stopped); - - // The second time, we'll provide the entire interruptible response. - download.onchange = null; - yield download.start() - - do_check_eq(download.progress, 100); - do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length); - - yield promiseVerifyContents(download.target.file, TEST_DATA_SHORT); -}); - -/** - * Download with parental controls enabled. - */ -add_task(function test_download_blocked_parental_controls() -{ - function cleanup() { - DownloadIntegration.shouldBlockInTest = false; - } - do_register_cleanup(cleanup); - DownloadIntegration.shouldBlockInTest = true; - - let download = yield promiseSimpleDownload(); - - try { - yield download.start(); - do_throw("The download should have blocked."); - } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) { - do_check_true(ex.becauseBlockedByParentalControls); - } - cleanup(); -}); +let gUseLegacySaver = false; +let scriptFile = do_get_file("common_test_Download.js"); +Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec); diff --git a/toolkit/components/jsdownloads/test/unit/test_DownloadLegacy.js b/toolkit/components/jsdownloads/test/unit/test_DownloadLegacy.js index 75f2ec75b86..071e834f51f 100644 --- a/toolkit/components/jsdownloads/test/unit/test_DownloadLegacy.js +++ b/toolkit/components/jsdownloads/test/unit/test_DownloadLegacy.js @@ -10,370 +10,9 @@ "use strict"; //////////////////////////////////////////////////////////////////////////////// -//// Globals +//// Execution of common tests -/** - * Starts a new download using the nsIWebBrowserPersist interface, and controls - * it using the legacy nsITransfer interface. - * - * @param aSourceURI - * The nsIURI for the download source, or null to use TEST_SOURCE_URI. - * @param isPrivate - * Optional boolean indicates whether the download originated from a - * private window. - * @param aOutPersist - * Optional object that receives a reference to the created - * nsIWebBrowserPersist instance in the "value" property. - * - * @return {Promise} - * @resolves The Download object created as a consequence of controlling the - * download through the legacy nsITransfer interface. - * @rejects Never. The current test fails in case of exceptions. - */ -function promiseStartLegacyDownload(aSourceURI, aIsPrivate, aOutPersist) { - let sourceURI = aSourceURI || TEST_SOURCE_URI; - let targetFile = getTempFile(TEST_TARGET_FILE_NAME); +let gUseLegacySaver = true; - let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] - .createInstance(Ci.nsIWebBrowserPersist); - - // We must create the nsITransfer implementation using its class ID because - // the "@mozilla.org/transfer;1" contract is currently implemented in - // "toolkit/components/downloads". When the other folder is not included in - // builds anymore (bug 851471), we'll be able to use the contract ID. - let transfer = - Components.classesByID["{1b4c85df-cbdd-4bb6-b04e-613caece083c}"] - .createInstance(Ci.nsITransfer); - - if (aOutPersist) { - aOutPersist.value = persist; - } - - let deferred = Promise.defer(); - let promise = aIsPrivate ? Downloads.getPrivateDownloadList() : - Downloads.getPublicDownloadList(); - promise.then(function (aList) { - // Temporarily register a view that will get notified when the download we - // are controlling becomes visible in the list of public downloads. - aList.addView({ - onDownloadAdded: function (aDownload) { - aList.removeView(this); - - // Remove the download to keep the list empty for the next test. This - // also allows the caller to register the "onchange" event directly. - aList.remove(aDownload); - - // When the download object is ready, make it available to the caller. - deferred.resolve(aDownload); - }, - }); - - // Initialize the components so they reference each other. This will cause - // the Download object to be created and added to the public downloads. - transfer.init(sourceURI, NetUtil.newURI(targetFile), null, null, null, null, - persist, aIsPrivate); - persist.progressListener = transfer; - - // Start the actual download process. - persist.savePrivacyAwareURI(sourceURI, null, null, null, null, targetFile, aIsPrivate); - }.bind(this)).then(null, do_report_unexpected_exception); - - return deferred.promise; -} - -//////////////////////////////////////////////////////////////////////////////// -//// Tests - -/** - * Executes a download controlled by the legacy nsITransfer interface. - */ -add_task(function test_basic() -{ - let tempDirectory = FileUtils.getDir("TmpD", []); - - let download = yield promiseStartLegacyDownload(); - - // Checks the generated DownloadSource and DownloadTarget properties. - do_check_true(download.source.uri.equals(TEST_SOURCE_URI)); - do_check_true(download.target.file.parent.equals(tempDirectory)); - - // The download is already started, wait for completion and report any errors. - if (!download.stopped) { - yield download.start(); - } - - yield promiseVerifyContents(download.target.file, TEST_DATA_SHORT); -}); - -/** - * Checks final state and progress for a successful download. - */ -add_task(function test_final_state() -{ - let download = yield promiseStartLegacyDownload(); - - // The download is already started, wait for completion and report any errors. - if (!download.stopped) { - yield download.start(); - } - - do_check_true(download.stopped); - do_check_true(download.succeeded); - do_check_false(download.canceled); - do_check_true(download.error === null); - do_check_eq(download.progress, 100); -}); - -/** - * Checks intermediate progress for a successful download. - */ -add_task(function test_intermediate_progress() -{ - let deferResponse = deferNextResponse(); - - let download = yield promiseStartLegacyDownload(TEST_INTERRUPTIBLE_URI); - - let onchange = function () { - if (download.progress == 50) { - do_check_true(download.hasProgress); - do_check_eq(download.currentBytes, TEST_DATA_SHORT.length); - do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2); - - // Continue after the first chunk of data is fully received. - deferResponse.resolve(); - } - }; - - // Register for the notification, but also call the function directly in case - // the download already reached the expected progress. - download.onchange = onchange; - onchange(); - - // The download is already started, wait for completion and report any errors. - if (!download.stopped) { - yield download.start(); - } - - do_check_true(download.stopped); - do_check_eq(download.progress, 100); - - yield promiseVerifyContents(download.target.file, - TEST_DATA_SHORT + TEST_DATA_SHORT); -}); - -/** - * Downloads a file with a "Content-Length" of 0 and checks the progress. - */ -add_task(function test_empty_progress() -{ - let download = yield promiseStartLegacyDownload(TEST_EMPTY_URI); - - // The download is already started, wait for completion and report any errors. - if (!download.stopped) { - yield download.start(); - } - - do_check_true(download.stopped); - do_check_true(download.hasProgress); - do_check_eq(download.progress, 100); - do_check_eq(download.currentBytes, 0); - do_check_eq(download.totalBytes, 0); - - do_check_eq(download.target.file.fileSize, 0); -}); - -/** - * Downloads an empty file with no "Content-Length" and checks the progress. - */ -add_task(function test_empty_noprogress() -{ - let deferResponse = deferNextResponse(); - let promiseEmptyRequestReceived = promiseNextRequestReceived(); - - let download = yield promiseStartLegacyDownload(TEST_EMPTY_NOPROGRESS_URI); - - // Wait for the request to be received by the HTTP server, but don't allow the - // request to finish yet. Before checking the download state, wait for the - // events to be processed by the client. - yield promiseEmptyRequestReceived; - yield promiseExecuteSoon(); - - // Check that this download has no progress report. - do_check_false(download.stopped); - do_check_false(download.hasProgress); - do_check_eq(download.currentBytes, 0); - do_check_eq(download.totalBytes, 0); - - // Now allow the response to finish, and wait for the download to complete, - // while reporting any errors that may occur. - deferResponse.resolve(); - if (!download.stopped) { - yield download.start(); - } - - // Verify the state of the completed download. - do_check_true(download.stopped); - do_check_false(download.hasProgress); - do_check_eq(download.progress, 100); - do_check_eq(download.currentBytes, 0); - do_check_eq(download.totalBytes, 0); - - do_check_eq(download.target.file.fileSize, 0); -}); - -/** - * Cancels a download and verifies that its state is reported correctly. - */ -add_task(function test_cancel_midway() -{ - let deferResponse = deferNextResponse(); - let outPersist = {}; - let download = yield promiseStartLegacyDownload(TEST_INTERRUPTIBLE_URI, false, - outPersist); - - try { - // Cancel the download after receiving the first part of the response. - let deferCancel = Promise.defer(); - let onchange = function () { - if (!download.stopped && !download.canceled && download.progress == 50) { - deferCancel.resolve(download.cancel()); - - // The state change happens immediately after calling "cancel", but - // temporary files or part files may still exist at this point. - do_check_true(download.canceled); - } - }; - - // Register for the notification, but also call the function directly in - // case the download already reached the expected progress. - download.onchange = onchange; - onchange(); - - // Wait on the promise returned by the "cancel" method to ensure that the - // cancellation process finished and temporary files were removed. - yield deferCancel.promise; - - // The nsIWebBrowserPersist instance should have been canceled now. - do_check_eq(outPersist.value.result, Cr.NS_ERROR_ABORT); - - do_check_true(download.stopped); - do_check_true(download.canceled); - do_check_true(download.error === null); - - do_check_false(download.target.file.exists()); - - // Progress properties are not reset by canceling. - do_check_eq(download.progress, 50); - do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2); - do_check_eq(download.currentBytes, TEST_DATA_SHORT.length); - } finally { - deferResponse.resolve(); - } -}); - -/** - * Ensures download error details are reported for legacy downloads. - */ -add_task(function test_error() -{ - let serverSocket = startFakeServer(); - try { - let download = yield promiseStartLegacyDownload(TEST_FAKE_SOURCE_URI); - - // We must check the download properties instead of calling the "start" - // method because the download has been started and may already be stopped. - let deferStopped = Promise.defer(); - let onchange = function () { - if (download.stopped) { - deferStopped.resolve(); - } - }; - download.onchange = onchange; - onchange(); - yield deferStopped.promise; - - // Check the properties now that the download stopped. - do_check_false(download.canceled); - do_check_true(download.error !== null); - do_check_true(download.error.becauseSourceFailed); - do_check_false(download.error.becauseTargetFailed); - } finally { - serverSocket.close(); - } -}); - -/** - * Executes download in both public and private modes. - */ -add_task(function test_download_public_and_private() -{ - let source_path = "/test_download_public_and_private.txt"; - let source_uri = NetUtil.newURI(HTTP_BASE + source_path); - let testCount = 0; - - // Apply pref to allow all cookies. - Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); - - function cleanup() { - Services.prefs.clearUserPref("network.cookie.cookieBehavior"); - Services.cookies.removeAll(); - gHttpServer.registerPathHandler(source_path, null); - } - - do_register_cleanup(cleanup); - - gHttpServer.registerPathHandler(source_path, function (aRequest, aResponse) { - aResponse.setHeader("Content-Type", "text/plain", false); - - if (testCount == 0) { - // No cookies should exist for first public download. - do_check_false(aRequest.hasHeader("Cookie")); - aResponse.setHeader("Set-Cookie", "foobar=1", false); - testCount++; - } else if (testCount == 1) { - // The cookie should exists for second public download. - do_check_true(aRequest.hasHeader("Cookie")); - do_check_eq(aRequest.getHeader("Cookie"), "foobar=1"); - testCount++; - } else if (testCount == 2) { - // No cookies should exist for first private download. - do_check_false(aRequest.hasHeader("Cookie")); - } - }); - - let targetFile = getTempFile(TEST_TARGET_FILE_NAME); - yield Downloads.simpleDownload(source_uri, targetFile); - yield Downloads.simpleDownload(source_uri, targetFile); - let download = yield promiseStartLegacyDownload(source_uri, true); - // The download is already started, wait for completion and report any errors. - if (!download.stopped) { - yield download.start(); - } - - cleanup(); -}); - -/** - * Download with parental controls enabled. - */ -add_task(function test_download_blocked_parental_controls() -{ - function cleanup() { - DownloadIntegration.shouldBlockInTest = false; - } - do_register_cleanup(cleanup); - DownloadIntegration.shouldBlockInTest = true; - - let download = yield promiseStartLegacyDownload(); - - try { - yield download.start(); - do_throw("The download should have blocked."); - } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) { - do_check_true(ex.becauseBlockedByParentalControls); - } - - do_check_false(download.target.file.exists()); - - cleanup(); -}); +let scriptFile = do_get_file("common_test_Download.js"); +Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec); diff --git a/toolkit/components/jsdownloads/test/unit/test_DownloadList.js b/toolkit/components/jsdownloads/test/unit/test_DownloadList.js index 4828f611330..4a50fee055e 100644 --- a/toolkit/components/jsdownloads/test/unit/test_DownloadList.js +++ b/toolkit/components/jsdownloads/test/unit/test_DownloadList.js @@ -34,14 +34,14 @@ add_task(function test_add_getAll() { let list = yield promiseNewDownloadList(); - let downloadOne = yield promiseSimpleDownload(); + let downloadOne = yield promiseNewDownload(); list.add(downloadOne); let itemsOne = yield list.getAll(); do_check_eq(itemsOne.length, 1); do_check_eq(itemsOne[0], downloadOne); - let downloadTwo = yield promiseSimpleDownload(); + let downloadTwo = yield promiseNewDownload(); list.add(downloadTwo); let itemsTwo = yield list.getAll(); @@ -60,14 +60,14 @@ add_task(function test_remove() { let list = yield promiseNewDownloadList(); - list.add(yield promiseSimpleDownload()); - list.add(yield promiseSimpleDownload()); + list.add(yield promiseNewDownload()); + list.add(yield promiseNewDownload()); let items = yield list.getAll(); list.remove(items[0]); // Removing an item that was never added should not raise an error. - list.remove(yield promiseSimpleDownload()); + list.remove(yield promiseNewDownload()); items = yield list.getAll(); do_check_eq(items.length, 1); @@ -81,8 +81,8 @@ add_task(function test_notifications_add_remove() { let list = yield promiseNewDownloadList(); - let downloadOne = yield promiseSimpleDownload(); - let downloadTwo = yield promiseSimpleDownload(); + let downloadOne = yield promiseNewDownload(); + let downloadTwo = yield promiseNewDownload(); list.add(downloadOne); list.add(downloadTwo); @@ -103,7 +103,7 @@ add_task(function test_notifications_add_remove() do_check_eq(addNotifications, 2); // Check that we receive add notifications for new elements. - list.add(yield promiseSimpleDownload()); + list.add(yield promiseNewDownload()); do_check_eq(addNotifications, 3); // Check that we receive remove notifications. @@ -125,7 +125,7 @@ add_task(function test_notifications_add_remove() // We should not receive add notifications after the view is removed. list.removeView(viewOne); - list.add(yield promiseSimpleDownload()); + list.add(yield promiseNewDownload()); do_check_eq(addNotifications, 3); }); @@ -136,8 +136,8 @@ add_task(function test_notifications_change() { let list = yield promiseNewDownloadList(); - let downloadOne = yield promiseSimpleDownload(); - let downloadTwo = yield promiseSimpleDownload(); + let downloadOne = yield promiseNewDownload(); + let downloadTwo = yield promiseNewDownload(); list.add(downloadOne); list.add(downloadTwo); @@ -174,11 +174,11 @@ add_task(function test_history_expiration() // Add expirable visit for downloads. yield promiseAddDownloadToHistory(); - yield promiseAddDownloadToHistory(TEST_INTERRUPTIBLE_URI); + yield promiseAddDownloadToHistory(httpUrl("interruptible.txt")); let list = yield promiseNewDownloadList(); - let downloadOne = yield promiseSimpleDownload(); - let downloadTwo = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI); + let downloadOne = yield promiseNewDownload(); + let downloadTwo = yield promiseNewDownload(httpUrl("interruptible.txt")); list.add(downloadOne); list.add(downloadTwo); @@ -221,8 +221,8 @@ add_task(function test_history_clear() yield promiseAddDownloadToHistory(); let list = yield promiseNewDownloadList(); - let downloadOne = yield promiseSimpleDownload(); - let downloadTwo = yield promiseSimpleDownload(); + let downloadOne = yield promiseNewDownload(); + let downloadTwo = yield promiseNewDownload(); list.add(downloadOne); list.add(downloadTwo); diff --git a/toolkit/components/jsdownloads/test/unit/test_DownloadStore.js b/toolkit/components/jsdownloads/test/unit/test_DownloadStore.js index b9fc857b212..929a9f015dd 100644 --- a/toolkit/components/jsdownloads/test/unit/test_DownloadStore.js +++ b/toolkit/components/jsdownloads/test/unit/test_DownloadStore.js @@ -49,12 +49,11 @@ add_task(function test_save_reload() let [listForLoad, storeForLoad] = yield promiseNewListAndStore( storeForSave.path); - listForSave.add(yield promiseSimpleDownload(TEST_SOURCE_URI)); + listForSave.add(yield promiseNewDownload(httpUrl("source.txt"))); listForSave.add(yield Downloads.createDownload({ - source: { uri: TEST_EMPTY_URI, - referrer: TEST_REFERRER_URI }, - target: { file: getTempFile(TEST_TARGET_FILE_NAME) }, - saver: { type: "copy" }, + source: { url: httpUrl("empty.txt"), + referrer: TEST_REFERRER_URL }, + target: getTempFile(TEST_TARGET_FILE_NAME), })); yield storeForSave.save(); @@ -71,16 +70,12 @@ add_task(function test_save_reload() do_check_neq(itemsForSave[i], itemsForLoad[i]); // The reloaded downloads have the same properties. - do_check_true(itemsForSave[i].source.uri.equals( - itemsForLoad[i].source.uri)); - if (itemsForSave[i].source.referrer) { - do_check_true(itemsForSave[i].source.referrer.equals( - itemsForLoad[i].source.referrer)); - } else { - do_check_true(itemsForLoad[i].source.referrer === null); - } - do_check_true(itemsForSave[i].target.file.equals( - itemsForLoad[i].target.file)); + do_check_eq(itemsForSave[i].source.url, + itemsForLoad[i].source.url); + do_check_eq(itemsForSave[i].source.referrer, + itemsForLoad[i].source.referrer); + do_check_eq(itemsForSave[i].target.path, + itemsForLoad[i].target.path); do_check_eq(itemsForSave[i].saver.type, itemsForLoad[i].saver.type); } @@ -129,19 +124,17 @@ add_task(function test_load_string_predefined() let [list, store] = yield promiseNewListAndStore(); // The platform-dependent file name should be generated dynamically. - let targetFile = getTempFile(TEST_TARGET_FILE_NAME); - let filePathLiteral = JSON.stringify(targetFile.path); - let sourceUriLiteral = JSON.stringify(TEST_SOURCE_URI.spec); - let emptyUriLiteral = JSON.stringify(TEST_EMPTY_URI.spec); - let referrerUriLiteral = JSON.stringify(TEST_REFERRER_URI.spec); + let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path; + let filePathLiteral = JSON.stringify(targetPath); + let sourceUriLiteral = JSON.stringify(httpUrl("source.txt")); + let emptyUriLiteral = JSON.stringify(httpUrl("empty.txt")); + let referrerUriLiteral = JSON.stringify(TEST_REFERRER_URL); - let string = "[{\"source\":{\"uri\":" + sourceUriLiteral + "}," + - "\"target\":{\"file\":" + filePathLiteral + "}," + - "\"saver\":{\"type\":\"copy\"}}," + - "{\"source\":{\"uri\":" + emptyUriLiteral + "," + + let string = "{\"list\":[{\"source\":" + sourceUriLiteral + "," + + "\"target\":" + filePathLiteral + "}," + + "{\"source\":{\"url\":" + emptyUriLiteral + "," + "\"referrer\":" + referrerUriLiteral + "}," + - "\"target\":{\"file\":" + filePathLiteral + "}," + - "\"saver\":{\"type\":\"copy\"}}]"; + "\"target\":" + filePathLiteral + "}]}"; yield OS.File.writeAtomic(store.path, new TextEncoder().encode(string), @@ -153,12 +146,12 @@ add_task(function test_load_string_predefined() do_check_eq(items.length, 2); - do_check_true(items[0].source.uri.equals(TEST_SOURCE_URI)); - do_check_true(items[0].target.file.equals(targetFile)); + do_check_eq(items[0].source.url, httpUrl("source.txt")); + do_check_eq(items[0].target.path, targetPath); - do_check_true(items[1].source.uri.equals(TEST_EMPTY_URI)); - do_check_true(items[1].source.referrer.equals(TEST_REFERRER_URI)); - do_check_true(items[1].target.file.equals(targetFile)); + do_check_eq(items[1].source.url, httpUrl("empty.txt")); + do_check_eq(items[1].source.referrer, TEST_REFERRER_URL); + do_check_eq(items[1].target.path, targetPath); }); /** @@ -169,15 +162,15 @@ add_task(function test_load_string_unrecognized() let [list, store] = yield promiseNewListAndStore(); // The platform-dependent file name should be generated dynamically. - let targetFile = getTempFile(TEST_TARGET_FILE_NAME); - let filePathLiteral = JSON.stringify(targetFile.path); - let sourceUriLiteral = JSON.stringify(TEST_SOURCE_URI.spec); + let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path; + let filePathLiteral = JSON.stringify(targetPath); + let sourceUriLiteral = JSON.stringify(httpUrl("source.txt")); - let string = "[{\"source\":null," + + let string = "{\"list\":[{\"source\":null," + "\"target\":null}," + - "{\"source\":{\"uri\":" + sourceUriLiteral + "}," + - "\"target\":{\"file\":" + filePathLiteral + "}," + - "\"saver\":{\"type\":\"copy\"}}]"; + "{\"source\":{\"url\":" + sourceUriLiteral + "}," + + "\"target\":{\"path\":" + filePathLiteral + "}," + + "\"saver\":{\"type\":\"copy\"}}]}"; yield OS.File.writeAtomic(store.path, new TextEncoder().encode(string), @@ -189,8 +182,8 @@ add_task(function test_load_string_unrecognized() do_check_eq(items.length, 1); - do_check_true(items[0].source.uri.equals(TEST_SOURCE_URI)); - do_check_true(items[0].target.file.equals(targetFile)); + do_check_eq(items[0].source.url, httpUrl("source.txt")); + do_check_eq(items[0].target.path, targetPath); }); /** @@ -200,8 +193,8 @@ add_task(function test_load_string_malformed() { let [list, store] = yield promiseNewListAndStore(); - let string = "[{\"source\":null,\"target\":null}," + - "{\"source\":{\"uri\":\"about:blank\"}}"; + let string = "{\"list\":[{\"source\":null,\"target\":null}," + + "{\"source\":{\"url\":\"about:blank\"}}}"; yield OS.File.writeAtomic(store.path, new TextEncoder().encode(string), { tmpPath: store.path + ".tmp" }); diff --git a/toolkit/components/jsdownloads/test/unit/test_Downloads.js b/toolkit/components/jsdownloads/test/unit/test_Downloads.js index faf55fb6b21..f526a91085e 100644 --- a/toolkit/components/jsdownloads/test/unit/test_Downloads.js +++ b/toolkit/components/jsdownloads/test/unit/test_Downloads.js @@ -20,21 +20,20 @@ add_task(function test_createDownload() { // Creates a simple Download object without starting the download. yield Downloads.createDownload({ - source: { uri: NetUtil.newURI("about:blank") }, - target: { file: getTempFile(TEST_TARGET_FILE_NAME) }, + source: { url: "about:blank" }, + target: { path: getTempFile(TEST_TARGET_FILE_NAME).path }, saver: { type: "copy" }, }); }); /** -* Tests createDownload for private download. + * Tests createDownload for private download. */ add_task(function test_createDownload_private() { let download = yield Downloads.createDownload({ - source: { uri: NetUtil.newURI("about:blank"), - isPrivate: true }, - target: { file: getTempFile(TEST_TARGET_FILE_NAME) }, + source: { url: "about:blank", isPrivate: true }, + target: { path: getTempFile(TEST_TARGET_FILE_NAME).path }, saver: { type: "copy" } }); do_check_true(download.source.isPrivate); @@ -45,21 +44,20 @@ add_task(function test_createDownload_private() */ add_task(function test_createDownload_public() { - let uri = NetUtil.newURI("about:blank"); - let tempFile = getTempFile(TEST_TARGET_FILE_NAME); + let tempPath = getTempFile(TEST_TARGET_FILE_NAME).path; let download = yield Downloads.createDownload({ - source: { uri: uri, isPrivate: false }, - target: { file: tempFile }, + source: { url: "about:blank", isPrivate: false }, + target: { path: tempPath }, saver: { type: "copy" } }); do_check_false(download.source.isPrivate); download = yield Downloads.createDownload({ - source: { uri: uri }, - target: { file: tempFile }, + source: { url: "about:blank" }, + target: { path: tempPath }, saver: { type: "copy" } }); - do_check_true(!download.source.isPrivate); + do_check_false(download.source.isPrivate); }); /** @@ -68,8 +66,9 @@ add_task(function test_createDownload_public() add_task(function test_simpleDownload_uri_file_arguments() { let targetFile = getTempFile(TEST_TARGET_FILE_NAME); - yield Downloads.simpleDownload(TEST_SOURCE_URI, targetFile); - yield promiseVerifyContents(targetFile, TEST_DATA_SHORT); + yield Downloads.simpleDownload(NetUtil.newURI(httpUrl("source.txt")), + targetFile); + yield promiseVerifyContents(targetFile.path, TEST_DATA_SHORT); }); /** @@ -77,10 +76,10 @@ add_task(function test_simpleDownload_uri_file_arguments() */ add_task(function test_simpleDownload_object_arguments() { - let targetFile = getTempFile(TEST_TARGET_FILE_NAME); - yield Downloads.simpleDownload({ uri: TEST_SOURCE_URI }, - { file: targetFile }); - yield promiseVerifyContents(targetFile, TEST_DATA_SHORT); + let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path; + yield Downloads.simpleDownload({ url: httpUrl("source.txt") }, + { path: targetPath }); + yield promiseVerifyContents(targetPath, TEST_DATA_SHORT); }); /** @@ -88,15 +87,15 @@ add_task(function test_simpleDownload_object_arguments() */ add_task(function test_simpleDownload_string_arguments() { - let targetFile = getTempFile(TEST_TARGET_FILE_NAME); - yield Downloads.simpleDownload(TEST_SOURCE_URI.spec, - targetFile.path); - yield promiseVerifyContents(targetFile, TEST_DATA_SHORT); + let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path; + yield Downloads.simpleDownload(httpUrl("source.txt"), + targetPath); + yield promiseVerifyContents(targetPath, TEST_DATA_SHORT); - targetFile = getTempFile(TEST_TARGET_FILE_NAME); - yield Downloads.simpleDownload(new String(TEST_SOURCE_URI.spec), - new String(targetFile.path)); - yield promiseVerifyContents(targetFile, TEST_DATA_SHORT); + targetPath = getTempFile(TEST_TARGET_FILE_NAME).path; + yield Downloads.simpleDownload(new String(httpUrl("source.txt")), + new String(targetPath)); + yield promiseVerifyContents(targetPath, TEST_DATA_SHORT); }); /**