diff options
Diffstat (limited to 'dom/serviceworkers/test/browser_download_canceled.js')
-rw-r--r-- | dom/serviceworkers/test/browser_download_canceled.js | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/dom/serviceworkers/test/browser_download_canceled.js b/dom/serviceworkers/test/browser_download_canceled.js new file mode 100644 index 0000000000..1a71a44e78 --- /dev/null +++ b/dom/serviceworkers/test/browser_download_canceled.js @@ -0,0 +1,155 @@ +/* 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.xhtml"; + return 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 + .getElementById("unknownContentType") + .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}"`); + /* eslint-disable no-shadow */ + await SpecialPowers.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(); + }); + /* eslint-enable no-shadow */ + + // 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.`); + /* eslint-disable no-shadow */ + const why = await SpecialPowers.spawn(tab.linkedBrowser, [path], function( + path + ) { + return content.wrappedJSObject.streamClosed[path].promise; + }); + /* eslint-enable no-shadow */ + 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], + ], + }); + + // 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 SpecialPowers.spawn( + tab.linkedBrowser, + [], + 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 SpecialPowers.spawn(tab.linkedBrowser, [], function() { + return content.wrappedJSObject.registration.unregister(); + }); + BrowserTestUtils.removeTab(tab); + await clearDownloads(); +}); |