gecko-dev/dom/serviceworkers/test/browser_download_canceled.js

148 lines
5.6 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Test cancellation of a download in order to test edge-cases related to
* channel diversion. Channel diversion occurs in cases of file (and PSM cert)
* downloads where we realize in the child that we really want to consume the
* channel data in the parent. For data "sourced" by the parent, like network
* data, data streaming to the child is suspended and the parent waits for the
* child to send back the data it already received, then the channel is resumed.
* For data generated by the child, such as (the current, to be mooted by
* parent-intercept) child-side intercept, the data (currently) stream is
* continually pumped up to the parent.
*
* In particular, we want to reproduce the circumstances of Bug 1418795 where
* the child-side input-stream pump attempts to send data to the parent process
* but the parent has canceled the channel and so the IPC Actor has been torn
* down. Diversion begins once the nsURILoader receives the OnStartRequest
* notification with the headers, so there are two ways to produce
*/
ChromeUtils.import('resource://gre/modules/Services.jsm');
const { Downloads } = ChromeUtils.import("resource://gre/modules/Downloads.jsm", {});
/**
* Clear the downloads list so other tests don't see our byproducts.
*/
async function clearDownloads() {
const downloads = await Downloads.getList(Downloads.ALL);
downloads.removeFinished();
}
/**
* Returns a Promise that will be resolved once the download dialog shows up and
* we have clicked the given button.
*/
function promiseClickDownloadDialogButton(buttonAction) {
const uri = "chrome://mozapps/content/downloads/unknownContentType.xul";
BrowserTestUtils.promiseAlertDialogOpen(buttonAction, uri, async win => {
// nsHelperAppDlg.js currently uses an eval-based setTimeout(0) to invoke
// its postShowCallback that results in a misleading error to the console
// if we close the dialog before it gets a chance to run. Just a
// setTimeout is not sufficient because it appears we get our "load"
// listener before the document's, so we use TestUtils.waitForTick() to
// defer until after its load handler runs, then use setTimeout(0) to end
// up after its eval.
await TestUtils.waitForTick();
await new Promise(resolve => setTimeout(resolve, 0));
const button = win.document.documentElement.getButton(buttonAction);
button.disabled = false;
info(`clicking ${buttonAction} button`);
button.click();
});
}
async function performCanceledDownload(tab, path) {
// Start waiting for the download dialog before triggering the download.
info("watching for download popup");
const cancelDownload = promiseClickDownloadDialogButton("cancel");
// Trigger the download.
info(`triggering download of "${path}"`);
await ContentTask.spawn(
tab.linkedBrowser,
path,
function(path) {
// Put a Promise in place that we can wait on for stream closure.
content.wrappedJSObject.trackStreamClosure(path);
// Create the link and trigger the download.
const link = content.document.createElement('a');
link.href = path;
link.download = path;
content.document.body.appendChild(link);
link.click();
});
// Wait for the cancelation to have been triggered.
info("waiting for download popup");
await cancelDownload;
ok(true, "canceled download");
// Wait for confirmation that the stream stopped.
info(`wait for the ${path} stream to close.`);
const why = await ContentTask.spawn(
tab.linkedBrowser,
path,
function(path) {
return content.wrappedJSObject.streamClosed[path].promise;
});
is(why.why, "canceled", "Ensure the stream canceled instead of timing out.");
// Note that for the "sw-stream-download" case, we end up with a bogus
// reason of "'close' may only be called on a stream in the 'readable' state."
// Since we aren't actually invoking close(), I'm assuming this is an
// implementation bug that will be corrected in the web platform tests.
info(`Cancellation reason: ${why.message} after ${why.ticks} ticks`);
}
const gTestRoot = getRootDirectory(gTestPath)
.replace("chrome://mochitests/content/", "http://mochi.test:8888/");
const PAGE_URL = `${gTestRoot}download_canceled/page_download_canceled.html`;
add_task(async function interruptedDownloads() {
await SpecialPowers.pushPrefEnv({'set': [
['dom.serviceWorkers.enabled', true],
['dom.serviceWorkers.exemptFromPerDomainMax', true],
['dom.serviceWorkers.testing.enabled', true],
["javascript.options.streams", true],
["dom.streams.enabled", true],
]});
// Open the tab
const tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: PAGE_URL
});
// Wait for it to become controlled. Check that it was a promise that
// resolved as expected rather than undefined by checking the return value.
const controlled = await ContentTask.spawn(
tab.linkedBrowser,
null,
function() {
// This is a promise set up by the page during load, and we are post-load.
return content.wrappedJSObject.controlled;
});
is(controlled, "controlled", "page became controlled");
// Download a pass-through fetch stream.
await performCanceledDownload(tab, "sw-passthrough-download");
// Download a SW-generated stream
await performCanceledDownload(tab, "sw-stream-download");
// Cleanup
await ContentTask.spawn(
tab.linkedBrowser,
null,
function() {
return content.wrappedJSObject.registration.unregister();
});
BrowserTestUtils.removeTab(tab);
await clearDownloads();
});