зеркало из https://github.com/mozilla/gecko-dev.git
Bug 835872 - Handle download errors. r=enn
This commit is contained in:
Родитель
7824a51ca0
Коммит
31f9e5ac5b
|
@ -19,6 +19,9 @@
|
|||
* Represents the target of a download, for example a file in the global
|
||||
* downloads directory, or a file in the system temporary directory.
|
||||
*
|
||||
* DownloadError
|
||||
* Provides detailed information about a download failure.
|
||||
*
|
||||
* DownloadSaver
|
||||
* Template for an object that actually transfers the data for the download.
|
||||
*
|
||||
|
@ -32,6 +35,7 @@ this.EXPORTED_SYMBOLS = [
|
|||
"Download",
|
||||
"DownloadSource",
|
||||
"DownloadTarget",
|
||||
"DownloadError",
|
||||
"DownloadSaver",
|
||||
"DownloadCopySaver",
|
||||
];
|
||||
|
@ -94,6 +98,13 @@ Download.prototype = {
|
|||
*/
|
||||
done: false,
|
||||
|
||||
/**
|
||||
* When the download fails, this is set to a DownloadError instance indicating
|
||||
* the cause of the failure. If the download has been completed successfully
|
||||
* or has been canceled, this property is null.
|
||||
*/
|
||||
error: null,
|
||||
|
||||
/**
|
||||
* Indicates whether this download's "progress" property is able to report
|
||||
* partial progress while the download proceeds, and whether the value in
|
||||
|
@ -166,6 +177,9 @@ Download.prototype = {
|
|||
try {
|
||||
yield this.saver.execute();
|
||||
this.progress = 100;
|
||||
} catch (ex) {
|
||||
this.error = ex;
|
||||
throw ex;
|
||||
} finally {
|
||||
this.done = true;
|
||||
this._notifyChange();
|
||||
|
@ -238,6 +252,64 @@ DownloadTarget.prototype = {
|
|||
file: null,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// DownloadError
|
||||
|
||||
/**
|
||||
* Provides detailed information about a download failure.
|
||||
*
|
||||
* @param aResult
|
||||
* The result code associated with the error.
|
||||
* @param aMessage
|
||||
* The message to be displayed, or null to use the message associated
|
||||
* with the result code.
|
||||
* @param aInferCause
|
||||
* If true, attempts to determine if the cause of the download is a
|
||||
* network failure or a local file failure, based on a set of known
|
||||
* values of the result code. This is useful when the error is received
|
||||
* by a component that handles both aspects of the download.
|
||||
*/
|
||||
function DownloadError(aResult, aMessage, aInferCause)
|
||||
{
|
||||
const NS_ERROR_MODULE_BASE_OFFSET = 0x45;
|
||||
const NS_ERROR_MODULE_NETWORK = 6;
|
||||
const NS_ERROR_MODULE_FILES = 13;
|
||||
|
||||
// Set the error name used by the Error object prototype first.
|
||||
this.name = "DownloadError";
|
||||
this.result = aResult || Cr.NS_ERROR_FAILURE;
|
||||
if (aMessage) {
|
||||
this.message = aMessage;
|
||||
} else {
|
||||
let exception = new Components.Exception(this.result);
|
||||
this.message = exception.toString();
|
||||
}
|
||||
if (aInferCause) {
|
||||
let module = ((aResult & 0x7FFF0000) >> 16) - NS_ERROR_MODULE_BASE_OFFSET;
|
||||
this.becauseSourceFailed = (module == NS_ERROR_MODULE_NETWORK);
|
||||
this.becauseTargetFailed = (module == NS_ERROR_MODULE_FILES);
|
||||
}
|
||||
}
|
||||
|
||||
DownloadError.prototype = {
|
||||
__proto__: Error.prototype,
|
||||
|
||||
/**
|
||||
* The result code associated with this error.
|
||||
*/
|
||||
result: false,
|
||||
|
||||
/**
|
||||
* Indicates an error occurred while reading from the remote location.
|
||||
*/
|
||||
becauseSourceFailed: false,
|
||||
|
||||
/**
|
||||
* Indicates an error occurred while writing to the local target.
|
||||
*/
|
||||
becauseTargetFailed: false,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// DownloadSaver
|
||||
|
||||
|
@ -274,7 +346,7 @@ DownloadSaver.prototype = {
|
|||
function DownloadCopySaver() { }
|
||||
|
||||
DownloadCopySaver.prototype = {
|
||||
__proto__: DownloadSaver,
|
||||
__proto__: DownloadSaver.prototype,
|
||||
|
||||
/**
|
||||
* Implements "DownloadSaver.execute".
|
||||
|
@ -296,8 +368,9 @@ DownloadCopySaver.prototype = {
|
|||
if (Components.isSuccessCode(aStatus)) {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject(new Components.Exception("Download failed.",
|
||||
aStatus));
|
||||
// Infer the origin of the error from the failure code, because
|
||||
// BackgroundFileSaver does not provide more specific data.
|
||||
deferred.reject(new DownloadError(aStatus, null, true));
|
||||
}
|
||||
|
||||
// Free the reference cycle, in order to release resources earlier.
|
||||
|
|
|
@ -124,4 +124,11 @@ this.Downloads = {
|
|||
return aDownload.start();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Constructor for a DownloadError object. When you catch an exception during
|
||||
* a download, you can use this to verify if "ex instanceof Downloads.Error",
|
||||
* before reading the exception properties with the error details.
|
||||
*/
|
||||
Error: DownloadError,
|
||||
};
|
||||
|
|
|
@ -34,10 +34,20 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
const ServerSocket = Components.Constructor(
|
||||
"@mozilla.org/network/server-socket;1",
|
||||
"nsIServerSocket",
|
||||
"init");
|
||||
|
||||
const HTTP_SERVER_PORT = 4444;
|
||||
const HTTP_BASE = "http://localhost:" + HTTP_SERVER_PORT;
|
||||
|
||||
const FAKE_SERVER_PORT = 4445;
|
||||
const FAKE_BASE = "http://localhost:" + FAKE_SERVER_PORT;
|
||||
|
||||
const TEST_SOURCE_URI = NetUtil.newURI(HTTP_BASE + "/source.txt");
|
||||
const TEST_FAKE_SOURCE_URI = NetUtil.newURI(FAKE_BASE + "/source.txt");
|
||||
|
||||
const TEST_TARGET_FILE_NAME = "test-download.txt";
|
||||
const TEST_DATA_SHORT = "This test string is downloaded.";
|
||||
|
||||
|
@ -107,6 +117,24 @@ function promiseVerifyContents(aFile, aExpectedContents)
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a socket listener that closes each incoming connection.
|
||||
*
|
||||
* @returns nsIServerSocket that listens for connections. Call its "close"
|
||||
* method to stop listening and free the server port.
|
||||
*/
|
||||
function startFakeServer()
|
||||
{
|
||||
let serverSocket = new ServerSocket(FAKE_SERVER_PORT, true, -1);
|
||||
serverSocket.asyncListen({
|
||||
onSocketAccepted: function (aServ, aTransport) {
|
||||
aTransport.close(Cr.NS_BINDING_ABORTED);
|
||||
},
|
||||
onStopListening: function () { },
|
||||
});
|
||||
return serverSocket;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Initialization functions common to all tests
|
||||
|
||||
|
|
|
@ -133,3 +133,67 @@ add_task(function test_download_intermediate_progress()
|
|||
|
||||
yield promiseVerifyContents(targetFile, TEST_DATA_SHORT + TEST_DATA_SHORT);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensures download error details are reported on network failures.
|
||||
*/
|
||||
add_task(function test_download_error_source()
|
||||
{
|
||||
let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
|
||||
|
||||
let serverSocket = startFakeServer();
|
||||
try {
|
||||
let download = yield Downloads.createDownload({
|
||||
source: { uri: TEST_FAKE_SOURCE_URI },
|
||||
target: { file: targetFile },
|
||||
saver: { type: "copy" },
|
||||
});
|
||||
|
||||
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.done);
|
||||
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 targetFile = getTempFile(TEST_TARGET_FILE_NAME);
|
||||
|
||||
// Create a file without write access permissions.
|
||||
targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0);
|
||||
|
||||
let download = yield Downloads.createDownload({
|
||||
source: { uri: TEST_SOURCE_URI },
|
||||
target: { file: targetFile },
|
||||
saver: { type: "copy" },
|
||||
});
|
||||
|
||||
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.becauseTargetFailed) {
|
||||
// A specific error object is thrown when writing to the target fails.
|
||||
}
|
||||
|
||||
do_check_true(download.done);
|
||||
do_check_true(download.error !== null);
|
||||
do_check_true(download.error.becauseTargetFailed);
|
||||
do_check_false(download.error.becauseSourceFailed);
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче