From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- browser/components/downloads/test/browser/head.js | 448 ++++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 browser/components/downloads/test/browser/head.js (limited to 'browser/components/downloads/test/browser/head.js') diff --git a/browser/components/downloads/test/browser/head.js b/browser/components/downloads/test/browser/head.js new file mode 100644 index 0000000000..49b4d8d04c --- /dev/null +++ b/browser/components/downloads/test/browser/head.js @@ -0,0 +1,448 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Provides infrastructure for automated download components tests. + */ + +// Globals + +ChromeUtils.defineESModuleGetters(this, { + Downloads: "resource://gre/modules/Downloads.sys.mjs", + DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs", + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", +}); +ChromeUtils.defineModuleGetter( + this, + "HttpServer", + "resource://testing-common/httpd.js" +); + +let gTestTargetFile = new FileUtils.File( + PathUtils.join( + Services.dirsvc.get("TmpD", Ci.nsIFile).path, + "dm-ui-test.file" + ) +); + +gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); +Services.prefs.setIntPref("security.dialog_enable_delay", 0); + +// The file may have been already deleted when removing a paused download. +// Also clear security.dialog_enable_delay pref. +registerCleanupFunction(async () => { + Services.prefs.clearUserPref("security.dialog_enable_delay"); + + if (await IOUtils.exists(gTestTargetFile.path)) { + info("removing " + gTestTargetFile.path); + if (Services.appinfo.OS === "WINNT") { + // We need to make the file writable to delete it on Windows. + await IOUtils.setPermissions(gTestTargetFile.path, 0o600); + } + await IOUtils.remove(gTestTargetFile.path); + } +}); + +const DATA_PDF = atob( + "JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G" +); + +const TEST_DATA_SHORT = "This test string is downloaded."; + +/** + * This is an internal reference that should not be used directly by tests. + */ +var _gDeferResponses = PromiseUtils.defer(); + +/** + * Ensures that all the interruptible requests started after this function is + * called won't complete until the continueResponses function is called. + * + * Normally, the internal HTTP server returns all the available data as soon as + * a request is received. In order for some requests to be served one part at a + * time, special interruptible handlers are registered on the HTTP server. This + * allows testing events or actions that need to happen in the middle of a + * download. + * + * For example, the handler accessible at the httpUri("interruptible.txt") + * address returns the TEST_DATA_SHORT text, then it may block until the + * continueResponses method is called. At this point, the handler sends the + * TEST_DATA_SHORT text again to complete the response. + * + * If an interruptible request is started before the function is called, it may + * or may not be blocked depending on the actual sequence of events. + */ +function mustInterruptResponses() { + // If there are pending blocked requests, allow them to complete. This is + // done to prevent requests from being blocked forever, but should not affect + // the test logic, since previously started requests should not be monitored + // on the client side anymore. + _gDeferResponses.resolve(); + + info("Interruptible responses will be blocked midway."); + _gDeferResponses = PromiseUtils.defer(); +} + +/** + * Allows all the current and future interruptible requests to complete. + */ +function continueResponses() { + info("Interruptible responses are now allowed to continue."); + _gDeferResponses.resolve(); +} + +/** + * Creates a download, which could be interrupted in the middle of it's progress. + */ +function promiseInterruptibleDownload(extension = ".txt") { + let interruptibleFile = FileUtils.getFile("TmpD", [ + `interruptible${extension}`, + ]); + interruptibleFile.createUnique( + Ci.nsIFile.NORMAL_FILE_TYPE, + FileUtils.PERMS_FILE + ); + + registerCleanupFunction(async () => { + if (await IOUtils.exists(interruptibleFile.path)) { + if (Services.appinfo.OS === "WINNT") { + // We need to make the file writable to delete it on Windows. + await IOUtils.setPermissions(interruptibleFile.path, 0o600); + } + await IOUtils.remove(interruptibleFile.path); + } + }); + + return Downloads.createDownload({ + source: httpUrl("interruptible.txt"), + target: { path: interruptibleFile.path }, + }); +} + +// Asynchronous support subroutines + +async function createDownloadedFile(pathname, contents) { + let file = new FileUtils.File(pathname); + if (file.exists()) { + info(`File at ${pathname} already exists`); + } + // No post-test cleanup necessary; tmp downloads directory is already removed after each test + await IOUtils.writeUTF8(pathname, contents); + ok(file.exists(), `Created ${pathname}`); + return file; +} + +async function openContextMenu(itemElement, win = window) { + let popupShownPromise = BrowserTestUtils.waitForEvent( + itemElement.ownerDocument, + "popupshown" + ); + EventUtils.synthesizeMouseAtCenter( + itemElement, + { + type: "contextmenu", + button: 2, + }, + win + ); + let { target } = await popupShownPromise; + return target; +} + +function promiseFocus() { + return new Promise(resolve => { + waitForFocus(resolve); + }); +} + +function promisePanelOpened() { + if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") { + return Promise.resolve(); + } + + return new Promise(resolve => { + // Hook to wait until the panel is shown. + let originalOnPopupShown = DownloadsPanel.onPopupShown; + DownloadsPanel.onPopupShown = function () { + DownloadsPanel.onPopupShown = originalOnPopupShown; + originalOnPopupShown.apply(this, arguments); + + // Defer to the next tick of the event loop so that we don't continue + // processing during the DOM event handler itself. + setTimeout(resolve, 0); + }; + }); +} + +async function task_resetState() { + // Remove all downloads. + let publicList = await Downloads.getList(Downloads.PUBLIC); + let downloads = await publicList.getAll(); + for (let download of downloads) { + await publicList.remove(download); + if (await IOUtils.exists(download.target.path)) { + await download.finalize(true); + info("removing " + download.target.path); + if (Services.appinfo.OS === "WINNT") { + // We need to make the file writable to delete it on Windows. + await IOUtils.setPermissions(download.target.path, 0o600); + } + await IOUtils.remove(download.target.path); + } + } + + DownloadsPanel.hidePanel(); + + await promiseFocus(); +} + +async function task_addDownloads(aItems) { + let startTimeMs = Date.now(); + + let publicList = await Downloads.getList(Downloads.PUBLIC); + for (let item of aItems) { + let source = { + url: "http://www.example.com/test-download.txt", + ...item.source, + }; + let target = + item.target instanceof Ci.nsIFile + ? item.target + : { + path: gTestTargetFile.path, + ...item.target, + }; + + let download = { + source, + target, + succeeded: item.state == DownloadsCommon.DOWNLOAD_FINISHED, + canceled: + item.state == DownloadsCommon.DOWNLOAD_CANCELED || + item.state == DownloadsCommon.DOWNLOAD_PAUSED, + deleted: item.deleted ?? false, + error: + item.state == DownloadsCommon.DOWNLOAD_FAILED + ? new Error("Failed.") + : null, + hasPartialData: item.state == DownloadsCommon.DOWNLOAD_PAUSED, + hasBlockedData: item.hasBlockedData || false, + openDownloadsListOnStart: item.openDownloadsListOnStart ?? true, + contentType: item.contentType, + startTime: new Date(startTimeMs++), + }; + // `"errorObj" in download` must be false when there's no error. + if (item.errorObj) { + download.errorObj = item.errorObj; + } + download = await Downloads.createDownload(download); + await publicList.add(download); + await download.refresh(); + } +} + +async function task_openPanel() { + await promiseFocus(); + + let promise = promisePanelOpened(); + DownloadsPanel.showPanel(); + await promise; +} + +async function setDownloadDir() { + let tmpDir = PathUtils.join( + PathUtils.tempDir, + "testsavedir" + Math.floor(Math.random() * 2 ** 32) + ); + // Create this dir if it doesn't exist (ignores existing dirs) + await IOUtils.makeDirectory(tmpDir); + registerCleanupFunction(async function () { + try { + await IOUtils.remove(tmpDir, { recursive: true }); + } catch (e) { + console.error(e); + } + }); + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setCharPref("browser.download.dir", tmpDir); + return tmpDir; +} + +let gHttpServer = null; +let gShouldServeInterruptibleFileAsDownload = false; +function startServer() { + gHttpServer = new HttpServer(); + gHttpServer.start(-1); + registerCleanupFunction(() => { + return new Promise(resolve => { + // Ensure all the pending HTTP requests have a chance to finish. + continueResponses(); + // Stop the HTTP server, calling resolve when it's done. + gHttpServer.stop(resolve); + }); + }); + + gHttpServer.identity.setPrimary( + "http", + "www.example.com", + gHttpServer.identity.primaryPort + ); + + gHttpServer.registerPathHandler("/file1.txt", (request, response) => { + response.setStatusLine(null, 200, "OK"); + response.write("file1"); + response.processAsync(); + response.finish(); + }); + gHttpServer.registerPathHandler("/file2.txt", (request, response) => { + response.setStatusLine(null, 200, "OK"); + response.write("file2"); + response.processAsync(); + response.finish(); + }); + gHttpServer.registerPathHandler("/file3.txt", (request, response) => { + response.setStatusLine(null, 200, "OK"); + response.write("file3"); + response.processAsync(); + response.finish(); + }); + + gHttpServer.registerPathHandler( + "/interruptible.txt", + function (aRequest, aResponse) { + info("Interruptible request started."); + + // Process the first part of the response. + aResponse.processAsync(); + aResponse.setHeader("Content-Type", "text/plain", false); + if (gShouldServeInterruptibleFileAsDownload) { + aResponse.setHeader("Content-Disposition", "attachment"); + } + aResponse.setHeader( + "Content-Length", + "" + TEST_DATA_SHORT.length * 2, + false + ); + aResponse.write(TEST_DATA_SHORT); + + // Wait on the current deferred object, then finish the request. + _gDeferResponses.promise + .then(function RIH_onSuccess() { + aResponse.write(TEST_DATA_SHORT); + aResponse.finish(); + info("Interruptible request finished."); + }) + .catch(console.error); + } + ); +} + +function serveInterruptibleAsDownload() { + gShouldServeInterruptibleFileAsDownload = true; + registerCleanupFunction( + () => (gShouldServeInterruptibleFileAsDownload = false) + ); +} + +function httpUrl(aFileName) { + return ( + "http://localhost:" + gHttpServer.identity.primaryPort + "/" + aFileName + ); +} + +function openLibrary(aLeftPaneRoot) { + let library = window.openDialog( + "chrome://browser/content/places/places.xhtml", + "", + "chrome,toolbar=yes,dialog=no,resizable", + aLeftPaneRoot + ); + + return new Promise(resolve => { + waitForFocus(resolve, library); + }); +} + +/** + * Waits for a download to reach its progress, in case it has not + * reached the expected progress already. + * + * @param aDownload + * The Download object to wait upon. + * + * @return {Promise} + * @resolves When the download has reached its progress. + * @rejects Never. + */ +function promiseDownloadHasProgress(aDownload, progress) { + return new Promise(resolve => { + // Wait for the download to reach its progress. + let onchange = function () { + let downloadInProgress = + !aDownload.stopped && aDownload.progress == progress; + let downloadFinished = + progress == 100 && + aDownload.progress == progress && + aDownload.succeeded; + if (downloadInProgress || downloadFinished) { + info(`Download reached ${progress}%`); + aDownload.onchange = null; + resolve(); + } + }; + + // Register for the notification, but also call the function directly in + // case the download already reached the expected progress. + aDownload.onchange = onchange; + onchange(); + }); +} + +/** + * Waits for a given button to become visible. + */ +function promiseButtonShown(id) { + let dwu = window.windowUtils; + return BrowserTestUtils.waitForCondition(() => { + let target = document.getElementById(id); + let bounds = dwu.getBoundsWithoutFlushing(target); + return bounds.width > 0 && bounds.height > 0; + }, `Waiting for button ${id} to have non-0 size`); +} + +async function simulateDropAndCheck(win, dropTarget, urls) { + let dragData = [[{ type: "text/plain", data: urls.join("\n") }]]; + let list = await Downloads.getList(Downloads.ALL); + + let added = new Set(); + let succeeded = new Set(); + await new Promise(resolve => { + let view = { + onDownloadAdded(download) { + added.add(download.source.url); + }, + onDownloadChanged(download) { + if (!added.has(download.source.url)) { + return; + } + if (!download.succeeded) { + return; + } + succeeded.add(download.source.url); + if (succeeded.size == urls.length) { + list.removeView(view).then(resolve); + } + }, + }; + list.addView(view).then(function () { + EventUtils.synthesizeDrop(dropTarget, dropTarget, dragData, "link", win); + }); + }); + + for (let url of urls) { + ok(added.has(url), url + " is added to download"); + } +} -- cgit v1.2.3