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 --- .../test/browser/browser_downloads_panel_opens.js | 674 +++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 browser/components/downloads/test/browser/browser_downloads_panel_opens.js (limited to 'browser/components/downloads/test/browser/browser_downloads_panel_opens.js') diff --git a/browser/components/downloads/test/browser/browser_downloads_panel_opens.js b/browser/components/downloads/test/browser/browser_downloads_panel_opens.js new file mode 100644 index 0000000000..499b5320da --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_panel_opens.js @@ -0,0 +1,674 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +let { MockFilePicker } = SpecialPowers; +MockFilePicker.init(window); +registerCleanupFunction(() => MockFilePicker.cleanup()); + +/** + * Check that the downloads panel opens when a download is spoofed. + */ +async function checkPanelOpens() { + info("Waiting for panel to open."); + let promise = promisePanelOpened(); + DownloadsCommon.getData(window)._notifyDownloadEvent("start"); + is( + DownloadsPanel.isPanelShowing, + true, + "Panel state should indicate a preparation to be opened." + ); + await promise; + + is(DownloadsPanel.panel.state, "open", "Panel should be opened."); + + DownloadsPanel.hidePanel(); +} + +/** + * Start a download and check that the downloads panel opens correctly according + * to the download parameter, openDownloadsListOnStart + * @param {boolean} [openDownloadsListOnStart] + * true (default) - open downloads panel when download starts + * false - no downloads panel; update indicator attention state + */ +async function downloadAndCheckPanel({ openDownloadsListOnStart = true } = {}) { + info("creating a download and setting it to in progress"); + await task_addDownloads([ + { + state: DownloadsCommon.DOWNLOAD_DOWNLOADING, + openDownloadsListOnStart, + }, + ]); + let publicList = await Downloads.getList(Downloads.PUBLIC); + let downloads = await publicList.getAll(); + downloads[0].stopped = false; + + // Make sure we remove that download at the end of the test. + let oldShowEventNotification = DownloadsIndicatorView.showEventNotification; + registerCleanupFunction(async () => { + for (let download of downloads) { + await publicList.remove(download); + } + DownloadsIndicatorView.showEventNotification = oldShowEventNotification; + }); + + // Instead of the panel opening, the download notification should be shown. + let promiseDownloadStartedNotification = new Promise(resolve => { + DownloadsIndicatorView.showEventNotification = aType => { + if (aType == "start") { + resolve(); + } + }; + }); + + DownloadsCommon.getData(window)._notifyDownloadEvent("start", { + openDownloadsListOnStart, + }); + is( + DownloadsPanel.isPanelShowing, + false, + "Panel state should indicate it is not preparing to be opened" + ); + + info("waiting for download to start"); + await promiseDownloadStartedNotification; + + DownloadsIndicatorView.showEventNotification = oldShowEventNotification; + is(DownloadsPanel.panel.state, "closed", "Panel should be closed"); +} + +function clickCheckbox(checkbox) { + // Clicking a checkbox toggles its checkedness first. + if (checkbox.getAttribute("checked") == "true") { + checkbox.removeAttribute("checked"); + } else { + checkbox.setAttribute("checked", "true"); + } + // Then it runs the command and closes the popup. + checkbox.doCommand(); + checkbox.parentElement.hidePopup(); +} + +/** + * Test that the downloads panel correctly opens or doesn't open based on + * whether the download triggered a dialog already. If askWhereToSave is true, + * we should get a file picker dialog. If preferredAction is alwaysAsk, we + * should get an unknown content type dialog. If neither of those is true, we + * should get no dialog at all, and expect the downloads panel to open. + * @param {boolean} [expectPanelToOpen] true - fail if panel doesn't open + * false (default) - fail if it opens + * @param {number} [preferredAction] Default download action: + * 0 (default) - save download to disk + * 1 - open UCT dialog first + * @param {boolean} [askWhereToSave] true - open file picker dialog + * false (default) - use download dir + */ +async function testDownloadsPanelAfterDialog({ + expectPanelToOpen = false, + preferredAction, + askWhereToSave = false, +} = {}) { + const { saveToDisk, alwaysAsk } = Ci.nsIHandlerInfo; + if (![saveToDisk, alwaysAsk].includes(preferredAction)) { + preferredAction = saveToDisk; + } + const openUCT = preferredAction === alwaysAsk; + const TEST_PATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ); + const MimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + const HandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService( + Ci.nsIHandlerService + ); + let publicList = await Downloads.getList(Downloads.PUBLIC); + + for (let download of await publicList.getAll()) { + await publicList.remove(download); + } + + // We need to test the changes from bug 1739348, where the helper app service + // sets a flag based on whether a file picker dialog was opened, and this flag + // determines whether the downloads panel will be opened as the download + // starts. We need to actually hit "Save" for the download to start, but we + // can't interact with the real file picker dialog. So this temporarily + // replaces it with a barebones component that plugs into the helper app + // service and tells it to start saving the file to the default path. + if (askWhereToSave) { + MockFilePicker.returnValue = MockFilePicker.returnOK; + MockFilePicker.showCallback = function (fp) { + // Get the default location from the helper app service. + let testFile = MockFilePicker.displayDirectory.clone(); + testFile.append(fp.defaultString); + info("File picker download path: " + testFile.path); + MockFilePicker.setFiles([testFile]); + MockFilePicker.filterIndex = 0; // kSaveAsType_Complete + MockFilePicker.showCallback = null; + // Confirm that saving should proceed. The helper app service uses this + // value to determine whether to invoke launcher.saveDestinationAvailable + return MockFilePicker.returnOK; + }; + } + + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.download.useDownloadDir", !askWhereToSave], + ["browser.download.always_ask_before_handling_new_types", openUCT], + ["security.dialog_enable_delay", 0], + ], + }); + + // Configure the handler for the file according to parameters. + let mimeInfo = MimeSvc.getFromTypeAndExtension("text/plain", "txt"); + let existed = HandlerSvc.exists(mimeInfo); + mimeInfo.alwaysAskBeforeHandling = openUCT; + mimeInfo.preferredAction = preferredAction; + HandlerSvc.store(mimeInfo); + registerCleanupFunction(async () => { + // Reset the handler to its original state. + if (existed) { + HandlerSvc.store(mimeInfo); + } else { + HandlerSvc.remove(mimeInfo); + } + await publicList.removeFinished(); + BrowserTestUtils.removeTab(loadingTab); + }); + + let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded(); + let downloadFinishedPromise = new Promise(resolve => { + publicList.addView({ + onDownloadChanged(download) { + info("Download changed!"); + if (download.succeeded || download.error) { + info("Download succeeded or failed."); + publicList.removeView(this); + resolve(download); + } + }, + }); + }); + let panelOpenedPromise = expectPanelToOpen ? promisePanelOpened() : null; + + // Open the tab that will trigger the download. + let loadingTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: TEST_PATH + "foo.txt", + waitForLoad: false, + waitForStateStop: true, + }); + + // Wait for a UCT dialog if the handler was set up to open one. + if (openUCT) { + let dialogWindow = await dialogWindowPromise; + is( + dialogWindow.location.href, + "chrome://mozapps/content/downloads/unknownContentType.xhtml", + "Should have seen the unknown content dialogWindow." + ); + let doc = dialogWindow.document; + let dialog = doc.getElementById("unknownContentType"); + let radio = doc.getElementById("save"); + let button = dialog.getButton("accept"); + + await TestUtils.waitForCondition( + () => !button.disabled, + "Waiting for the UCT dialog's Accept button to be enabled." + ); + ok(!radio.hidden, "The Save option should be visible"); + // Make sure we aren't opening the file. + radio.click(); + ok(radio.selected, "The Save option should be selected"); + button.disabled = false; + dialog.acceptDialog(); + } + + info("Waiting for download to finish."); + let download = await downloadFinishedPromise; + ok(!download.error, "There should be no error."); + is( + DownloadsPanel.isPanelShowing, + expectPanelToOpen, + `Panel should${expectPanelToOpen ? " " : " not "}be showing.` + ); + if (DownloadsPanel.isPanelShowing) { + await panelOpenedPromise; + let hiddenPromise = BrowserTestUtils.waitForPopupEvent( + DownloadsPanel.panel, + "hidden" + ); + DownloadsPanel.hidePanel(); + await hiddenPromise; + } + if (download?.target.exists) { + try { + info("Removing test file: " + download.target.path); + if (Services.appinfo.OS === "WINNT") { + await IOUtils.setPermissions(download.target.path, 0o600); + } + await IOUtils.remove(download.target.path); + } catch (ex) { + /* ignore */ + } + } + for (let dl of await publicList.getAll()) { + await publicList.remove(dl); + } + BrowserTestUtils.removeTab(loadingTab); +} + +/** + * Make sure the downloads panel opens automatically with a new download. + */ +add_task(async function test_downloads_panel_opens() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.download.always_ask_before_handling_new_types", false], + ["browser.download.alwaysOpenPanel", true], + ], + }); + await checkPanelOpens(); +}); + +add_task(async function test_customizemode_doesnt_wreck_things() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.download.always_ask_before_handling_new_types", false], + ["browser.download.alwaysOpenPanel", true], + ], + }); + + // Enter customize mode: + let customizationReadyPromise = BrowserTestUtils.waitForEvent( + gNavToolbox, + "customizationready" + ); + gCustomizeMode.enter(); + await customizationReadyPromise; + + info("Try to open the panel (will not work, in customize mode)"); + let promise = promisePanelOpened(); + DownloadsCommon.getData(window)._notifyDownloadEvent("start"); + await TestUtils.waitForCondition( + () => DownloadsPanel._state == DownloadsPanel.kStateHidden, + "Should try to show but stop short and hide the panel" + ); + is( + DownloadsPanel._state, + DownloadsPanel.kStateHidden, + "Should not start opening the panel." + ); + + let afterCustomizationPromise = BrowserTestUtils.waitForEvent( + gNavToolbox, + "aftercustomization" + ); + gCustomizeMode.exit(); + await afterCustomizationPromise; + + // Avoid a failure on Linux where the window isn't active for some reason, + // which prevents the window's downloads panel from opening. + if (Services.focus.activeWindow != window) { + info("Main window is not active, trying to focus."); + await SimpleTest.promiseFocus(window); + is(Services.focus.activeWindow, window, "Main window should be active."); + } + DownloadsCommon.getData(window)._notifyDownloadEvent("start"); + await TestUtils.waitForCondition( + () => DownloadsPanel.isPanelShowing, + "Panel state should indicate a preparation to be opened" + ); + await promise; + + is(DownloadsPanel.panel.state, "open", "Panel should be opened"); + + DownloadsPanel.hidePanel(); +}); + +/** + * Make sure the downloads panel _does not_ open automatically if we set the + * pref telling it not to do that. + */ +add_task(async function test_downloads_panel_opening_pref() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.download.always_ask_before_handling_new_types", false], + ["browser.download.alwaysOpenPanel", false], + ], + }); + registerCleanupFunction(async () => { + await SpecialPowers.popPrefEnv(); + }); + await downloadAndCheckPanel(); + await SpecialPowers.popPrefEnv(); +}); + +/** + * Make sure the downloads panel _does not_ open automatically if we pass the + * parameter telling it not to do that to the download constructor. + */ +add_task(async function test_downloads_openDownloadsListOnStart_param() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.download.always_ask_before_handling_new_types", false], + ["browser.download.alwaysOpenPanel", true], + ], + }); + registerCleanupFunction(async () => { + await SpecialPowers.popPrefEnv(); + }); + await downloadAndCheckPanel({ openDownloadsListOnStart: false }); + await SpecialPowers.popPrefEnv(); +}); + +/** + * Make sure the downloads panel _does not_ open automatically when an + * extension calls the browser.downloads.download API method while it is + * not handling user input, but that we do open it automatically when + * the same WebExtensions API is called while handling user input + * (See Bug 1759231) + */ +add_task(async function test_downloads_panel_on_webext_download_api() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.download.always_ask_before_handling_new_types", false], + ["browser.download.alwaysOpenPanel", true], + ], + }); + registerCleanupFunction(async () => { + await SpecialPowers.popPrefEnv(); + }); + + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["downloads"], + }, + background() { + async function startDownload(downloadOptions) { + /* globals browser */ + const downloadId = await browser.downloads.download(downloadOptions); + const downloadDone = new Promise(resolve => { + browser.downloads.onChanged.addListener(function listener(delta) { + browser.test.log(`downloads.onChanged = ${JSON.stringify(delta)}`); + if ( + delta.id == downloadId && + delta.state?.current !== "in_progress" + ) { + browser.downloads.onChanged.removeListener(listener); + resolve(); + } + }); + }); + + browser.test.sendMessage("start-download:done"); + await downloadDone; + await browser.downloads.removeFile(downloadId); + browser.test.sendMessage("removed-download-file"); + } + + browser.test.onMessage.addListener( + (msg, { withHandlingUserInput, downloadOptions }) => { + if (msg !== "start-download") { + browser.test.fail(`Got unexpected test message: ${msg}`); + return; + } + + if (withHandlingUserInput) { + browser.test.withHandlingUserInput(() => + startDownload(downloadOptions) + ); + } else { + startDownload(downloadOptions); + } + } + ); + }, + }); + + await extension.startup(); + + startServer(); + + async function testExtensionDownloadCall({ withHandlingUserInput }) { + mustInterruptResponses(); + let rnd = Math.random(); + let url = httpUrl(`interruptible.txt?q=${rnd}`); + + extension.sendMessage("start-download", { + withHandlingUserInput, + downloadOptions: { url }, + }); + await extension.awaitMessage("start-download:done"); + + let publicList = await Downloads.getList(Downloads.PUBLIC); + let downloads = await publicList.getAll(); + + let download = downloads.find(d => d.source.url === url); + is(download.source.url, url, "download has the expected url"); + is( + download.openDownloadsListOnStart, + withHandlingUserInput, + `download panel should ${withHandlingUserInput ? "open" : "stay closed"}` + ); + + continueResponses(); + await extension.awaitMessage("removed-download-file"); + } + + info( + "Test extension downloads.download API method call without handling user input" + ); + await testExtensionDownloadCall({ withHandlingUserInput: true }); + + info( + "Test extension downloads.download API method call while handling user input" + ); + await testExtensionDownloadCall({ withHandlingUserInput: false }); + + await extension.unload(); + await SpecialPowers.popPrefEnv(); +}); + +/** + * Make sure the downloads panel opens automatically with new download, only if + * no other downloads are in progress. + */ +add_task(async function test_downloads_panel_remains_closed() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", false]], + }); + await task_addDownloads([ + { state: DownloadsCommon.DOWNLOAD_DOWNLOADING }, + { state: DownloadsCommon.DOWNLOAD_DOWNLOADING }, + ]); + + let publicList = await Downloads.getList(Downloads.PUBLIC); + let downloads = await publicList.getAll(); + + info("setting 2 downloads to be in progress"); + downloads[0].stopped = false; + downloads[1].stopped = false; + + let oldShowEventNotification = DownloadsIndicatorView.showEventNotification; + + registerCleanupFunction(async () => { + // Remove all downloads created during the test. + for (let download of downloads) { + await publicList.remove(download); + } + DownloadsIndicatorView.showEventNotification = oldShowEventNotification; + }); + + let promiseDownloadStartedNotification = new Promise(resolve => { + // Instead of downloads panel opening, download notification should be shown. + DownloadsIndicatorView.showEventNotification = aType => { + if (aType == "start") { + DownloadsIndicatorView.showEventNotification = oldShowEventNotification; + resolve(); + } + }; + }); + + DownloadsCommon.getData(window)._notifyDownloadEvent("start"); + + is( + DownloadsPanel.isPanelShowing, + false, + "Panel state should NOT indicate a preparation to be opened" + ); + + await promiseDownloadStartedNotification; + + is(DownloadsPanel.panel.state, "closed", "Panel should be closed"); + + for (let download of downloads) { + await publicList.remove(download); + } + is((await publicList.getAll()).length, 0, "Should have no downloads left."); +}); + +/** + * Make sure the downloads panel doesn't open if the window isn't in the + * foreground. + */ +add_task(async function test_downloads_panel_inactive_window() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", false]], + }); + + let oldShowEventNotification = DownloadsIndicatorView.showEventNotification; + + registerCleanupFunction(async () => { + DownloadsIndicatorView.showEventNotification = oldShowEventNotification; + }); + + let promiseDownloadStartedNotification = new Promise(resolve => { + // Instead of downloads panel opening, download notification should be shown. + DownloadsIndicatorView.showEventNotification = aType => { + if (aType == "start") { + DownloadsIndicatorView.showEventNotification = oldShowEventNotification; + resolve(); + } + }; + }); + + let testRunnerWindow = Array.from(Services.wm.getEnumerator("")).find( + someWin => someWin != window + ); + + await SimpleTest.promiseFocus(testRunnerWindow); + + DownloadsCommon.getData(window)._notifyDownloadEvent("start"); + + is( + DownloadsPanel.isPanelShowing, + false, + "Panel state should NOT indicate a preparation to be opened" + ); + + await promiseDownloadStartedNotification; + await SimpleTest.promiseFocus(window); + + is(DownloadsPanel.panel.state, "closed", "Panel should be closed"); + + testRunnerWindow = null; +}); + +/** + * When right-clicking the downloads toolbar button, there should be a menuitem + * for toggling alwaysOpenPanel. Check that it works correctly. + */ +add_task(async function test_alwaysOpenPanel_menuitem() { + const alwaysOpenPanelPref = "browser.download.alwaysOpenPanel"; + let checkbox = document.getElementById( + "toolbar-context-always-open-downloads-panel" + ); + let button = document.getElementById("downloads-button"); + + Services.prefs.clearUserPref(alwaysOpenPanelPref); + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.autohideButton", false]], + }); + registerCleanupFunction(async () => { + await SpecialPowers.popPrefEnv(); + Services.prefs.clearUserPref(alwaysOpenPanelPref); + }); + + is(button.hidden, false, "Downloads button should not be hidden."); + + info("Check context menu for downloads button."); + await openContextMenu(button); + is(checkbox.hidden, false, "Always Open checkbox is visible."); + is(checkbox.getAttribute("checked"), "true", "Always Open is enabled."); + + info("Disable Always Open via context menu."); + clickCheckbox(checkbox); + is( + Services.prefs.getBoolPref(alwaysOpenPanelPref), + false, + "Always Open pref has been set to false." + ); + + await downloadAndCheckPanel(); + + await openContextMenu(button); + is(checkbox.hidden, false, "Always Open checkbox is visible."); + isnot(checkbox.getAttribute("checked"), "true", "Always Open is disabled."); + + info("Enable Always Open via context menu"); + clickCheckbox(checkbox); + is( + Services.prefs.getBoolPref(alwaysOpenPanelPref), + true, + "Pref has been set to true" + ); + + await checkPanelOpens(); +}); + +/** + * Verify that the downloads panel opens if the download did not open a file + * picker or UCT dialog + */ +add_task(async function test_downloads_panel_after_no_dialogs() { + await testDownloadsPanelAfterDialog({ expectPanelToOpen: true }); + ok(true, "Downloads panel opened because no dialogs were opened."); +}); + +/** + * Verify that the downloads panel doesn't open if the download opened an + * unknown content type dialog (e.g. action = always ask) + */ +add_task(async function test_downloads_panel_after_UCT_dialog() { + await testDownloadsPanelAfterDialog({ + expectPanelToOpen: false, + preferredAction: Ci.nsIHandlerInfo.alwaysAsk, + }); + ok(true, "Downloads panel suppressed after UCT dialog."); +}); + +/** + * Verify that the downloads panel doesn't open if the download opened a file + * picker dialog (e.g. useDownloadDir = false) + */ +add_task(async function test_downloads_panel_after_file_picker_dialog() { + await testDownloadsPanelAfterDialog({ + expectPanelToOpen: false, + preferredAction: Ci.nsIHandlerInfo.saveToDisk, + askWhereToSave: true, + }); + ok(true, "Downloads panel suppressed after file picker dialog."); +}); + +/** + * Verify that the downloads panel doesn't open if the download opened both + * dialogs (e.g. default action = always ask AND useDownloadDir = false) + */ +add_task(async function test_downloads_panel_after_both_dialogs() { + await testDownloadsPanelAfterDialog({ + expectPanelToOpen: false, + preferredAction: Ci.nsIHandlerInfo.alwaysAsk, + askWhereToSave: true, + }); + ok(true, "Downloads panel suppressed after UCT and file picker dialogs."); +}); -- cgit v1.2.3