bug 915036 - Implement DownloadSource.adjustChannel callback to support POST requests r=Paolo

MozReview-Commit-ID: 1RplqGhjtn6

--HG--
extra : rebase_source : 72c1300be889a61740cf6eca9c9e21fb979504a7
This commit is contained in:
Tomislav Jovanovic 2016-10-21 15:54:18 +02:00
parent e4fa3c53f8
commit 1975d67b88
3 changed files with 100 additions and 4 deletions

View File

@ -1105,8 +1105,9 @@ this.Download.prototype = {
};
let saver = this.saver.toSerializable();
if (!saver) {
// If we are unable to serialize the saver, we won't persist the download.
if (!serializable.source || !saver) {
// If we are unable to serialize either the source or the saver,
// we won't persist the download.
return null;
}
@ -1275,6 +1276,23 @@ this.DownloadSource.prototype = {
*/
referrer: null,
/**
* For downloads handled by the (default) DownloadCopySaver, this function
* can adjust the network channel before it is opened, for example to change
* the HTTP headers or to upload a stream as POST data.
*
* @note If this is defined this object will not be serializable, thus the
* Download object will not be persisted across sessions.
*
* @param aChannel
* The nsIChannel to be adjusted.
*
* @return {Promise}
* @resolves When the channel has been adjusted and can be opened.
* @rejects JavaScript exception that will cause the download to fail.
*/
adjustChannel: null,
/**
* Returns a static representation of the current object state.
*
@ -1282,6 +1300,11 @@ this.DownloadSource.prototype = {
*/
toSerializable: function ()
{
if (this.adjustChannel) {
// If the callback was used, we can't reproduce this across sessions.
return null;
}
// Simplify the representation if we don't have other details.
if (!this.isPrivate && !this.referrer && !this._unknownProperties) {
return this.url;
@ -1314,6 +1337,10 @@ this.DownloadSource.prototype = {
* 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.
* adjustChannel: For downloads handled by (default) DownloadCopySaver,
* this function can adjust the network channel before
* it is opened, for example to change the HTTP headers
* or to upload a stream as POST data. Optional.
* }
*
* @return The newly created DownloadSource object.
@ -1338,6 +1365,9 @@ this.DownloadSource.fromSerializable = function (aSerializable) {
if ("referrer" in aSerializable) {
source.referrer = aSerializable.referrer;
}
if ("adjustChannel" in aSerializable) {
source.adjustChannel = aSerializable.adjustChannel;
}
deserializeUnknownProperties(source, aSerializable, property =>
property != "url" && property != "isPrivate" && property != "referrer");
@ -2012,6 +2042,11 @@ this.DownloadCopySaver.prototype = {
onStatus: function () { },
};
// If the callback was set, handle it now before opening the channel.
if (download.source.adjustChannel) {
yield download.source.adjustChannel(channel);
}
// Open the channel, directing output to the background file saver.
backgroundFileSaver.QueryInterface(Ci.nsIStreamListener);
channel.asyncOpen2({
@ -2843,4 +2878,3 @@ this.DownloadPDFSaver.prototype = {
this.DownloadPDFSaver.fromSerializable = function (aSerializable) {
return new DownloadPDFSaver();
};

View File

@ -340,6 +340,58 @@ add_task(function* test_referrer()
cleanup();
});
/**
* Checks the adjustChannel callback for downloads.
*/
add_task(function* test_adjustChannel()
{
const sourcePath = "/test_post.txt";
const sourceUrl = httpUrl("test_post.txt");
const targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
const customHeader = { name: "X-Answer", value: "42" };
const postData = "Don't Panic";
function cleanup() {
gHttpServer.registerPathHandler(sourcePath, null);
}
do_register_cleanup(cleanup);
gHttpServer.registerPathHandler(sourcePath, aRequest => {
do_check_eq(aRequest.method, "POST");
do_check_true(aRequest.hasHeader(customHeader.name));
do_check_eq(aRequest.getHeader(customHeader.name), customHeader.value);
const stream = aRequest.bodyInputStream;
const body = NetUtil.readInputStreamToString(stream, stream.available());
do_check_eq(body, postData);
});
function adjustChannel(channel) {
channel.QueryInterface(Ci.nsIHttpChannel);
channel.setRequestHeader(customHeader.name, customHeader.value, false);
const stream = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
stream.setData(postData, postData.length);
channel.QueryInterface(Ci.nsIUploadChannel2);
channel.explicitSetUploadStream(stream, null, -1, "POST", false);
return Promise.resolve();
}
const download = yield Downloads.createDownload({
source: { url: sourceUrl, adjustChannel },
target: targetPath,
});
do_check_eq(download.source.adjustChannel, adjustChannel);
do_check_eq(download.toSerializable(), null);
yield download.start();
cleanup();
});
/**
* Checks initial and final state and progress for a successful download.
*/

View File

@ -66,6 +66,15 @@ add_task(function* test_save_reload()
});
listForSave.add(pdfDownload);
// If we used a callback to adjust the channel, the download should
// not be serialized because we can't recreate it across sessions.
let adjustedDownload = yield Downloads.createDownload({
source: { url: httpUrl("empty.txt"),
adjustChannel: () => Promise.resolve() },
target: getTempFile(TEST_TARGET_FILE_NAME),
});
listForSave.add(adjustedDownload);
let legacyDownload = yield promiseStartLegacyDownload();
yield legacyDownload.cancel();
listForSave.add(legacyDownload);
@ -73,7 +82,8 @@ add_task(function* test_save_reload()
yield storeForSave.save();
yield storeForLoad.load();
// Remove the PDF download because it should not appear in this list.
// Remove the PDF and adjusted downloads because they should not appear here.
listForSave.remove(adjustedDownload);
listForSave.remove(pdfDownload);
let itemsForSave = yield listForSave.getAll();