summaryrefslogtreecommitdiffstats
path: root/browser/components/downloads/test/browser/browser_downloads_panel_opens.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/downloads/test/browser/browser_downloads_panel_opens.js')
-rw-r--r--browser/components/downloads/test/browser/browser_downloads_panel_opens.js674
1 files changed, 674 insertions, 0 deletions
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.");
+});