Merge m-c to birch.

This commit is contained in:
Ryan VanderMeulen 2013-07-26 12:10:00 -04:00
commit 6522fca953
17 changed files with 1701 additions and 1602 deletions

View File

@ -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

View File

@ -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

View File

@ -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.
*/

View File

@ -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);
}
}

View File

@ -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();
};

View File

@ -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);

View File

@ -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(() => {

View File

@ -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() {

View File

@ -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);
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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,<html><body></body></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);

View File

@ -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);

View File

@ -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);

View File

@ -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" });

View File

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