mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 835875 - Add the ability to cancel downloads. r=enn
This commit is contained in:
parent
19128f1970
commit
0ebc3e70a9
@ -71,7 +71,7 @@ const BackgroundFileSaverStreamListener = Components.Constructor(
|
||||
*/
|
||||
function Download()
|
||||
{
|
||||
this._deferDone = Promise.defer();
|
||||
this._deferStopped = Promise.defer();
|
||||
}
|
||||
|
||||
Download.prototype = {
|
||||
@ -93,10 +93,15 @@ Download.prototype = {
|
||||
/**
|
||||
* Becomes true when the download has been completed successfully, failed, or
|
||||
* has been canceled. This property can become true, then it can be reset to
|
||||
* false when a failed or canceled download is resumed. This property remains
|
||||
* false while the download is paused.
|
||||
* false when a failed or canceled download is restarted.
|
||||
*/
|
||||
done: false,
|
||||
stopped: false,
|
||||
|
||||
/**
|
||||
* Indicates that the download has been canceled. This property can become
|
||||
* true, then it can be reset to false when a canceled download is restarted.
|
||||
*/
|
||||
canceled: false,
|
||||
|
||||
/**
|
||||
* When the download fails, this is set to a DownloadError instance indicating
|
||||
@ -141,7 +146,7 @@ Download.prototype = {
|
||||
currentBytes: 0,
|
||||
|
||||
/**
|
||||
* This can be set to a function that is called when other properties change.
|
||||
* This can be set to a function that is called after other properties change.
|
||||
*/
|
||||
onchange: null,
|
||||
|
||||
@ -162,7 +167,7 @@ Download.prototype = {
|
||||
* This deferred object is resolved when this download finishes successfully,
|
||||
* and rejected if this download fails.
|
||||
*/
|
||||
_deferDone: null,
|
||||
_deferStopped: null,
|
||||
|
||||
/**
|
||||
* Starts the download.
|
||||
@ -173,31 +178,36 @@ Download.prototype = {
|
||||
*/
|
||||
start: function D_start()
|
||||
{
|
||||
this._deferDone.resolve(Task.spawn(function task_D_start() {
|
||||
this._deferStopped.resolve(Task.spawn(function task_D_start() {
|
||||
try {
|
||||
yield this.saver.execute();
|
||||
this.progress = 100;
|
||||
} catch (ex) {
|
||||
if (this.canceled) {
|
||||
throw new DownloadError(Cr.NS_ERROR_FAILURE, "Download canceled.");
|
||||
}
|
||||
this.error = ex;
|
||||
throw ex;
|
||||
} finally {
|
||||
this.done = true;
|
||||
this.stopped = true;
|
||||
this._notifyChange();
|
||||
}
|
||||
}.bind(this)));
|
||||
|
||||
return this.whenDone();
|
||||
return this._deferStopped.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Waits for the download to finish.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves When the download has finished successfully.
|
||||
* @rejects JavaScript exception if the download failed.
|
||||
* Cancels the download.
|
||||
*/
|
||||
whenDone: function D_whenDone() {
|
||||
return this._deferDone.promise;
|
||||
cancel: function D_cancel()
|
||||
{
|
||||
if (this.stopped || this.canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.canceled = true;
|
||||
this.saver.cancel();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -334,7 +344,15 @@ DownloadSaver.prototype = {
|
||||
execute: function DS_execute()
|
||||
{
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancels the download.
|
||||
*/
|
||||
cancel: function DS_cancel()
|
||||
{
|
||||
throw new Error("Not implemented.");
|
||||
},
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -348,6 +366,11 @@ function DownloadCopySaver() { }
|
||||
DownloadCopySaver.prototype = {
|
||||
__proto__: DownloadSaver.prototype,
|
||||
|
||||
/**
|
||||
* BackgroundFileSaver object currently handling the download.
|
||||
*/
|
||||
_backgroundFileSaver: null,
|
||||
|
||||
/**
|
||||
* Implements "DownloadSaver.execute".
|
||||
*/
|
||||
@ -424,6 +447,9 @@ DownloadCopySaver.prototype = {
|
||||
aOffset, aCount);
|
||||
},
|
||||
}, null);
|
||||
|
||||
// If the operation succeeded, store the object to allow cancellation.
|
||||
this._backgroundFileSaver = backgroundFileSaver;
|
||||
} catch (ex) {
|
||||
// In case an error occurs while setting up the chain of objects for the
|
||||
// download, ensure that we release the resources of the background saver.
|
||||
@ -432,4 +458,15 @@ DownloadCopySaver.prototype = {
|
||||
}
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Implements "DownloadSaver.cancel".
|
||||
*/
|
||||
cancel: function DCS_cancel()
|
||||
{
|
||||
if (this._backgroundFileSaver) {
|
||||
this._backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
|
||||
this._backgroundFileSaver = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -48,6 +48,10 @@ 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_INTERRUPTIBLE_PATH = "/interruptible.txt";
|
||||
const TEST_INTERRUPTIBLE_URI = NetUtil.newURI(HTTP_BASE +
|
||||
TEST_INTERRUPTIBLE_PATH);
|
||||
|
||||
const TEST_TARGET_FILE_NAME = "test-download.txt";
|
||||
const TEST_DATA_SHORT = "This test string is downloaded.";
|
||||
|
||||
@ -135,6 +139,48 @@ function startFakeServer()
|
||||
return serverSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows testing events or actions that need to happen in the
|
||||
* middle of a download.
|
||||
*
|
||||
* Calling this function registers a new request handler in the internal HTTP
|
||||
* server, accessible at the TEST_INTERRUPTIBLE_URI address. The HTTP handler
|
||||
* 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
|
||||
* not cause the socket to return an error.
|
||||
*
|
||||
* The handler is unregistered when the response finishes or is interrupted.
|
||||
*
|
||||
* @returns Deferred object used to control the response.
|
||||
*/
|
||||
function startInterruptibleResponseHandler()
|
||||
{
|
||||
let deferResponse = Promise.defer();
|
||||
gHttpServer.registerPathHandler(TEST_INTERRUPTIBLE_PATH,
|
||||
function (aRequest, aResponse)
|
||||
{
|
||||
aResponse.processAsync();
|
||||
aResponse.setHeader("Content-Type", "text/plain", false);
|
||||
aResponse.setHeader("Content-Length", "" + (TEST_DATA_SHORT.length * 2),
|
||||
false);
|
||||
aResponse.write(TEST_DATA_SHORT);
|
||||
|
||||
deferResponse.promise.then(function SIRH_onSuccess() {
|
||||
aResponse.write(TEST_DATA_SHORT);
|
||||
aResponse.finish();
|
||||
gHttpServer.registerPathHandler(TEST_INTERRUPTIBLE_PATH, null);
|
||||
}, function SIRH_onFailure() {
|
||||
aResponse.abort();
|
||||
gHttpServer.registerPathHandler(TEST_INTERRUPTIBLE_PATH, null);
|
||||
});
|
||||
});
|
||||
return deferResponse;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Initialization functions common to all tests
|
||||
|
||||
|
@ -12,9 +12,20 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
function promiseSimpleDownload() {
|
||||
/**
|
||||
* Creates a new Download object, using TEST_TARGET_FILE_NAME as the target.
|
||||
* The target is deleted by getTempFile when this function is called.
|
||||
*
|
||||
* @param aSourceURI
|
||||
* The nsIURI for the download source, or null to use TEST_SOURCE_URI.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves The newly created Download object.
|
||||
* @rejects JavaScript exception.
|
||||
*/
|
||||
function promiseSimpleDownload(aSourceURI) {
|
||||
return Downloads.createDownload({
|
||||
source: { uri: TEST_SOURCE_URI },
|
||||
source: { uri: aSourceURI || TEST_SOURCE_URI },
|
||||
target: { file: getTempFile(TEST_TARGET_FILE_NAME) },
|
||||
saver: { type: "copy" },
|
||||
});
|
||||
@ -36,6 +47,10 @@ add_task(function test_download_construction()
|
||||
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);
|
||||
|
||||
// Starts the download and waits for completion.
|
||||
yield download.start();
|
||||
|
||||
@ -49,13 +64,13 @@ add_task(function test_download_initial_final_state()
|
||||
{
|
||||
let download = yield promiseSimpleDownload();
|
||||
|
||||
do_check_false(download.done);
|
||||
do_check_false(download.stopped);
|
||||
do_check_eq(download.progress, 0);
|
||||
|
||||
// Starts the download and waits for completion.
|
||||
yield download.start();
|
||||
|
||||
do_check_true(download.done);
|
||||
do_check_true(download.stopped);
|
||||
do_check_eq(download.progress, 100);
|
||||
});
|
||||
|
||||
@ -67,11 +82,11 @@ add_task(function test_download_view_final_notified()
|
||||
let download = yield promiseSimpleDownload();
|
||||
|
||||
let onchangeNotified = false;
|
||||
let lastNotifiedDone;
|
||||
let lastNotifiedStopped;
|
||||
let lastNotifiedProgress;
|
||||
download.onchange = function () {
|
||||
onchangeNotified = true;
|
||||
lastNotifiedDone = download.done;
|
||||
lastNotifiedStopped = download.stopped;
|
||||
lastNotifiedProgress = download.progress;
|
||||
};
|
||||
|
||||
@ -80,7 +95,7 @@ add_task(function test_download_view_final_notified()
|
||||
|
||||
// The view should have been notified before the download completes.
|
||||
do_check_true(onchangeNotified);
|
||||
do_check_true(lastNotifiedDone);
|
||||
do_check_true(lastNotifiedStopped);
|
||||
do_check_eq(lastNotifiedProgress, 100);
|
||||
});
|
||||
|
||||
@ -89,30 +104,9 @@ add_task(function test_download_view_final_notified()
|
||||
*/
|
||||
add_task(function test_download_intermediate_progress()
|
||||
{
|
||||
let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
|
||||
let interruptible = startInterruptibleResponseHandler();
|
||||
|
||||
let deferUntilHalfProgress = Promise.defer();
|
||||
gHttpServer.registerPathHandler("/test_download_intermediate_progress",
|
||||
function (aRequest, aResponse)
|
||||
{
|
||||
aResponse.processAsync();
|
||||
aResponse.setHeader("Content-Type", "text/plain", false);
|
||||
aResponse.setHeader("Content-Length", "" + (TEST_DATA_SHORT.length * 2),
|
||||
false);
|
||||
aResponse.write(TEST_DATA_SHORT);
|
||||
|
||||
deferUntilHalfProgress.promise.then(function () {
|
||||
aResponse.write(TEST_DATA_SHORT);
|
||||
aResponse.finish();
|
||||
});
|
||||
});
|
||||
|
||||
let download = yield Downloads.createDownload({
|
||||
source: { uri: NetUtil.newURI(HTTP_BASE +
|
||||
"/test_download_intermediate_progress") },
|
||||
target: { file: targetFile },
|
||||
saver: { type: "copy" },
|
||||
});
|
||||
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
|
||||
|
||||
download.onchange = function () {
|
||||
if (download.progress == 50) {
|
||||
@ -121,17 +115,82 @@ add_task(function test_download_intermediate_progress()
|
||||
do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2);
|
||||
|
||||
// Continue after the first chunk of data is fully received.
|
||||
deferUntilHalfProgress.resolve();
|
||||
interruptible.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
// Starts the download and waits for completion.
|
||||
yield download.start();
|
||||
|
||||
do_check_true(download.done);
|
||||
do_check_true(download.stopped);
|
||||
do_check_eq(download.progress, 100);
|
||||
|
||||
yield promiseVerifyContents(targetFile, TEST_DATA_SHORT + TEST_DATA_SHORT);
|
||||
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()
|
||||
{
|
||||
let interruptible = startInterruptibleResponseHandler();
|
||||
try {
|
||||
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
|
||||
|
||||
// Cancel the download after receiving the first part of the response.
|
||||
download.onchange = function () {
|
||||
if (!download.stopped && download.progress == 50) {
|
||||
download.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
yield download.start();
|
||||
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());
|
||||
} finally {
|
||||
interruptible.reject();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Cancels a download right after starting it.
|
||||
*/
|
||||
add_task(function test_download_cancel_immediately()
|
||||
{
|
||||
let interruptible = startInterruptibleResponseHandler();
|
||||
try {
|
||||
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
|
||||
|
||||
let promiseStopped = download.start();
|
||||
download.cancel();
|
||||
|
||||
try {
|
||||
yield promiseStopped;
|
||||
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());
|
||||
} finally {
|
||||
interruptible.reject();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@ -139,15 +198,9 @@ add_task(function test_download_intermediate_progress()
|
||||
*/
|
||||
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" },
|
||||
});
|
||||
let download = yield promiseSimpleDownload(TEST_FAKE_SOURCE_URI);
|
||||
|
||||
do_check_true(download.error === null);
|
||||
|
||||
@ -158,7 +211,8 @@ add_task(function test_download_error_source()
|
||||
// A specific error object is thrown when reading from the source fails.
|
||||
}
|
||||
|
||||
do_check_true(download.done);
|
||||
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);
|
||||
@ -172,19 +226,13 @@ add_task(function test_download_error_source()
|
||||
*/
|
||||
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" },
|
||||
});
|
||||
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.");
|
||||
@ -192,7 +240,8 @@ add_task(function test_download_error_target()
|
||||
// A specific error object is thrown when writing to the target fails.
|
||||
}
|
||||
|
||||
do_check_true(download.done);
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user