From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../test/browser_download_canceled.js | 174 +++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 dom/serviceworkers/test/browser_download_canceled.js (limited to 'dom/serviceworkers/test/browser_download_canceled.js') diff --git a/dom/serviceworkers/test/browser_download_canceled.js b/dom/serviceworkers/test/browser_download_canceled.js new file mode 100644 index 0000000000..2deb8389ef --- /dev/null +++ b/dom/serviceworkers/test/browser_download_canceled.js @@ -0,0 +1,174 @@ +/* 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 + */ + +/** + * 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 callback(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) { + // If we're going to show a modal dialog for this download, then we should + // use it to cancel the download. If not, then we have to let the download + // start and then call into the downloads API ourselves to cancel it. + // We use this promise to signal the cancel being complete in either case. + let cancelledDownload; + + if ( + Services.prefs.getBoolPref( + "browser.download.always_ask_before_handling_new_types", + false + ) + ) { + // Start waiting for the download dialog before triggering the download. + cancelledDownload = promiseClickDownloadDialogButton("cancel"); + // Wait for the cancelation to have been triggered. + info("waiting for download popup"); + } else { + let downloadView; + cancelledDownload = new Promise(resolve => { + downloadView = { + onDownloadAdded(aDownload) { + aDownload.cancel(); + resolve(); + }, + }; + }); + const downloadList = await Downloads.getList(Downloads.ALL); + await downloadList.addView(downloadView); + } + + // 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 download to cancel. + await cancelledDownload; + info("cancelled 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], + ], + }); + + // 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(); +}); -- cgit v1.2.3