diff options
Diffstat (limited to 'browser/components/downloads/test')
32 files changed, 3797 insertions, 0 deletions
diff --git a/browser/components/downloads/test/browser/blank.JPG b/browser/components/downloads/test/browser/blank.JPG Binary files differnew file mode 100644 index 0000000000..1cda9a53dc --- /dev/null +++ b/browser/components/downloads/test/browser/blank.JPG diff --git a/browser/components/downloads/test/browser/browser.ini b/browser/components/downloads/test/browser/browser.ini new file mode 100644 index 0000000000..c8232aef5f --- /dev/null +++ b/browser/components/downloads/test/browser/browser.ini @@ -0,0 +1,35 @@ +[DEFAULT] +support-files = head.js + +[browser_about_downloads.js] +[browser_basic_functionality.js] +[browser_download_overwrite.js] +support-files = + foo.txt + foo.txt^headers^ + !/toolkit/content/tests/browser/common/mockTransfer.js +[browser_first_download_panel.js] +skip-if = os == "linux" # Bug 949434 +[browser_image_mimetype_issues.js] +support-files = + not-really-a-jpeg.jpeg + not-really-a-jpeg.jpeg^headers^ + blank.JPG +[browser_library_select_all.js] +[browser_overflow_anchor.js] +skip-if = os == "linux" # Bug 952422 +[browser_confirm_unblock_download.js] +[browser_iframe_gone_mid_download.js] +[browser_indicatorDrop.js] +[browser_libraryDrop.js] +skip-if = (os == 'win' && os_version == '10.0' && ccov) # Bug 1306510 +[browser_library_clearall.js] +[browser_downloads_panel_block.js] +skip-if = true # Bug 1352792 +[browser_downloads_panel_context_menu.js] +[browser_downloads_panel_ctrl_click.js] +[browser_downloads_panel_height.js] +[browser_downloads_autohide.js] +[browser_go_to_download_page.js] +[browser_pdfjs_preview.js] +[browser_downloads_pauseResume.js] diff --git a/browser/components/downloads/test/browser/browser_about_downloads.js b/browser/components/downloads/test/browser/browser_about_downloads.js new file mode 100644 index 0000000000..ab6568ef24 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_about_downloads.js @@ -0,0 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Ensure about:downloads actually works. + */ +add_task(async function test_about_downloads() { + await task_resetState(); + registerCleanupFunction(task_resetState); + + await setDownloadDir(); + + await task_addDownloads([ + { state: DownloadsCommon.DOWNLOAD_FINISHED }, + { state: DownloadsCommon.DOWNLOAD_PAUSED }, + ]); + + await BrowserTestUtils.withNewTab("about:blank", async browser => { + let downloadsLoaded = BrowserTestUtils.waitForEvent( + browser, + "InitialDownloadsLoaded", + true + ); + BrowserTestUtils.loadURI(browser, "about:downloads"); + await downloadsLoaded; + await SpecialPowers.spawn(browser, [], async function() { + let box = content.document.getElementById("downloadsRichListBox"); + ok(box, "Should have list of downloads"); + is(box.children.length, 2, "Should have 2 downloads."); + for (let kid of box.children) { + let desc = kid.querySelector(".downloadTarget"); + // This would just be an `is` check, but stray temp files + // if this test (or another in this dir) ever fails could throw that off. + ok( + /^dm-ui-test(-\d+)?.file$/.test(desc.value), + `Label '${desc.value}' should match 'dm-ui-test.file'` + ); + } + ok(box.firstChild.selected, "First item should be selected."); + }); + }); +}); diff --git a/browser/components/downloads/test/browser/browser_basic_functionality.js b/browser/components/downloads/test/browser/browser_basic_functionality.js new file mode 100644 index 0000000000..7e58f95fca --- /dev/null +++ b/browser/components/downloads/test/browser/browser_basic_functionality.js @@ -0,0 +1,59 @@ +/* -*- 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/ */ + +registerCleanupFunction(async function() { + await task_resetState(); +}); + +/** + * Make sure the downloads panel can display items in the right order and + * contains the expected data. + */ +add_task(async function test_basic_functionality() { + // Display one of each download state. + const DownloadData = [ + { state: DownloadsCommon.DOWNLOAD_NOTSTARTED }, + { state: DownloadsCommon.DOWNLOAD_PAUSED }, + { state: DownloadsCommon.DOWNLOAD_FINISHED }, + { state: DownloadsCommon.DOWNLOAD_FAILED }, + { state: DownloadsCommon.DOWNLOAD_CANCELED }, + ]; + + // Wait for focus first + await promiseFocus(); + + // Ensure that state is reset in case previous tests didn't finish. + await task_resetState(); + + // For testing purposes, show all the download items at once. + var originalCountLimit = DownloadsView.kItemCountLimit; + DownloadsView.kItemCountLimit = DownloadData.length; + registerCleanupFunction(function() { + DownloadsView.kItemCountLimit = originalCountLimit; + }); + + // Populate the downloads database with the data required by this test. + await task_addDownloads(DownloadData); + + // Open the user interface and wait for data to be fully loaded. + await task_openPanel(); + + // Test item data and count. This also tests the ordering of the display. + let richlistbox = document.getElementById("downloadsListBox"); + /* disabled for failing intermittently (bug 767828) + is(richlistbox.itemChildren.length, DownloadData.length, + "There is the correct number of richlistitems"); + */ + let itemCount = richlistbox.itemChildren.length; + for (let i = 0; i < itemCount; i++) { + let element = richlistbox.itemChildren[itemCount - i - 1]; + let download = DownloadsView.itemForElement(element).download; + is( + DownloadsCommon.stateOfDownload(download), + DownloadData[i].state, + "Download states match up" + ); + } +}); diff --git a/browser/components/downloads/test/browser/browser_confirm_unblock_download.js b/browser/components/downloads/test/browser/browser_confirm_unblock_download.js new file mode 100644 index 0000000000..f47ea4d9b2 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_confirm_unblock_download.js @@ -0,0 +1,94 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests the dialog which allows the user to unblock a downloaded file. + +registerCleanupFunction(() => {}); + +async function assertDialogResult({ args, buttonToClick, expectedResult }) { + BrowserTestUtils.promiseAlertDialogOpen(buttonToClick); + is(await DownloadsCommon.confirmUnblockDownload(args), expectedResult); +} + +/** + * Tests the "unblock" dialog, for each of the possible verdicts. + */ +add_task(async function test_unblock_dialog_unblock() { + for (let verdict of [ + Downloads.Error.BLOCK_VERDICT_MALWARE, + Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED, + Downloads.Error.BLOCK_VERDICT_UNCOMMON, + ]) { + let args = { verdict, window, dialogType: "unblock" }; + + // Test both buttons. + await assertDialogResult({ + args, + buttonToClick: "accept", + expectedResult: "unblock", + }); + await assertDialogResult({ + args, + buttonToClick: "cancel", + expectedResult: "cancel", + }); + } +}); + +/** + * Tests the "chooseUnblock" dialog for potentially unwanted downloads. + */ +add_task(async function test_chooseUnblock_dialog() { + let args = { + verdict: Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED, + window, + dialogType: "chooseUnblock", + }; + + // Test each of the three buttons. + await assertDialogResult({ + args, + buttonToClick: "accept", + expectedResult: "unblock", + }); + await assertDialogResult({ + args, + buttonToClick: "cancel", + expectedResult: "cancel", + }); + await assertDialogResult({ + args, + buttonToClick: "extra1", + expectedResult: "confirmBlock", + }); +}); + +/** + * Tests the "chooseOpen" dialog for uncommon downloads. + */ +add_task(async function test_chooseOpen_dialog() { + let args = { + verdict: Downloads.Error.BLOCK_VERDICT_UNCOMMON, + window, + dialogType: "chooseOpen", + }; + + // Test each of the three buttons. + await assertDialogResult({ + args, + buttonToClick: "accept", + expectedResult: "open", + }); + await assertDialogResult({ + args, + buttonToClick: "cancel", + expectedResult: "cancel", + }); + await assertDialogResult({ + args, + buttonToClick: "extra1", + expectedResult: "confirmBlock", + }); +}); diff --git a/browser/components/downloads/test/browser/browser_download_overwrite.js b/browser/components/downloads/test/browser/browser_download_overwrite.js new file mode 100644 index 0000000000..71eb715315 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_download_overwrite.js @@ -0,0 +1,129 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_ROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" +); + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +/* import-globals-from ../../../../../toolkit/content/tests/browser/common/mockTransfer.js */ +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this +); + +add_task(async function setup() { + // head.js has helpers that write to a nice unique file we can use. + await createDownloadedFile(gTestTargetFile.path, "Hello.\n"); + ok(gTestTargetFile.exists(), "We created a test file."); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.useDownloadDir", false]], + }); + // Set up the file picker. + let destDir = gTestTargetFile.parent; + + MockFilePicker.displayDirectory = destDir; + MockFilePicker.showCallback = function(fp) { + MockFilePicker.setFiles([gTestTargetFile]); + }; + registerCleanupFunction(function() { + MockFilePicker.cleanup(); + if (gTestTargetFile.exists()) { + gTestTargetFile.remove(false); + } + }); +}); + +// If we download a file and the user accepts overwriting an existing one, +// we shouldn't first delete that file before moving the .part file into +// place. +add_task(async function test_overwrite_does_not_delete_first() { + let unregisteredTransfer = false; + let transferCompletePromise = new Promise(resolve => { + mockTransferCallback = resolve; + }); + mockTransferRegisterer.register(); + + registerCleanupFunction(function() { + if (!unregisteredTransfer) { + mockTransferRegisterer.unregister(); + } + }); + + let dialogPromise = BrowserTestUtils.domWindowOpenedAndLoaded(); + // Now try and download a thing to the file: + await BrowserTestUtils.withNewTab(TEST_ROOT + "foo.txt", async function() { + let dialog = await dialogPromise; + info("Got dialog."); + let saveEl = dialog.document.getElementById("save"); + dialog.document.getElementById("mode").selectedItem = saveEl; + // Allow accepting the dialog (to avoid the delay helper): + dialog.document + .getElementById("unknownContentType") + .getButton("accept").disabled = false; + // Then accept it: + dialog.document.querySelector("dialog").acceptDialog(); + ok(await transferCompletePromise, "download should succeed"); + ok( + gTestTargetFile.exists(), + "File should still exist and not have been deleted." + ); + // Note: the download transfer is fake so data won't have been written to + // the file, so we can't verify that the download actually overwrites data + // like this. + mockTransferRegisterer.unregister(); + unregisteredTransfer = true; + }); +}); + +// If we download a file and the user accepts overwriting an existing one, +// we should successfully overwrite its contents. +add_task(async function test_overwrite_works() { + let dialogPromise = BrowserTestUtils.domWindowOpenedAndLoaded(); + let publicDownloads = await Downloads.getList(Downloads.PUBLIC); + // Now try and download a thing to the file: + await BrowserTestUtils.withNewTab(TEST_ROOT + "foo.txt", async function() { + let dialog = await dialogPromise; + info("Got dialog."); + // First ensure we catch the download finishing. + let downloadFinishedPromise = new Promise(resolve => { + publicDownloads.addView({ + onDownloadChanged(download) { + info("Download changed!"); + if (download.succeeded || download.error) { + info("Download succeeded or errored"); + publicDownloads.removeView(this); + publicDownloads.removeFinished(); + resolve(download); + } + }, + }); + }); + let saveEl = dialog.document.getElementById("save"); + dialog.document.getElementById("mode").selectedItem = saveEl; + // Allow accepting the dialog (to avoid the delay helper): + dialog.document + .getElementById("unknownContentType") + .getButton("accept").disabled = false; + // Then accept it: + dialog.document.querySelector("dialog").acceptDialog(); + info("wait for download to finish"); + let download = await downloadFinishedPromise; + ok(download.succeeded, "Download should succeed"); + ok( + gTestTargetFile.exists(), + "File should still exist and not have been deleted." + ); + let contents = new TextDecoder().decode( + await IOUtils.read(gTestTargetFile.path) + ); + info("Got: " + contents); + ok(contents.startsWith("Dummy"), "The file was overwritten."); + }); +}); diff --git a/browser/components/downloads/test/browser/browser_downloads_autohide.js b/browser/components/downloads/test/browser/browser_downloads_autohide.js new file mode 100644 index 0000000000..f56957d7aa --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_autohide.js @@ -0,0 +1,510 @@ +/* -*- 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/ */ + +"use strict"; + +const kDownloadAutoHidePref = "browser.download.autohideButton"; + +registerCleanupFunction(async function() { + Services.prefs.clearUserPref(kDownloadAutoHidePref); + if (document.documentElement.hasAttribute("customizing")) { + await gCustomizeMode.reset(); + await promiseCustomizeEnd(); + } else { + CustomizableUI.reset(); + } +}); + +add_task(async function checkStateDuringPrefFlips() { + ok( + Services.prefs.getBoolPref(kDownloadAutoHidePref), + "Should be autohiding the button by default" + ); + ok( + !DownloadsIndicatorView.hasDownloads, + "Should be no downloads when starting the test" + ); + let downloadsButton = document.getElementById("downloads-button"); + ok( + downloadsButton.hasAttribute("hidden"), + "Button should be hidden in the toolbar" + ); + await gCustomizeMode.addToPanel(downloadsButton); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button shouldn't be hidden in the panel" + ); + ok( + !Services.prefs.getBoolPref(kDownloadAutoHidePref), + "Pref got set to false when the user moved the button" + ); + gCustomizeMode.addToToolbar(downloadsButton); + ok( + !Services.prefs.getBoolPref(kDownloadAutoHidePref), + "Pref remains false when the user moved the button" + ); + Services.prefs.setBoolPref(kDownloadAutoHidePref, true); + ok( + downloadsButton.hasAttribute("hidden"), + "Button should be hidden again in the toolbar " + + "now that we flipped the pref" + ); + Services.prefs.setBoolPref(kDownloadAutoHidePref, false); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button shouldn't be hidden with autohide turned off" + ); + await gCustomizeMode.addToPanel(downloadsButton); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button shouldn't be hidden with autohide turned off " + + "after moving it to the panel" + ); + gCustomizeMode.addToToolbar(downloadsButton); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button shouldn't be hidden with autohide turned off " + + "after moving it back to the toolbar" + ); + await gCustomizeMode.addToPanel(downloadsButton); + Services.prefs.setBoolPref(kDownloadAutoHidePref, true); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should still not be hidden with autohide turned back on " + + "because it's in the panel" + ); + // Use CUI directly instead of the customize mode APIs, + // to avoid tripping the "automatically turn off autohide" code. + CustomizableUI.addWidgetToArea("downloads-button", "nav-bar"); + ok( + downloadsButton.hasAttribute("hidden"), + "Button should be hidden again in the toolbar" + ); + gCustomizeMode.removeFromArea(downloadsButton); + Services.prefs.setBoolPref(kDownloadAutoHidePref, false); + // Can't use gCustomizeMode.addToToolbar here because it doesn't work for + // palette items if the window isn't in customize mode: + CustomizableUI.addWidgetToArea( + downloadsButton.id, + CustomizableUI.AREA_NAVBAR + ); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be unhidden again in the toolbar " + + "even if the pref was flipped while the button was in the palette" + ); + Services.prefs.setBoolPref(kDownloadAutoHidePref, true); +}); + +add_task(async function checkStateInCustomizeMode() { + ok( + Services.prefs.getBoolPref("browser.download.autohideButton"), + "Should be autohiding the button" + ); + let downloadsButton = document.getElementById("downloads-button"); + await promiseCustomizeStart(); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in customize mode." + ); + await promiseCustomizeEnd(); + ok( + downloadsButton.hasAttribute("hidden"), + "Button should be hidden if it's in the toolbar " + + "after customize mode without any moves." + ); + await promiseCustomizeStart(); + await gCustomizeMode.addToPanel(downloadsButton); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in customize mode when moved to the panel" + ); + gCustomizeMode.addToToolbar(downloadsButton); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in customize mode when moved back to the toolbar" + ); + gCustomizeMode.removeFromArea(downloadsButton); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in customize mode when in the palette" + ); + Services.prefs.setBoolPref(kDownloadAutoHidePref, false); + Services.prefs.setBoolPref(kDownloadAutoHidePref, true); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in customize mode " + + "even when flipping the autohide pref" + ); + await gCustomizeMode.addToPanel(downloadsButton); + await promiseCustomizeEnd(); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown after customize mode when moved to the panel" + ); + await promiseCustomizeStart(); + gCustomizeMode.addToToolbar(downloadsButton); + await promiseCustomizeEnd(); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in the toolbar after " + + "customize mode because we moved it." + ); + await promiseCustomizeStart(); + await gCustomizeMode.reset(); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in the toolbar in customize mode after a reset." + ); + await gCustomizeMode.undoReset(); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in the toolbar in customize mode " + + "when undoing the reset." + ); + await gCustomizeMode.addToPanel(downloadsButton); + await gCustomizeMode.reset(); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in the toolbar in customize mode " + + "after a reset moved it." + ); + await gCustomizeMode.undoReset(); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in the panel in customize mode " + + "when undoing the reset." + ); + await gCustomizeMode.reset(); + await promiseCustomizeEnd(); +}); + +add_task(async function checkStateInCustomizeModeMultipleWindows() { + ok( + Services.prefs.getBoolPref("browser.download.autohideButton"), + "Should be autohiding the button" + ); + let downloadsButton = document.getElementById("downloads-button"); + await promiseCustomizeStart(); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in customize mode." + ); + let otherWin = await BrowserTestUtils.openNewBrowserWindow(); + let otherDownloadsButton = otherWin.document.getElementById( + "downloads-button" + ); + ok( + otherDownloadsButton.hasAttribute("hidden"), + "Button should be hidden in the other window." + ); + + // Use CUI directly instead of the customize mode APIs, + // to avoid tripping the "automatically turn off autohide" code. + CustomizableUI.addWidgetToArea( + "downloads-button", + CustomizableUI.AREA_FIXED_OVERFLOW_PANEL + ); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should still be shown in customize mode." + ); + ok( + !otherDownloadsButton.hasAttribute("hidden"), + "Button should be shown in the other window too because it's in a panel." + ); + + CustomizableUI.addWidgetToArea( + "downloads-button", + CustomizableUI.AREA_NAVBAR + ); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should still be shown in customize mode." + ); + ok( + otherDownloadsButton.hasAttribute("hidden"), + "Button should be hidden again in the other window." + ); + + Services.prefs.setBoolPref(kDownloadAutoHidePref, false); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in customize mode" + ); + ok( + !otherDownloadsButton.hasAttribute("hidden"), + "Button should be shown in the other window with the pref flipped" + ); + + Services.prefs.setBoolPref(kDownloadAutoHidePref, true); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be shown in customize mode " + + "even when flipping the autohide pref" + ); + ok( + otherDownloadsButton.hasAttribute("hidden"), + "Button should be hidden in the other window with the pref flipped again" + ); + + await gCustomizeMode.addToPanel(downloadsButton); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should still be shown in customize mode." + ); + ok( + !otherDownloadsButton.hasAttribute("hidden"), + "Button should be shown in the other window too because it's in a panel." + ); + + gCustomizeMode.removeFromArea(downloadsButton); + ok( + !Services.prefs.getBoolPref(kDownloadAutoHidePref), + "Autohide pref turned off by moving the button" + ); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should still be shown in customize mode." + ); + // Don't need to assert in the other window - button is gone there. + + await gCustomizeMode.reset(); + ok( + Services.prefs.getBoolPref(kDownloadAutoHidePref), + "Autohide pref reset by reset()" + ); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should still be shown in customize mode." + ); + ok( + otherDownloadsButton.hasAttribute("hidden"), + "Button should be hidden in the other window." + ); + ok( + otherDownloadsButton.closest("#nav-bar"), + "Button should be back in the nav bar in the other window." + ); + + await promiseCustomizeEnd(); + ok( + downloadsButton.hasAttribute("hidden"), + "Button should be hidden again outside of customize mode" + ); + await BrowserTestUtils.closeWindow(otherWin); +}); + +add_task(async function checkStateForDownloads() { + ok( + Services.prefs.getBoolPref("browser.download.autohideButton"), + "Should be autohiding the button" + ); + let downloadsButton = document.getElementById("downloads-button"); + ok( + downloadsButton.hasAttribute("hidden"), + "Button should be hidden when there are no downloads." + ); + + await task_addDownloads([{ state: DownloadsCommon.DOWNLOAD_DOWNLOADING }]); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be unhidden when there are downloads." + ); + let publicList = await Downloads.getList(Downloads.PUBLIC); + let downloads = await publicList.getAll(); + for (let download of downloads) { + publicList.remove(download); + } + ok( + downloadsButton.hasAttribute("hidden"), + "Button should be hidden when the download is removed" + ); + await task_addDownloads([{ state: DownloadsCommon.DOWNLOAD_DOWNLOADING }]); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should be unhidden when there are downloads." + ); + + Services.prefs.setBoolPref(kDownloadAutoHidePref, false); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should still be unhidden." + ); + + downloads = await publicList.getAll(); + for (let download of downloads) { + publicList.remove(download); + } + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should still be unhidden because the pref was flipped." + ); + Services.prefs.setBoolPref(kDownloadAutoHidePref, true); + ok( + downloadsButton.hasAttribute("hidden"), + "Button should be hidden now that the pref flipped back " + + "because there were already no downloads." + ); + + gCustomizeMode.addToPanel(downloadsButton); + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should not be hidden in the panel." + ); + + await task_addDownloads([{ state: DownloadsCommon.DOWNLOAD_DOWNLOADING }]); + + downloads = await publicList.getAll(); + for (let download of downloads) { + publicList.remove(download); + } + + ok( + !downloadsButton.hasAttribute("hidden"), + "Button should still not be hidden in the panel " + + "when downloads count reaches 0 after being non-0." + ); + + CustomizableUI.reset(); +}); + +/** + * Check that if the button is moved to the palette, we unhide it + * in customize mode even if it was always hidden. We use a new + * window to test this. + */ +add_task(async function checkStateWhenHiddenInPalette() { + ok( + Services.prefs.getBoolPref(kDownloadAutoHidePref), + "Pref should be causing us to autohide" + ); + gCustomizeMode.removeFromArea(document.getElementById("downloads-button")); + // In a new window, the button will have been hidden + let otherWin = await BrowserTestUtils.openNewBrowserWindow(); + ok( + !otherWin.document.getElementById("downloads-button"), + "Button shouldn't be visible in the window" + ); + + let paletteButton = otherWin.gNavToolbox.palette.querySelector( + "#downloads-button" + ); + ok(paletteButton, "Button should exist in the palette"); + if (paletteButton) { + ok(paletteButton.hidden, "Button will still have the hidden attribute"); + await promiseCustomizeStart(otherWin); + ok( + !paletteButton.hidden, + "Button should no longer be hidden in customize mode" + ); + ok( + otherWin.document.getElementById("downloads-button"), + "Button should be in the document now." + ); + await promiseCustomizeEnd(otherWin); + // We purposefully don't assert anything about what happens next. + // It doesn't really matter if the button remains unhidden in + // the palette, and if we move it we'll unhide it then (the other + // tests check this). + } + await BrowserTestUtils.closeWindow(otherWin); + CustomizableUI.reset(); +}); + +add_task(async function checkContextMenu() { + let contextMenu = document.getElementById("toolbar-context-menu"); + let checkbox = document.getElementById( + "toolbar-context-autohide-downloads-button" + ); + let button = document.getElementById("downloads-button"); + + is( + Services.prefs.getBoolPref(kDownloadAutoHidePref), + true, + "Pref should be causing us to autohide" + ); + is( + DownloadsIndicatorView.hasDownloads, + false, + "Should be no downloads when starting the test" + ); + is(button.hidden, true, "Downloads button is hidden"); + + info("Simulate a download to show the downloads button."); + DownloadsIndicatorView.hasDownloads = true; + is(button.hidden, false, "Downloads button is visible"); + + info("Check context menu"); + await openContextMenu(button); + is(checkbox.hidden, false, "Auto-hide checkbox is visible"); + is(checkbox.getAttribute("checked"), "true", "Auto-hide is enabled"); + + info("Disable auto-hide via context menu"); + clickCheckbox(checkbox); + is( + Services.prefs.getBoolPref(kDownloadAutoHidePref), + false, + "Pref has been set to false" + ); + + info("Clear downloads"); + DownloadsIndicatorView.hasDownloads = false; + is(button.hidden, false, "Downloads button is still visible"); + + info("Check context menu"); + await openContextMenu(button); + is(checkbox.hidden, false, "Auto-hide checkbox is visible"); + is(checkbox.hasAttribute("checked"), false, "Auto-hide is disabled"); + + info("Enable auto-hide via context menu"); + clickCheckbox(checkbox); + is(button.hidden, true, "Downloads button is hidden"); + is( + Services.prefs.getBoolPref(kDownloadAutoHidePref), + true, + "Pref has been set to true" + ); + + info("Check context menu in another button"); + await openContextMenu(document.getElementById("home-button")); + is(checkbox.hidden, true, "Auto-hide checkbox is hidden"); + contextMenu.hidePopup(); + + info("Open popup directly"); + contextMenu.openPopup(); + is(checkbox.hidden, true, "Auto-hide checkbox is hidden"); + contextMenu.hidePopup(); +}); + +function promiseCustomizeStart(aWindow = window) { + return new Promise(resolve => { + aWindow.gNavToolbox.addEventListener("customizationready", resolve, { + once: true, + }); + aWindow.gCustomizeMode.enter(); + }); +} + +function promiseCustomizeEnd(aWindow = window) { + return new Promise(resolve => { + aWindow.gNavToolbox.addEventListener("aftercustomization", resolve, { + once: true, + }); + aWindow.gCustomizeMode.exit(); + }); +} + +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(); +} diff --git a/browser/components/downloads/test/browser/browser_downloads_panel_block.js b/browser/components/downloads/test/browser/browser_downloads_panel_block.js new file mode 100644 index 0000000000..7e0678205c --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_panel_block.js @@ -0,0 +1,181 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +"use strict"; + +add_task(async function mainTest() { + await task_resetState(); + + let verdicts = [ + Downloads.Error.BLOCK_VERDICT_UNCOMMON, + Downloads.Error.BLOCK_VERDICT_MALWARE, + Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED, + Downloads.Error.BLOCK_VERDICT_INSECURE, + ]; + await task_addDownloads(verdicts.map(v => makeDownload(v))); + + // Check that the richlistitem for each download is correct. + for (let i = 0; i < verdicts.length; i++) { + await openPanel(); + + // The current item is always the first one in the listbox since each + // iteration of this loop removes the item at the end. + let item = DownloadsView.richListBox.firstElementChild; + + // Open the panel and click the item to show the subview. + let viewPromise = promiseViewShown(DownloadsBlockedSubview.subview); + await EventUtils.sendMouseEvent({ type: "click" }, item); + await viewPromise; + + // Items are listed in newest-to-oldest order, so e.g. the first item's + // verdict is the last element in the verdicts array. + Assert.ok( + DownloadsBlockedSubview.subview.getAttribute("verdict"), + verdicts[verdicts.count - i - 1] + ); + + // Go back to the main view. + viewPromise = promiseViewShown(DownloadsBlockedSubview.mainView); + DownloadsBlockedSubview.panelMultiView.goBack(); + await viewPromise; + + // Show the subview again. + viewPromise = promiseViewShown(DownloadsBlockedSubview.subview); + await EventUtils.sendMouseEvent({ type: "click" }, item); + await viewPromise; + + // Click the Open button. The download should be unblocked and then opened, + // i.e., unblockAndOpenDownload() should be called on the item. The panel + // should also be closed as a result, so wait for that too. + let unblockOpenPromise = promiseUnblockAndOpenDownloadCalled(item); + let hidePromise = promisePanelHidden(); + EventUtils.synthesizeMouse( + DownloadsBlockedSubview.elements.unblockButton, + 10, + 10, + {}, + window + ); + await unblockOpenPromise; + await hidePromise; + + window.focus(); + await SimpleTest.promiseFocus(window); + + // Reopen the panel and show the subview again. + await openPanel(); + + viewPromise = promiseViewShown(DownloadsBlockedSubview.subview); + await EventUtils.sendMouseEvent({ type: "click" }, item); + await viewPromise; + + // Click the Remove button. The panel should close and the item should be + // removed from it. + hidePromise = promisePanelHidden(); + EventUtils.synthesizeMouse( + DownloadsBlockedSubview.elements.deleteButton, + 10, + 10, + {}, + window + ); + await hidePromise; + + await openPanel(); + Assert.ok(!item.parentNode); + + hidePromise = promisePanelHidden(); + DownloadsPanel.hidePanel(); + await hidePromise; + } + + await task_resetState(); +}); + +async function openPanel() { + // This function is insane but something intermittently causes the panel to be + // closed as soon as it's opening on Linux ASAN. Maybe it would also happen + // on other build machines if the test ran often enough. Not only is the + // panel closed, it's closed while it's opening, leaving DownloadsPanel._state + // such that when you try to open the panel again, it thinks it's already + // open, but it's not. The result is that the test times out. + // + // What this does is call DownloadsPanel.showPanel over and over again until + // the panel is really open. There are a few wrinkles: + // + // (1) When panel.state is "open", check four more times (for a total of five) + // before returning to make the panel stays open. + // (2) If the panel is not open, check the _state. It should be either + // kStateUninitialized or kStateHidden. If it's not, then the panel is in the + // process of opening -- or maybe it's stuck in that process -- so reset the + // _state to kStateHidden. + // (3) If the _state is not kStateUninitialized or kStateHidden, then it may + // actually be properly opening and not stuck at all. To avoid always closing + // the panel while it's properly opening, use an exponential backoff mechanism + // for retries. + // + // If all that fails, then the test will time out, but it would have timed out + // anyway. + + await promiseFocus(); + await new Promise(resolve => { + let verifyCount = 5; + let backoff = 0; + let iBackoff = 0; + let interval = setInterval(() => { + if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") { + if (verifyCount > 0) { + verifyCount--; + } else { + clearInterval(interval); + resolve(); + } + } else if (iBackoff < backoff) { + // Keep backing off before trying again. + iBackoff++; + } else { + // Try (or retry) opening the panel. + verifyCount = 5; + backoff = Math.max(1, 2 * backoff); + iBackoff = 0; + if (DownloadsPanel._state != DownloadsPanel.kStateUninitialized) { + DownloadsPanel._state = DownloadsPanel.kStateHidden; + } + DownloadsPanel.showPanel(); + } + }, 100); + }); +} + +function promisePanelHidden() { + return BrowserTestUtils.waitForEvent(DownloadsPanel.panel, "popuphidden"); +} + +function makeDownload(verdict) { + return { + state: DownloadsCommon.DOWNLOAD_DIRTY, + hasBlockedData: true, + errorObj: { + result: Cr.NS_ERROR_FAILURE, + message: "Download blocked.", + becauseBlocked: true, + becauseBlockedByReputationCheck: true, + reputationCheckVerdict: verdict, + }, + }; +} + +function promiseViewShown(view) { + return BrowserTestUtils.waitForEvent(view, "ViewShown"); +} + +function promiseUnblockAndOpenDownloadCalled(item) { + return new Promise(resolve => { + let realFn = item._shell.unblockAndOpenDownload; + item._shell.unblockAndOpenDownload = () => { + item._shell.unblockAndOpenDownload = realFn; + resolve(); + // unblockAndOpenDownload returns a promise (that's resolved when the file + // is opened). + return Promise.resolve(); + }; + }); +} diff --git a/browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js b/browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js new file mode 100644 index 0000000000..31841fde5e --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js @@ -0,0 +1,204 @@ +/* + Coverage for context menu state for downloads in the Downloads Panel +*/ + +let gDownloadDir; +const TestFiles = {}; + +const MENU_ITEMS = { + pause: ".downloadPauseMenuItem", + resume: ".downloadResumeMenuItem", + unblock: '[command="downloadsCmd_unblock"]', + openInSystemViewer: '[command="downloadsCmd_openInSystemViewer"]', + alwaysOpenInSystemViewer: '[command="downloadsCmd_alwaysOpenInSystemViewer"]', + show: '[command="downloadsCmd_show"]', + commandsSeparator: "menuseparator,.downloadCommandsSeparator", + openReferrer: '[command="downloadsCmd_openReferrer"]', + copyLocation: '[command="downloadsCmd_copyLocation"]', + separator: "menuseparator", + delete: '[command="cmd_delete"]', + clearList: '[command="downloadsCmd_clearList"]', + clearDownloads: '[command="downloadsCmd_clearDownloads"]', +}; + +const TestCases = [ + { + name: "Completed PDF download", + downloads: [ + { + state: DownloadsCommon.DOWNLOAD_FINISHED, + contentType: "application/pdf", + target: {}, + }, + ], + expected: { + menu: [ + MENU_ITEMS.openInSystemViewer, + MENU_ITEMS.alwaysOpenInSystemViewer, + MENU_ITEMS.show, + MENU_ITEMS.commandsSeparator, + MENU_ITEMS.openReferrer, + MENU_ITEMS.copyLocation, + MENU_ITEMS.separator, + MENU_ITEMS.delete, + MENU_ITEMS.clearList, + ], + }, + }, + { + name: "Canceled PDF download", + downloads: [ + { + state: DownloadsCommon.DOWNLOAD_CANCELED, + contentType: "application/pdf", + target: {}, + }, + ], + expected: { + menu: [ + MENU_ITEMS.openReferrer, + MENU_ITEMS.copyLocation, + MENU_ITEMS.separator, + MENU_ITEMS.delete, + MENU_ITEMS.clearList, + ], + }, + }, +]; + +add_task(async function test_setUp() { + // remove download files, empty out collections + let downloadList = await Downloads.getList(Downloads.ALL); + let downloadCount = (await downloadList.getAll()).length; + is(downloadCount, 0, "At the start of the test, there should be 0 downloads"); + + await task_resetState(); + if (!gDownloadDir) { + gDownloadDir = await setDownloadDir(); + } + info("Created download directory: " + gDownloadDir); + + // create the downloaded files we'll need + TestFiles.pdf = await createDownloadedFile( + OS.Path.join(gDownloadDir, "downloaded.pdf"), + DATA_PDF + ); + info("Created downloaded PDF file at:" + TestFiles.pdf.path); + TestFiles.txt = await createDownloadedFile( + OS.Path.join(gDownloadDir, "downloaded.txt"), + "Test file" + ); + info("Created downloaded text file at:" + TestFiles.txt.path); +}); + +// register the tests +for (let testData of TestCases) { + if (testData.skip) { + info("Skipping test:" + testData.name); + continue; + } + // use the 'name' property of each test case as the test function name + // so we get useful logs + let tmp = { + async [testData.name]() { + await testDownloadContextMenu(testData); + }, + }; + add_task(tmp[testData.name]); +} + +async function testDownloadContextMenu({ downloads = [], expected }) { + // prepare downloads + await prepareDownloads(downloads); + let downloadList = await Downloads.getList(Downloads.PUBLIC); + let [firstDownload] = await downloadList.getAll(); + info("Download succeeded? " + firstDownload.succeeded); + info("Download target exists? " + firstDownload.target.exists); + + // open panel + await task_openPanel(); + await TestUtils.waitForCondition( + () => + document.getElementById("downloadsListBox").childElementCount == + downloads.length + ); + + info("trigger the context menu"); + let itemTarget = document.querySelector( + "#downloadsListBox richlistitem .downloadMainArea" + ); + + let contextMenu = await openContextMenu(itemTarget); + + info("context menu should be open, verify its menu items"); + let result = verifyContextMenu(contextMenu, expected.menu); + + // close menus + contextMenu.hidePopup(); + let hiddenPromise = BrowserTestUtils.waitForEvent( + DownloadsPanel.panel, + "popuphidden" + ); + DownloadsPanel.hidePanel(); + await hiddenPromise; + + ok(!result, "Expected no errors verifying context menu items"); + + // clean up downloads + await downloadList.removeFinished(); +} + +// ---------------------------------------------------------------------------- +// Helpers + +function verifyContextMenu(contextMenu, itemSelectors) { + // Ignore hidden nodes + let items = Array.from(contextMenu.children).filter(n => + BrowserTestUtils.is_visible(n) + ); + let menuAsText = items + .map(n => { + return n.nodeName == "menuseparator" + ? "---" + : `${n.label} (${n.command})`; + }) + .join("\n"); + info("Got actual context menu items: \n" + menuAsText); + + try { + is( + items.length, + itemSelectors.length, + "Context menu has the expected number of items" + ); + for (let i = 0; i < items.length; i++) { + let selector = itemSelectors[i]; + ok( + items[i].matches(selector), + `Item at ${i} matches expected selector: ${selector}` + ); + } + } catch (ex) { + return ex; + } + return null; +} + +async function prepareDownloads(downloads) { + for (let props of downloads) { + info(JSON.stringify(props)); + if (props.state !== DownloadsCommon.DOWNLOAD_FINISHED) { + continue; + } + switch (props.contentType) { + case "application/pdf": + props.target = TestFiles.pdf; + break; + default: + props.target = TestFiles.txt; + break; + } + ok(props.target instanceof Ci.nsIFile, "download target is a nsIFile"); + } + await task_addDownloads(downloads); +} diff --git a/browser/components/downloads/test/browser/browser_downloads_panel_ctrl_click.js b/browser/components/downloads/test/browser/browser_downloads_panel_ctrl_click.js new file mode 100644 index 0000000000..57ef284bc1 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_panel_ctrl_click.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_downloads_panel() { + // On macOS, ctrl-click shouldn't open the panel because this normally opens + // the context menu. This happens via the `contextmenu` event which is created + // by widget code, so our simulated clicks do not do so, so we can't test + // anything on macOS. + if (AppConstants.platform == "macosx") { + ok(true, "The test is ignored on Mac"); + return; + } + + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.autohideButton", false]], + }); + await promiseButtonShown("downloads-button"); + + const button = document.getElementById("downloads-button"); + let shownPromise = promisePanelOpened(); + // Should still open the panel when Ctrl key is pressed. + EventUtils.synthesizeMouseAtCenter(button, { ctrlKey: true }); + await shownPromise; + is(DownloadsPanel.panel.state, "open", "Check that panel state is 'open'"); + + // Close download panel + DownloadsPanel.hidePanel(); + is( + DownloadsPanel.panel.state, + "closed", + "Check that panel state is 'closed'" + ); +}); diff --git a/browser/components/downloads/test/browser/browser_downloads_panel_height.js b/browser/components/downloads/test/browser/browser_downloads_panel_height.js new file mode 100644 index 0000000000..f65be18cb4 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_panel_height.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test exists because we use a <panelmultiview> element and it handles + * some of the height changes for us. We need to verify that the height is + * updated correctly if downloads are removed while the panel is hidden. + */ +add_task(async function test_height_reduced_after_removal() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.autohideButton", false]], + }); + await promiseButtonShown("downloads-button"); + await task_addDownloads([{ state: DownloadsCommon.DOWNLOAD_FINISHED }]); + + await task_openPanel(); + let panel = document.getElementById("downloadsPanel"); + let heightBeforeRemoval = panel.getBoundingClientRect().height; + + // We want to close the panel before we remove the download from the list. + DownloadsPanel.hidePanel(); + await task_resetState(); + + await task_openPanel(); + let heightAfterRemoval = panel.getBoundingClientRect().height; + Assert.greater(heightBeforeRemoval, heightAfterRemoval); + + await task_resetState(); +}); diff --git a/browser/components/downloads/test/browser/browser_downloads_pauseResume.js b/browser/components/downloads/test/browser/browser_downloads_pauseResume.js new file mode 100644 index 0000000000..21c786b1bb --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_pauseResume.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +registerCleanupFunction(async function() { + await task_resetState(); +}); + +add_task(async function test_downloads_library() { + let DownloadData = []; + for (let i = 0; i < 20; i++) { + DownloadData.push({ state: DownloadsCommon.DOWNLOAD_PAUSED }); + } + + // Ensure that state is reset in case previous tests didn't finish. + await task_resetState(); + + // Populate the downloads database with the data required by this test. + await task_addDownloads(DownloadData); + + let win = await openLibrary("Downloads"); + registerCleanupFunction(function() { + win.close(); + }); + + let listbox = win.document.getElementById("downloadsRichListBox"); + ok(listbox, "Download list box present"); + + // Select one of the downloads. + listbox.itemChildren[0].click(); + listbox.itemChildren[0]._shell._download.hasPartialData = true; + + EventUtils.synthesizeKey(" ", {}, win); + is( + listbox.itemChildren[0]._shell._downloadState, + DownloadsCommon.DOWNLOAD_DOWNLOADING, + "Download state toggled from paused to downloading" + ); + + // there is no event to wait for in some cases, we need to wait for the keypress to potentially propagate + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(r => setTimeout(r, 500)); + is( + listbox.scrollTop, + 0, + "All downloads view did not scroll when spacebar event fired on a selected download" + ); +}); diff --git a/browser/components/downloads/test/browser/browser_first_download_panel.js b/browser/components/downloads/test/browser/browser_first_download_panel.js new file mode 100644 index 0000000000..cbffcc35bd --- /dev/null +++ b/browser/components/downloads/test/browser/browser_first_download_panel.js @@ -0,0 +1,65 @@ +/* -*- 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/ */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +/** + * Make sure the downloads panel only opens automatically on the first + * download it notices. All subsequent downloads, even across sessions, should + * not open the panel automatically. + */ +add_task(async function test_first_download_panel() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.autohideButton", false]], + }); + await promiseButtonShown("downloads-button"); + // Clear the download panel has shown preference first as this test is used to + // verify this preference's behaviour. + let oldPrefValue = Services.prefs.getBoolPref("browser.download.panel.shown"); + Services.prefs.setBoolPref("browser.download.panel.shown", false); + + registerCleanupFunction(async function() { + // Clean up when the test finishes. + await task_resetState(); + + // Set the preference instead of clearing it afterwards to ensure the + // right value is used no matter what the default was. This ensures the + // panel doesn't appear and affect other tests. + Services.prefs.setBoolPref("browser.download.panel.shown", oldPrefValue); + }); + + // Ensure that state is reset in case previous tests didn't finish. + await task_resetState(); + + // With this set to false, we should automatically open the panel the first + // time a download is started. + DownloadsCommon.getData(window).panelHasShownBefore = false; + + info("waiting for panel open"); + let promise = promisePanelOpened(); + DownloadsCommon.getData(window)._notifyDownloadEvent("start"); + await promise; + + // If we got here, that means the panel opened. + DownloadsPanel.hidePanel(); + + ok( + DownloadsCommon.getData(window).panelHasShownBefore, + "Should have recorded that the panel was opened on a download." + ); + + // Next, make sure that if we start another download, we don't open the + // panel automatically. + let originalOnPopupShown = DownloadsPanel.onPopupShown; + DownloadsPanel.onPopupShown = function() { + originalOnPopupShown.apply(this, arguments); + ok(false, "Should not have opened the downloads panel."); + }; + + DownloadsCommon.getData(window)._notifyDownloadEvent("start"); + + // Wait 2 seconds to ensure that the panel does not open. + await new Promise(resolve => setTimeout(resolve, 2000)); + DownloadsPanel.onPopupShown = originalOnPopupShown; +}); diff --git a/browser/components/downloads/test/browser/browser_go_to_download_page.js b/browser/components/downloads/test/browser/browser_go_to_download_page.js new file mode 100644 index 0000000000..6a42a885a0 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_go_to_download_page.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const ReferrerInfo = Components.Constructor( + "@mozilla.org/referrer-info;1", + "nsIReferrerInfo", + "init" +); + +const TEST_REFERRER = "https://example.com/"; + +registerCleanupFunction(async function() { + await task_resetState(); + await PlacesUtils.history.clear(); +}); + +async function addDownload(referrerInfo) { + let startTimeMs = Date.now(); + + let publicList = await Downloads.getList(Downloads.PUBLIC); + let downloadData = { + source: { + url: "http://www.example.com/test-download.txt", + referrerInfo, + }, + target: { + path: gTestTargetFile.path, + }, + startTime: new Date(startTimeMs++), + }; + let download = await Downloads.createDownload(downloadData); + await publicList.add(download); + await download.start(); +} + +/** + * Make sure "Go To Download Page" is enabled and works as expected. + */ +add_task(async function test_go_to_download_page() { + let referrerInfo = new ReferrerInfo( + Ci.nsIReferrerInfo.NO_REFERRER, + true, + NetUtil.newURI(TEST_REFERRER) + ); + + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, TEST_REFERRER); + + // Wait for focus first + await promiseFocus(); + + // Ensure that state is reset in case previous tests didn't finish. + await task_resetState(); + + // Populate the downloads database with the data required by this test. + await addDownload(referrerInfo); + + // Open the user interface and wait for data to be fully loaded. + await task_openPanel(); + + let win = await openLibrary("Downloads"); + registerCleanupFunction(function() { + win.close(); + }); + + let listbox = win.document.getElementById("downloadsRichListBox"); + ok(listbox, "download list box present"); + + // Select one of the downloads. + listbox.itemChildren[0].click(); + + let contextMenu = win.document.getElementById("downloadsContextMenu"); + + let popupShownPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popupshown" + ); + EventUtils.synthesizeMouseAtCenter( + listbox.itemChildren[0], + { type: "contextmenu", button: 2 }, + win + ); + await popupShownPromise; + + // Find and click "Go To Download Page" + let goToDownloadButton = [...contextMenu.children].find( + child => child.command == "downloadsCmd_openReferrer" + ); + goToDownloadButton.click(); + + let newTab = await tabPromise; + ok(newTab, "Go To Download Page opened a new tab"); + gBrowser.removeTab(newTab); +}); diff --git a/browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js b/browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js new file mode 100644 index 0000000000..582b49d985 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js @@ -0,0 +1,76 @@ +const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite"; + +function test_deleted_iframe(perSitePref, windowOptions = {}) { + return async function() { + await SpecialPowers.pushPrefEnv({ + set: [[SAVE_PER_SITE_PREF, perSitePref]], + }); + let { DownloadLastDir } = ChromeUtils.import( + "resource://gre/modules/DownloadLastDir.jsm" + ); + + let win = await BrowserTestUtils.openNewBrowserWindow(windowOptions); + let tab = await BrowserTestUtils.openNewForegroundTab( + win.gBrowser, + "about:mozilla" + ); + + let doc = tab.linkedBrowser.contentDocument; + let iframe = doc.createElement("iframe"); + doc.body.appendChild(iframe); + + ok(iframe.contentWindow, "iframe should have a window"); + let gDownloadLastDir = new DownloadLastDir(iframe.contentWindow); + let cw = iframe.contentWindow; + let promiseIframeWindowGone = new Promise((resolve, reject) => { + Services.obs.addObserver(function obs(subject, topic) { + if (subject == cw) { + Services.obs.removeObserver(obs, topic); + resolve(); + } + }, "dom-window-destroyed"); + }); + iframe.remove(); + await promiseIframeWindowGone; + cw = null; + ok(!iframe.contentWindow, "Managed to destroy iframe"); + + let someDir = "blah"; + try { + someDir = await new Promise((resolve, reject) => { + gDownloadLastDir.getFileAsync("http://www.mozilla.org/", function(dir) { + resolve(dir); + }); + }); + } catch (ex) { + ok( + false, + "Got an exception trying to get the directory where things should be saved." + ); + Cu.reportError(ex); + } + // NB: someDir can legitimately be null here when set, hence the 'blah' workaround: + isnot( + someDir, + "blah", + "Should get a file even after the window was destroyed." + ); + + try { + gDownloadLastDir.setFile("http://www.mozilla.org/", null); + } catch (ex) { + ok( + false, + "Got an exception trying to set the directory where things should be saved." + ); + Cu.reportError(ex); + } + + await BrowserTestUtils.closeWindow(win); + }; +} + +add_task(test_deleted_iframe(false)); +add_task(test_deleted_iframe(false)); +add_task(test_deleted_iframe(true, { private: true })); +add_task(test_deleted_iframe(true, { private: true })); diff --git a/browser/components/downloads/test/browser/browser_image_mimetype_issues.js b/browser/components/downloads/test/browser/browser_image_mimetype_issues.js new file mode 100644 index 0000000000..c0bb0b8d9e --- /dev/null +++ b/browser/components/downloads/test/browser/browser_image_mimetype_issues.js @@ -0,0 +1,133 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_ROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" +); + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +/* + * Popular websites implement image optimization as serving files with + * extension ".jpg" but content type "image/webp". If we save such images, + * we should actually save them with a .webp extension as that is what + * they are. + */ + +/** + * Test the above with the "save image as" context menu. + */ +add_task(async function test_save_image_webp_with_jpeg_extension() { + await BrowserTestUtils.withNewTab( + `data:text/html,<img src="${TEST_ROOT}/not-really-a-jpeg.jpeg?convert=webp">`, + async browser => { + let menu = document.getElementById("contentAreaContextMenu"); + let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown"); + BrowserTestUtils.synthesizeMouse( + "img", + 5, + 5, + { type: "contextmenu", button: 2 }, + browser + ); + await popupShown; + + await new Promise(resolve => { + MockFilePicker.showCallback = function(fp) { + ok( + fp.defaultString.endsWith("webp"), + `filepicker for image has "${fp.defaultString}", should end in webp` + ); + setTimeout(resolve, 0); + return Ci.nsIFilePicker.returnCancel; + }; + EventUtils.synthesizeMouseAtCenter( + menu.querySelector("#context-saveimage"), + {} + ); + }); + } + ); +}); + +/** + * Test with the "save link as" context menu. + */ +add_task(async function test_save_link_webp_with_jpeg_extension() { + await BrowserTestUtils.withNewTab( + `data:text/html,<a href="${TEST_ROOT}/not-really-a-jpeg.jpeg?convert=webp">Nice image</a>`, + async browser => { + let menu = document.getElementById("contentAreaContextMenu"); + let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown"); + BrowserTestUtils.synthesizeMouse( + "a[href]", + 5, + 5, + { type: "contextmenu", button: 2 }, + browser + ); + await popupShown; + + await new Promise(resolve => { + MockFilePicker.showCallback = function(fp) { + ok( + fp.defaultString.endsWith("webp"), + `filepicker for link has "${fp.defaultString}", should end in webp` + ); + setTimeout(resolve, 0); + return Ci.nsIFilePicker.returnCancel; + }; + EventUtils.synthesizeMouseAtCenter( + menu.querySelector("#context-savelink"), + {} + ); + }); + } + ); +}); + +/** + * Test with the main "save page" command. + */ +add_task(async function test_save_page_on_image_document() { + await BrowserTestUtils.withNewTab( + `${TEST_ROOT}/not-really-a-jpeg.jpeg?convert=webp`, + async browser => { + await new Promise(resolve => { + MockFilePicker.showCallback = function(fp) { + ok( + fp.defaultString.endsWith("webp"), + `filepicker for "save page" has "${fp.defaultString}", should end in webp` + ); + setTimeout(resolve, 0); + return Ci.nsIFilePicker.returnCancel; + }; + document.getElementById("Browser:SavePage").doCommand(); + }); + } + ); +}); + +/** + * Make sure that a valid JPEG image using the .JPG extension doesn't + * get it replaced with .jpeg. + */ +add_task(async function test_save_page_on_JPEG_image_document() { + await BrowserTestUtils.withNewTab(`${TEST_ROOT}/blank.JPG`, async browser => { + await new Promise(resolve => { + MockFilePicker.showCallback = function(fp) { + ok( + fp.defaultString.endsWith("JPG"), + `filepicker for "save page" has "${fp.defaultString}", should end in JPG` + ); + setTimeout(resolve, 0); + return Ci.nsIFilePicker.returnCancel; + }; + document.getElementById("Browser:SavePage").doCommand(); + }); + }); +}); diff --git a/browser/components/downloads/test/browser/browser_indicatorDrop.js b/browser/components/downloads/test/browser/browser_indicatorDrop.js new file mode 100644 index 0000000000..1cbdd89761 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_indicatorDrop.js @@ -0,0 +1,38 @@ +/* -*- 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/ */ + +registerCleanupFunction(async function() { + await task_resetState(); + await PlacesUtils.history.clear(); +}); + +add_task(async function test_indicatorDrop() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.autohideButton", false]], + }); + let downloadButton = document.getElementById("downloads-button"); + ok(downloadButton, "download button present"); + await promiseButtonShown(downloadButton.id); + + let EventUtils = {}; + Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", + EventUtils + ); + + // Ensure that state is reset in case previous tests didn't finish. + await task_resetState(); + + await setDownloadDir(); + + startServer(); + + await simulateDropAndCheck(window, downloadButton, [httpUrl("file1.txt")]); + await simulateDropAndCheck(window, downloadButton, [ + httpUrl("file1.txt"), + httpUrl("file2.txt"), + httpUrl("file3.txt"), + ]); +}); diff --git a/browser/components/downloads/test/browser/browser_libraryDrop.js b/browser/components/downloads/test/browser/browser_libraryDrop.js new file mode 100644 index 0000000000..d00ff2b9e6 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_libraryDrop.js @@ -0,0 +1,39 @@ +/* -*- 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/ */ + +registerCleanupFunction(async function() { + await task_resetState(); + await PlacesUtils.history.clear(); +}); + +add_task(async function test_indicatorDrop() { + let EventUtils = {}; + Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", + EventUtils + ); + + // Ensure that state is reset in case previous tests didn't finish. + await task_resetState(); + + await setDownloadDir(); + + startServer(); + + let win = await openLibrary("Downloads"); + registerCleanupFunction(function() { + win.close(); + }); + + let listBox = win.document.getElementById("downloadsRichListBox"); + ok(listBox, "download list box present"); + + await simulateDropAndCheck(win, listBox, [httpUrl("file1.txt")]); + await simulateDropAndCheck(win, listBox, [ + httpUrl("file1.txt"), + httpUrl("file2.txt"), + httpUrl("file3.txt"), + ]); +}); diff --git a/browser/components/downloads/test/browser/browser_library_clearall.js b/browser/components/downloads/test/browser/browser_library_clearall.js new file mode 100644 index 0000000000..41080f23b0 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_library_clearall.js @@ -0,0 +1,121 @@ +/* -*- 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/ */ + +ChromeUtils.defineModuleGetter( + this, + "PlacesTestUtils", + "resource://testing-common/PlacesTestUtils.jsm" +); + +let win; + +function waitForChildren(element, callback) { + let MutationObserver = element.ownerGlobal.MutationObserver; + return new Promise(resolve => { + let observer = new MutationObserver(() => { + if (callback()) { + observer.disconnect(); + resolve(); + } + }); + observer.observe(element, { childList: true }); + }); +} + +async function waitForChildrenLength(element, length, callback) { + if (element.childElementCount != length) { + await waitForChildren(element, () => element.childElementCount == length); + } +} + +registerCleanupFunction(async function() { + await task_resetState(); + await PlacesUtils.history.clear(); +}); + +async function testClearingDownloads(clearCallback) { + const DOWNLOAD_DATA = [ + httpUrl("file1.txt"), + httpUrl("file2.txt"), + httpUrl("file3.txt"), + ]; + + let listbox = win.document.getElementById("downloadsRichListBox"); + ok(listbox, "download list box present"); + + let promiseLength = waitForChildrenLength(listbox, DOWNLOAD_DATA.length); + await simulateDropAndCheck(win, listbox, DOWNLOAD_DATA); + await promiseLength; + + let receivedNotifications = []; + let promiseNotification = PlacesTestUtils.waitForNotification( + "onDeleteURI", + uri => { + if (DOWNLOAD_DATA.includes(uri.spec)) { + receivedNotifications.push(uri.spec); + } + return receivedNotifications.length == DOWNLOAD_DATA.length; + }, + "history" + ); + + promiseLength = waitForChildrenLength(listbox, 0); + await clearCallback(listbox); + await promiseLength; + + await promiseNotification; + + Assert.deepEqual( + receivedNotifications.sort(), + DOWNLOAD_DATA.sort(), + "Should have received notifications for each URL" + ); +} + +add_task(async function setup() { + // Ensure that state is reset in case previous tests didn't finish. + await task_resetState(); + + await setDownloadDir(); + + startServer(); + + win = await openLibrary("Downloads"); + registerCleanupFunction(function() { + win.close(); + }); +}); + +add_task(async function test_clear_downloads_toolbar() { + await testClearingDownloads(async () => { + win.document.getElementById("clearDownloadsButton").click(); + }); +}); + +add_task(async function test_clear_downloads_context_menu() { + await testClearingDownloads(async listbox => { + // Select one of the downloads. + listbox.itemChildren[0].click(); + + let contextMenu = win.document.getElementById("downloadsContextMenu"); + + let popupShownPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popupshown" + ); + EventUtils.synthesizeMouseAtCenter( + listbox.itemChildren[0], + { type: "contextmenu", button: 2 }, + win + ); + await popupShownPromise; + + // Find the clear context item. + let clearDownloadsButton = [...contextMenu.children].find( + child => child.command == "downloadsCmd_clearDownloads" + ); + clearDownloadsButton.click(); + }); +}); diff --git a/browser/components/downloads/test/browser/browser_library_select_all.js b/browser/components/downloads/test/browser/browser_library_select_all.js new file mode 100644 index 0000000000..b3a0a9f263 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_library_select_all.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let gDownloadDir; + +add_task(async function setup() { + await task_resetState(); + + if (!gDownloadDir) { + gDownloadDir = await setDownloadDir(); + } + + await task_addDownloads([ + { + state: DownloadsCommon.DOWNLOAD_FINISHED, + target: await createDownloadedFile( + PathUtils.join(gDownloadDir, "downloaded_one.txt"), + "Test file 1" + ), + }, + { + state: DownloadsCommon.DOWNLOAD_FINISHED, + target: await createDownloadedFile( + PathUtils.join(gDownloadDir, "downloaded_two.txt"), + "Test file 2" + ), + }, + ]); + registerCleanupFunction(async function() { + await task_resetState(); + await PlacesUtils.history.clear(); + }); +}); + +add_task(async function test_select_all() { + let win = await openLibrary("Downloads"); + registerCleanupFunction(() => { + win.close(); + }); + + let listbox = win.document.getElementById("downloadsRichListBox"); + Assert.ok(listbox, "download list box present"); + listbox.focus(); + await TestUtils.waitForCondition( + () => listbox.children.length == 2 && listbox.selectedItems.length == 1, + "waiting for both items to be present with one selected" + ); + info("Select all the downloads"); + win.goDoCommand("cmd_selectAll"); + Assert.equal( + listbox.selectedItems.length, + listbox.children.length, + "All the items should be selected" + ); + + info("Search for a specific download"); + let searchBox = win.document.getElementById("searchFilter"); + searchBox.value = "_one"; + win.PlacesSearchBox.search(searchBox.value); + await TestUtils.waitForCondition(() => { + let visibleItems = Array.from(listbox.children).filter(c => !c.hidden); + return ( + visibleItems.length == 1 && + visibleItems[0]._shell.download.target.path.includes("_one") + ); + }, "Waiting for the search to complete"); + Assert.equal( + listbox.selectedItems.length, + 0, + "Check previous selection has been cleared by the search" + ); + info("Select all the downloads"); + win.goDoCommand("cmd_selectAll"); + Assert.equal(listbox.children.length, 2, "Both items are present"); + Assert.equal(listbox.selectedItems.length, 1, "Only one item is selected"); + Assert.ok(!listbox.selectedItem.hidden, "The selected item is not hidden"); +}); diff --git a/browser/components/downloads/test/browser/browser_overflow_anchor.js b/browser/components/downloads/test/browser/browser_overflow_anchor.js new file mode 100644 index 0000000000..e07140c7a2 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_overflow_anchor.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This is the same value used by CustomizableUI tests. +const kForceOverflowWidthPx = 450; + +registerCleanupFunction(async function() { + // Clean up when the test finishes. + await task_resetState(); +}); + +/** + * Make sure the downloads button and indicator overflows into the nav-bar + * chevron properly, and then when those buttons are clicked in the overflow + * panel that the downloads panel anchors to the chevron`s icon. + */ +add_task(async function test_overflow_anchor() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.autohideButton", false]], + }); + // Ensure that state is reset in case previous tests didn't finish. + await task_resetState(); + + // The downloads button should not be overflowed to begin with. + let button = CustomizableUI.getWidget("downloads-button").forWindow(window); + ok(!button.overflowed, "Downloads button should not be overflowed."); + is( + button.node.getAttribute("cui-areatype"), + "toolbar", + "Button should know it's in the toolbar" + ); + + await gCustomizeMode.addToPanel(button.node); + + let promise = promisePanelOpened(); + await EventUtils.sendMouseEvent( + { type: "mousedown", button: 0 }, + button.node + ); + info("waiting for panel to open"); + await promise; + + let panel = DownloadsPanel.panel; + let chevron = document.getElementById("nav-bar-overflow-button"); + + is( + panel.anchorNode, + chevron.icon, + "Panel should be anchored to the chevron`s icon." + ); + + DownloadsPanel.hidePanel(); + + gCustomizeMode.addToToolbar(button.node); + + // Now try opening the panel again. + promise = promisePanelOpened(); + await EventUtils.sendMouseEvent( + { type: "mousedown", button: 0 }, + button.node + ); + await promise; + + let downloadsAnchor = button.node.badgeStack; + is(panel.anchorNode, downloadsAnchor); + + DownloadsPanel.hidePanel(); +}); diff --git a/browser/components/downloads/test/browser/browser_pdfjs_preview.js b/browser/components/downloads/test/browser/browser_pdfjs_preview.js new file mode 100644 index 0000000000..d183d0bcec --- /dev/null +++ b/browser/components/downloads/test/browser/browser_pdfjs_preview.js @@ -0,0 +1,749 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let gDownloadDir; + +SimpleTest.requestFlakyTimeout( + "Giving a chance for possible last-pb-context-exited to occur (Bug 1329912)" +); + +/* + Coverage for opening downloaded PDFs from download views +*/ + +const TestCases = [ + { + name: "Download panel, default click behavior", + whichUI: "downloadPanel", + itemSelector: "#downloadsListBox richlistitem .downloadMainArea", + async userEvents(itemTarget, win) { + EventUtils.synthesizeMouseAtCenter(itemTarget, {}, win); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + }, + }, + { + name: "Download panel, system viewer menu items prefd off", + whichUI: "downloadPanel", + itemSelector: "#downloadsListBox richlistitem .downloadMainArea", + async userEvents(itemTarget, win) { + EventUtils.synthesizeMouseAtCenter(itemTarget, {}, win); + }, + prefs: [ + ["browser.download.openInSystemViewerContextMenuItem", false], + ["browser.download.alwaysOpenInSystemViewerContextMenuItem", false], + ], + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + useSystemMenuItemDisabled: true, + alwaysMenuItemDisabled: true, + }, + }, + { + name: "Download panel, open from keyboard", + whichUI: "downloadPanel", + itemSelector: "#downloadsListBox richlistitem .downloadMainArea", + async userEvents(itemTarget, win) { + itemTarget.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}, win); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + }, + }, + { + name: "Download panel, open in new window", + whichUI: "downloadPanel", + itemSelector: "#downloadsListBox richlistitem .downloadMainArea", + async userEvents(itemTarget, win) { + EventUtils.synthesizeMouseAtCenter(itemTarget, { shiftKey: true }, win); + }, + expected: { + downloadCount: 1, + newWindow: true, + opensTab: false, + tabSelected: true, + }, + }, + { + name: "Download panel, open foreground tab", // duplicates the default behavior + whichUI: "downloadPanel", + itemSelector: "#downloadsListBox richlistitem .downloadMainArea", + async userEvents(itemTarget, win) { + EventUtils.synthesizeMouseAtCenter( + itemTarget, + { ctrlKey: true, metaKey: true }, + win + ); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + }, + }, + { + name: "Download panel, open background tab", + whichUI: "downloadPanel", + itemSelector: "#downloadsListBox richlistitem .downloadMainArea", + async userEvents(itemTarget, win) { + EventUtils.synthesizeMouseAtCenter( + itemTarget, + { ctrlKey: true, metaKey: true, shiftKey: true }, + win + ); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: false, + }, + }, + + { + name: "Library all downloads dialog, default click behavior", + whichUI: "allDownloads", + async userEvents(itemTarget, win) { + // double click + await triggerDblclickOn(itemTarget, {}, win); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + }, + }, + { + name: "Library all downloads dialog, system viewer menu items prefd off", + whichUI: "allDownloads", + async userEvents(itemTarget, win) { + // double click + await triggerDblclickOn(itemTarget, {}, win); + }, + prefs: [ + ["browser.download.openInSystemViewerContextMenuItem", false], + ["browser.download.alwaysOpenInSystemViewerContextMenuItem", false], + ], + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + useSystemMenuItemDisabled: true, + alwaysMenuItemDisabled: true, + }, + }, + { + name: "Library all downloads dialog, open from keyboard", + whichUI: "allDownloads", + async userEvents(itemTarget, win) { + itemTarget.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}, win); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + }, + }, + { + name: "Library all downloads dialog, open in new window", + whichUI: "allDownloads", + async userEvents(itemTarget, win) { + // double click + await triggerDblclickOn(itemTarget, { shiftKey: true }, win); + }, + expected: { + downloadCount: 1, + newWindow: true, + opensTab: false, + tabSelected: true, + }, + }, + { + name: "Library all downloads dialog, open foreground tab", // duplicates default behavior + whichUI: "allDownloads", + async userEvents(itemTarget, win) { + // double click + await triggerDblclickOn( + itemTarget, + { ctrlKey: true, metaKey: true }, + win + ); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + }, + }, + { + name: "Library all downloads dialog, open background tab", + whichUI: "allDownloads", + async userEvents(itemTarget, win) { + // double click + await triggerDblclickOn( + itemTarget, + { ctrlKey: true, metaKey: true, shiftKey: true }, + win + ); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: false, + }, + }, + { + name: "about:downloads, default click behavior", + whichUI: "aboutDownloads", + itemSelector: "#downloadsRichListBox richlistitem .downloadContainer", + async userEvents(itemSelector, win) { + let browser = win.gBrowser.selectedBrowser; + is(browser.currentURI.spec, "about:downloads"); + await contentTriggerDblclickOn(itemSelector, {}, browser); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + }, + }, + { + name: "about:downloads, system viewer menu items prefd off", + whichUI: "aboutDownloads", + itemSelector: "#downloadsRichListBox richlistitem .downloadContainer", + async userEvents(itemSelector, win) { + let browser = win.gBrowser.selectedBrowser; + is(browser.currentURI.spec, "about:downloads"); + await contentTriggerDblclickOn(itemSelector, {}, browser); + }, + prefs: [ + ["browser.download.openInSystemViewerContextMenuItem", false], + ["browser.download.alwaysOpenInSystemViewerContextMenuItem", false], + ], + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + useSystemMenuItemDisabled: true, + alwaysMenuItemDisabled: true, + }, + }, + { + name: "about:downloads, open in new window", + whichUI: "aboutDownloads", + itemSelector: "#downloadsRichListBox richlistitem .downloadContainer", + async userEvents(itemSelector, win) { + let browser = win.gBrowser.selectedBrowser; + is(browser.currentURI.spec, "about:downloads"); + await contentTriggerDblclickOn(itemSelector, { shiftKey: true }, browser); + }, + expected: { + downloadCount: 1, + newWindow: true, + opensTab: false, + tabSelected: true, + }, + }, + { + name: "about:downloads, open in foreground tab", + whichUI: "aboutDownloads", + itemSelector: "#downloadsRichListBox richlistitem .downloadContainer", + async userEvents(itemSelector, win) { + let browser = win.gBrowser.selectedBrowser; + is(browser.currentURI.spec, "about:downloads"); + await contentTriggerDblclickOn( + itemSelector, + { ctrlKey: true, metaKey: true }, + browser + ); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: true, + }, + }, + { + name: "about:downloads, open in background tab", + whichUI: "aboutDownloads", + itemSelector: "#downloadsRichListBox richlistitem .downloadContainer", + async userEvents(itemSelector, win) { + let browser = win.gBrowser.selectedBrowser; + is(browser.currentURI.spec, "about:downloads"); + await contentTriggerDblclickOn( + itemSelector, + { ctrlKey: true, metaKey: true, shiftKey: true }, + browser + ); + }, + expected: { + downloadCount: 1, + newWindow: false, + opensTab: true, + tabSelected: false, + }, + }, + { + name: "Private download in about:downloads, opens in new private window", + skip: true, // Bug 1641770 + whichUI: "aboutDownloads", + itemSelector: "#downloadsRichListBox richlistitem .downloadContainer", + async userEvents(itemSelector, win) { + let browser = win.gBrowser.selectedBrowser; + is(browser.currentURI.spec, "about:downloads"); + await contentTriggerDblclickOn(itemSelector, { shiftKey: true }, browser); + }, + isPrivate: true, + expected: { + downloadCount: 1, + newWindow: true, + opensTab: false, + tabSelected: true, + }, + }, +]; + +function triggerDblclickOn(target, modifiers = {}, win) { + let promise = BrowserTestUtils.waitForEvent(target, "dblclick"); + EventUtils.synthesizeMouseAtCenter( + target, + Object.assign({ clickCount: 1 }, modifiers), + win + ); + EventUtils.synthesizeMouseAtCenter( + target, + Object.assign({ clickCount: 2 }, modifiers), + win + ); + return promise; +} + +function contentTriggerDblclickOn(selector, eventModifiers = {}, browser) { + return SpecialPowers.spawn( + browser, + [selector, eventModifiers], + async function(itemSelector, modifiers) { + const EventUtils = ContentTaskUtils.getEventUtils(content); + let itemTarget = content.document.querySelector(itemSelector); + ok(itemTarget, "Download item target exists"); + + let doubleClicked = ContentTaskUtils.waitForEvent(itemTarget, "dblclick"); + EventUtils.synthesizeMouseAtCenter( + itemTarget, + Object.assign({ clickCount: 1 }, modifiers), + content + ); + EventUtils.synthesizeMouseAtCenter( + itemTarget, + Object.assign({ clickCount: 2 }, modifiers), + content + ); + info("Waiting for double-click content task"); + await doubleClicked; + } + ); +} + +async function verifyContextMenu(contextMenu, expected = {}) { + info("verifyContextMenu with expected: " + JSON.stringify(expected, null, 2)); + let alwaysMenuItem = contextMenu.querySelector( + ".downloadAlwaysUseSystemDefaultMenuItem" + ); + let useSystemMenuItem = contextMenu.querySelector( + ".downloadUseSystemDefaultMenuItem" + ); + info("Waiting for the context menu to show up"); + await TestUtils.waitForCondition( + () => BrowserTestUtils.is_visible(contextMenu), + "The context menu is visible" + ); + await TestUtils.waitForTick(); + + info("Checking visibility of the system viewer menu items"); + is( + BrowserTestUtils.is_hidden(useSystemMenuItem), + expected.useSystemMenuItemDisabled, + `The 'Use system viewer' menu item was ${ + expected.useSystemMenuItemDisabled ? "hidden" : "visible" + }` + ); + is( + BrowserTestUtils.is_hidden(alwaysMenuItem), + expected.alwaysMenuItemDisabled, + `The 'Use system viewer' menu item was ${ + expected.alwaysMenuItemDisabled ? "hidden" : "visible" + }` + ); + + if (!expected.useSystemMenuItemDisabled && expected.alwaysChecked) { + is( + alwaysMenuItem.getAttribute("checked"), + "true", + "The 'Always...' menu item is checked" + ); + } else if (!expected.useSystemMenuItemDisabled) { + ok( + !alwaysMenuItem.hasAttribute("checked"), + "The 'Always...' menu item not checked" + ); + } +} + +async function addPDFDownload(itemData) { + let startTimeMs = Date.now(); + info("addPDFDownload with itemData: " + JSON.stringify(itemData, null, 2)); + + let downloadPathname = OS.Path.join(gDownloadDir, itemData.targetFilename); + delete itemData.targetFilename; + + info("Creating saved download file at:" + downloadPathname); + let pdfFile = await createDownloadedFile(downloadPathname, DATA_PDF); + info("Created file at:" + pdfFile.path); + + let downloadList = await Downloads.getList( + itemData.isPrivate ? Downloads.PRIVATE : Downloads.PUBLIC + ); + let download = { + source: { + url: "https://example.com/some.pdf", + isPrivate: itemData.isPrivate, + }, + target: { + path: pdfFile.path, + }, + succeeded: DownloadsCommon.DOWNLOAD_FINISHED, + canceled: false, + error: null, + hasPartialData: false, + hasBlockedData: itemData.hasBlockedData || false, + startTime: new Date(startTimeMs++), + ...itemData, + }; + if (itemData.errorObj) { + download.errorObj = itemData.errorObj; + } + + await downloadList.add(await Downloads.createDownload(download)); + return download; +} + +async function testSetup() { + // remove download files, empty out collections + let downloadList = await Downloads.getList(Downloads.ALL); + let downloadCount = (await downloadList.getAll()).length; + is(downloadCount, 0, "At the start of the test, there should be 0 downloads"); + + await task_resetState(); + if (!gDownloadDir) { + gDownloadDir = await setDownloadDir(); + } + info("Created download directory: " + gDownloadDir); +} + +async function openDownloadPanel(expectedItemCount) { + // Open the user interface and wait for data to be fully loaded. + let richlistbox = document.getElementById("downloadsListBox"); + await task_openPanel(); + await TestUtils.waitForCondition( + () => richlistbox.childElementCount == expectedItemCount + ); +} + +async function testOpenPDFPreview({ + name, + whichUI, + downloadProperties, + itemSelector, + expected, + prefs = [], + userEvents, + isPrivate, +}) { + info("Test case: " + name); + // Wait for focus first + await promiseFocus(); + await testSetup(); + if (prefs.length) { + await SpecialPowers.pushPrefEnv({ + set: prefs, + }); + } + + // Populate downloads database with the data required by this test. + info("Adding download objects"); + if (!downloadProperties) { + downloadProperties = { + targetFilename: "downloaded.pdf", + }; + } + let download = await addPDFDownload({ + ...downloadProperties, + isPrivate, + }); + info("Got download pathname:" + download.target.path); + is( + !!download.source.isPrivate, + !!isPrivate, + `Added download is ${isPrivate ? "private" : "not private"} as expected` + ); + let downloadList = await Downloads.getList( + isPrivate ? Downloads.PRIVATE : Downloads.PUBLIC + ); + let downloads = await downloadList.getAll(); + is( + downloads.length, + expected.downloadCount, + `${isPrivate ? "Private" : "Public"} list has expected ${ + downloads.length + } downloads` + ); + + let pdfFileURI = NetUtil.newURI(new FileUtils.File(download.target.path)); + info("pdfFileURI:" + pdfFileURI.spec); + + let uiWindow = window; + let previewWindow = window; + // we never want to unload the test browser by loading the file: URI into it + await BrowserTestUtils.withNewTab("about:blank", async initialBrowser => { + let previewTab; + let previewHappened; + + if (expected.newWindow) { + info( + "previewHappened will wait for new browser window with url: " + + pdfFileURI.spec + ); + // wait for a new browser window + previewHappened = BrowserTestUtils.waitForNewWindow({ + anyWindow: true, + url: pdfFileURI.spec, + }); + } else if (expected.opensTab) { + // wait for a tab to be opened + info("previewHappened will wait for tab with URI:" + pdfFileURI.spec); + previewHappened = BrowserTestUtils.waitForNewTab( + gBrowser, + pdfFileURI.spec, + false, // dont wait for load + true // any tab, not just the next one + ); + } else { + info( + "previewHappened will wait to load " + + pdfFileURI.spec + + " into the current tab" + ); + previewHappened = BrowserTestUtils.browserLoaded( + initialBrowser, + false, + pdfFileURI.spec + ); + } + + let itemTarget; + let contextMenu; + + switch (whichUI) { + case "downloadPanel": + info("Opening download panel"); + await openDownloadPanel(expected.downloadCount); + info("/Opening download panel"); + itemTarget = document.querySelector(itemSelector); + contextMenu = uiWindow.document.querySelector("#downloadsContextMenu"); + + break; + case "allDownloads": + // we'll be interacting with the library dialog + uiWindow = await openLibrary("Downloads"); + + let listbox = uiWindow.document.getElementById("downloadsRichListBox"); + ok(listbox, "download list box present"); + // wait for the expected number of items in the view, + // and for the first item to be visible && clickable + await TestUtils.waitForCondition(() => { + return ( + listbox.itemChildren.length == expected.downloadCount && + BrowserTestUtils.is_visible(listbox.itemChildren[0]) + ); + }); + itemTarget = listbox.itemChildren[0]; + contextMenu = uiWindow.document.querySelector("#downloadsContextMenu"); + + break; + case "aboutDownloads": + info("Preparing about:downloads browser window"); + + // Because of bug 1329912, we sometimes get a bogus last-pb-context-exited notification + // which removes all the private downloads and about:downloads renders a empty list + // we'll allow time for that to happen before loading about:downloads + let pbExitedOrTimeout = isPrivate + ? new Promise(resolve => { + const topic = "last-pb-context-exited"; + const ENOUGH_TIME = 1000; + function observer() { + info(`Bogus ${topic} observed`); + done(); + } + function done() { + clearTimeout(timerId); + Services.obs.removeObserver(observer, topic); + resolve(); + } + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + const timerId = setTimeout(done, ENOUGH_TIME); + Services.obs.addObserver(observer, "last-pb-context-exited"); + }) + : Promise.resolve(); + + if (isPrivate) { + uiWindow = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + } + info( + "in aboutDownloads, initially there are tabs: " + + uiWindow.gBrowser.tabs.length + ); + + let browser = uiWindow.gBrowser.selectedBrowser; + await pbExitedOrTimeout; + + info("Loading about:downloads"); + let downloadsLoaded = BrowserTestUtils.waitForEvent( + browser, + "InitialDownloadsLoaded", + true + ); + BrowserTestUtils.loadURI(browser, "about:downloads"); + await BrowserTestUtils.browserLoaded(browser); + info("waiting for downloadsLoaded"); + await downloadsLoaded; + + await ContentTask.spawn( + browser, + [expected.downloadCount], + async function awaitListItems(expectedCount) { + await ContentTaskUtils.waitForCondition( + () => + content.document.getElementById("downloadsRichListBox") + .childElementCount == expectedCount, + `Await ${expectedCount} download list items` + ); + } + ); + break; + } + + if (contextMenu) { + info("trigger the contextmenu"); + await openContextMenu(itemTarget || itemSelector, uiWindow); + info("context menu should be open, verify its menu items"); + let expectedValues = { + useSystemMenuItemDisabled: false, + alwaysMenuItemDisabled: false, + ...expected, + }; + await verifyContextMenu(contextMenu, expectedValues); + contextMenu.hidePopup(); + } else { + todo(contextMenu, "No context menu checks for test: " + name); + } + + info("Executing user events"); + await userEvents(itemTarget || itemSelector, uiWindow); + + info("Waiting for previewHappened"); + let results = await previewHappened; + if (expected.newWindow) { + previewWindow = results; + info("New window expected, got previewWindow? " + previewWindow); + } + previewTab = + previewWindow.gBrowser.tabs[previewWindow.gBrowser.tabs.length - 1]; + ok(previewTab, "Got preview tab"); + + let isSelected = previewWindow.gBrowser.selectedTab == previewTab; + if (expected.tabSelected) { + ok(isSelected, "The preview tab was selected"); + } else { + ok(!isSelected, "The preview tab was opened in the background"); + } + + is( + previewTab.linkedBrowser.currentURI.spec, + pdfFileURI.spec, + "previewTab has the expected currentURI" + ); + + is( + PrivateBrowsingUtils.isBrowserPrivate(previewTab.linkedBrowser), + !!isPrivate, + `The preview tab was ${isPrivate ? "private" : "not private"} as expected` + ); + + info("cleaning up"); + if (whichUI == "downloadPanel") { + DownloadsPanel.hidePanel(); + } + let lastPBContextExitedPromise = isPrivate + ? TestUtils.topicObserved("last-pb-context-exited").then(() => + TestUtils.waitForTick() + ) + : Promise.resolve(); + + info("Test opened a new UI window? " + (uiWindow !== window)); + if (uiWindow !== window) { + info("Closing uiWindow"); + await BrowserTestUtils.closeWindow(uiWindow); + } + if (expected.newWindow) { + // will also close the previewTab + await BrowserTestUtils.closeWindow(previewWindow); + } else { + await BrowserTestUtils.removeTab(previewTab); + } + info("Waiting for lastPBContextExitedPromise"); + await lastPBContextExitedPromise; + }); + await downloadList.removeFinished(); + if (prefs.length) { + await SpecialPowers.popPrefEnv(); + } +} + +// register the tests +for (let testData of TestCases) { + if (testData.skip) { + info("Skipping test:" + testData.name); + continue; + } + // use the 'name' property of each test case as the test function name + // so we get useful logs + let tmp = { + async [testData.name]() { + await testOpenPDFPreview(testData); + }, + }; + add_task(tmp[testData.name]); +} diff --git a/browser/components/downloads/test/browser/foo.txt b/browser/components/downloads/test/browser/foo.txt new file mode 100644 index 0000000000..77e7195596 --- /dev/null +++ b/browser/components/downloads/test/browser/foo.txt @@ -0,0 +1 @@ +Dummy content for unknownContentType_dialog_layout_data.txt diff --git a/browser/components/downloads/test/browser/foo.txt^headers^ b/browser/components/downloads/test/browser/foo.txt^headers^ new file mode 100644 index 0000000000..2a3c472e26 --- /dev/null +++ b/browser/components/downloads/test/browser/foo.txt^headers^ @@ -0,0 +1,2 @@ +Content-Type: text/plain +Content-Disposition: attachment diff --git a/browser/components/downloads/test/browser/head.js b/browser/components/downloads/test/browser/head.js new file mode 100644 index 0000000000..a184bd1582 --- /dev/null +++ b/browser/components/downloads/test/browser/head.js @@ -0,0 +1,284 @@ +/* -*- 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.defineModuleGetter( + this, + "Downloads", + "resource://gre/modules/Downloads.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "DownloadsCommon", + "resource:///modules/DownloadsCommon.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "FileUtils", + "resource://gre/modules/FileUtils.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "HttpServer", + "resource://testing-common/httpd.js" +); + +var gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]); +gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); + +// The file may have been already deleted when removing a paused download. +registerCleanupFunction(() => + OS.File.remove(gTestTargetFile.path, { ignoreAbsent: true }) +); + +const DATA_PDF = atob( + "JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G" +); + +// Asynchronous support subroutines + +async function createDownloadedFile(pathname, contents) { + let encoder = new TextEncoder(); + 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 OS.File.writeAtomic(pathname, encoder.encode(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) { + publicList.remove(download); + await download.finalize(true); + } + + 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, + error: + item.state == DownloadsCommon.DOWNLOAD_FAILED + ? new Error("Failed.") + : null, + hasPartialData: item.state == DownloadsCommon.DOWNLOAD_PAUSED, + hasBlockedData: item.hasBlockedData || false, + 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 = await PathUtils.getTempDir(); + tmpDir = PathUtils.join( + tmpDir, + "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) { + Cu.reportError(e); + } + }); + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setCharPref("browser.download.dir", tmpDir); + return tmpDir; +} + +let gHttpServer = null; +function startServer() { + gHttpServer = new HttpServer(); + gHttpServer.start(-1); + registerCleanupFunction(async function() { + await new Promise(function(resolve) { + gHttpServer.stop(resolve); + }); + }); + + 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(); + }); +} + +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 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"); + } +} diff --git a/browser/components/downloads/test/browser/not-really-a-jpeg.jpeg b/browser/components/downloads/test/browser/not-really-a-jpeg.jpeg Binary files differnew file mode 100644 index 0000000000..04b7f003b4 --- /dev/null +++ b/browser/components/downloads/test/browser/not-really-a-jpeg.jpeg diff --git a/browser/components/downloads/test/browser/not-really-a-jpeg.jpeg^headers^ b/browser/components/downloads/test/browser/not-really-a-jpeg.jpeg^headers^ new file mode 100644 index 0000000000..c1a7794310 --- /dev/null +++ b/browser/components/downloads/test/browser/not-really-a-jpeg.jpeg^headers^ @@ -0,0 +1,2 @@ +Content-Type: image/webp + diff --git a/browser/components/downloads/test/unit/head.js b/browser/components/downloads/test/unit/head.js new file mode 100644 index 0000000000..6769bfe79a --- /dev/null +++ b/browser/components/downloads/test/unit/head.js @@ -0,0 +1,89 @@ +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +ChromeUtils.defineModuleGetter( + this, + "Downloads", + "resource://gre/modules/Downloads.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "DownloadsCommon", + "resource:///modules/DownloadsCommon.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "FileUtils", + "resource://gre/modules/FileUtils.jsm" +); +ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); +ChromeUtils.defineModuleGetter( + this, + "FileTestUtils", + "resource://testing-common/FileTestUtils.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "NetUtil", + "resource://gre/modules/NetUtil.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "TestUtils", + "resource://testing-common/TestUtils.jsm" +); + +async function createDownloadedFile(pathname, contents) { + info("createDownloadedFile: " + pathname); + let encoder = new TextEncoder(); + let file = new FileUtils.File(pathname); + if (file.exists()) { + info(`File at ${pathname} already exists`); + if (!contents) { + ok( + false, + `A file already exists at ${pathname}, but createDownloadedFile was asked to create a non-existant file` + ); + } + } + if (contents) { + await OS.File.writeAtomic(pathname, encoder.encode(contents)); + ok(file.exists(), `Created ${pathname}`); + } + // No post-test cleanup necessary; tmp downloads directory is already removed after each test + return file; +} + +let gDownloadDir; + +async function setDownloadDir() { + let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile).path; + tmpDir = PathUtils.join( + tmpDir, + "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) { + Cu.reportError(e); + } + }); + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setCharPref("browser.download.dir", tmpDir); + return tmpDir; +} + +/** + * All the tests are implemented with add_task, this starts them automatically. + */ +function run_test() { + do_get_profile(); + run_next_test(); +} + +add_task(async function test_common_initialize() { + gDownloadDir = await setDownloadDir(); + Services.prefs.setCharPref("browser.download.loglevel", "Debug"); +}); diff --git a/browser/components/downloads/test/unit/test_DownloadsCommon_getMimeInfo.js b/browser/components/downloads/test/unit/test_DownloadsCommon_getMimeInfo.js new file mode 100644 index 0000000000..89d1bf708a --- /dev/null +++ b/browser/components/downloads/test/unit/test_DownloadsCommon_getMimeInfo.js @@ -0,0 +1,175 @@ +const DATA_PDF = atob( + "JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G" +); + +const DOWNLOAD_TEMPLATE = { + source: { + url: "https://example.com/download", + }, + target: { + path: "", + }, + contentType: "text/plain", + succeeded: DownloadsCommon.DOWNLOAD_FINISHED, + canceled: false, + error: null, + hasPartialData: false, + hasBlockedData: false, + startTime: new Date(Date.now() - 1000), +}; + +const TESTFILES = { + "download-test.txt": "Text file contents\n", + "download-test.pdf": DATA_PDF, + "download-test.PDF": DATA_PDF, + "download-test.xxunknown": "Unknown file contents\n", + "download-test": "No extension file contents\n", +}; +let gPublicList; + +add_task(async function test_setup() { + Assert.ok( + OS.Constants.Path.profileDir, + "profileDir: " + OS.Constants.Path.profileDir + ); + for (let [filename, contents] of Object.entries(TESTFILES)) { + TESTFILES[filename] = await createDownloadedFile( + OS.Path.join(gDownloadDir, filename), + contents + ); + } + gPublicList = await Downloads.getList(Downloads.PUBLIC); +}); + +const TESTCASES = [ + { + name: "Check returned value is null when the download did not succeed", + testFile: "download-test.txt", + contentType: "text/plain", + succeeded: false, + expected: null, + }, + { + name: + "Check correct mime-info is returned when download contentType is unambiguous", + testFile: "download-test.txt", + contentType: "text/plain", + expected: { + type: "text/plain", + }, + }, + { + name: + "Returns correct mime-info from file extension when download contentType is missing", + testFile: "download-test.pdf", + contentType: undefined, + expected: { + type: "application/pdf", + }, + }, + { + name: "Returns correct mime-info from file extension case-insensitively", + testFile: "download-test.PDF", + contentType: undefined, + expected: { + type: "application/pdf", + }, + }, + { + name: + "Returns null when contentType is missing and file extension is unknown", + testFile: "download-test.xxunknown", + contentType: undefined, + expected: null, + }, + { + name: + "Returns contentType when contentType is ambiguous and file extension is unknown", + testFile: "download-test.xxunknown", + contentType: "application/octet-stream", + expected: { + type: "application/octet-stream", + }, + }, + { + name: + "Returns contentType when contentType is ambiguous and there is no file extension", + testFile: "download-test", + contentType: "application/octet-stream", + expected: { + type: "application/octet-stream", + }, + }, + { + name: "Returns null when there's no contentType and no file extension", + testFile: "download-test", + contentType: undefined, + expected: null, + }, +]; + +// add tests for each of the generic mime-types we recognize, +// to ensure they prefer the associated mime-type of the target file extension +for (let type of [ + "application/octet-stream", + "binary/octet-stream", + "application/unknown", +]) { + TESTCASES.push({ + name: `Returns correct mime-info from file extension when contentType is generic (${type})`, + testFile: "download-test.pdf", + contentType: type, + expected: { + type: "application/pdf", + }, + }); +} + +for (let testData of TESTCASES) { + let tmp = { + async [testData.name]() { + info("testing with: " + JSON.stringify(testData)); + await test_getMimeInfo_basic_function(testData); + }, + }; + add_task(tmp[testData.name]); +} + +/** + * Sanity test the DownloadsCommon.getMimeInfo method with test parameters + */ +async function test_getMimeInfo_basic_function(testData) { + let downloadData = { + ...DOWNLOAD_TEMPLATE, + source: "source" in testData ? testData.source : DOWNLOAD_TEMPLATE.source, + succeeded: + "succeeded" in testData + ? testData.succeeded + : DOWNLOAD_TEMPLATE.succeeded, + target: TESTFILES[testData.testFile], + contentType: testData.contentType, + }; + Assert.ok(downloadData.target instanceof Ci.nsIFile, "target is a nsIFile"); + let download = await Downloads.createDownload(downloadData); + await gPublicList.add(download); + await download.refresh(); + + Assert.ok( + await OS.File.exists(download.target.path), + "The file should actually exist." + ); + let result = await DownloadsCommon.getMimeInfo(download); + if (testData.expected) { + Assert.equal( + result.type, + testData.expected.type, + "Got expected mimeInfo.type" + ); + } else { + Assert.equal( + result, + null, + `Expected null, got object with type: ${result?.type}` + ); + } +} diff --git a/browser/components/downloads/test/unit/test_DownloadsCommon_isFileOfType.js b/browser/components/downloads/test/unit/test_DownloadsCommon_isFileOfType.js new file mode 100644 index 0000000000..e921d7b540 --- /dev/null +++ b/browser/components/downloads/test/unit/test_DownloadsCommon_isFileOfType.js @@ -0,0 +1,153 @@ +const DATA_PDF = atob( + "JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G" +); + +const DOWNLOAD_TEMPLATE = { + source: { + url: "https://download-test.com/download", + }, + target: { + path: "", + }, + contentType: "text/plain", + succeeded: DownloadsCommon.DOWNLOAD_FINISHED, + canceled: false, + error: null, + hasPartialData: false, + hasBlockedData: false, + startTime: new Date(Date.now() - 1000), +}; + +const TESTFILES = { + "download-test.pdf": DATA_PDF, + "download-test.xxunknown": DATA_PDF, + "download-test-missing.pdf": null, +}; +let gPublicList; + +add_task(async function test_setup() { + Assert.ok( + OS.Constants.Path.profileDir, + "profileDir: " + OS.Constants.Path.profileDir + ); + for (let [filename, contents] of Object.entries(TESTFILES)) { + TESTFILES[filename] = await createDownloadedFile( + OS.Path.join(gDownloadDir, filename), + contents + ); + } + gPublicList = await Downloads.getList(Downloads.PUBLIC); +}); + +const TESTCASES = [ + { + name: "Null download arg", + typeArg: "application/pdf", + downloadProps: null, + expected: /TypeError/, + }, + { + name: "Missing type arg", + typeArg: undefined, + downloadProps: { + target: "download-test.pdf", + }, + expected: /TypeError/, + }, + { + name: "Empty string type arg", + typeArg: "", + downloadProps: { + target: "download-test.pdf", + }, + expected: false, + }, + { + name: + "download succeeded, file exists, unknown extension but contentType matches", + typeArg: "application/pdf", + downloadProps: { + target: "download-test.xxunknown", + contentType: "application/pdf", + }, + expected: true, + }, + { + name: + "download succeeded, file exists, contentType is generic and file extension maps to matching mime-type", + typeArg: "application/pdf", + downloadProps: { + target: "download-test.pdf", + contentType: "application/unknown", + }, + expected: true, + }, + { + name: "download did not succeed", + typeArg: "application/pdf", + downloadProps: { + target: "download-test.pdf", + contentType: "application/pdf", + succeeded: false, + }, + expected: false, + }, + { + name: "file does not exist", + typeArg: "application/pdf", + downloadProps: { + target: "download-test-missing.pdf", + contentType: "application/pdf", + }, + expected: false, + }, + { + name: + "contentType is missing and file extension doesnt map to a known mime-type", + typeArg: "application/pdf", + downloadProps: { + contentType: undefined, + target: "download-test.xxunknown", + }, + expected: false, + }, +]; + +for (let testData of TESTCASES) { + let tmp = { + async [testData.name]() { + info("testing with: " + JSON.stringify(testData)); + await test_isFileOfType(testData); + }, + }; + add_task(tmp[testData.name]); +} + +/** + * Sanity test the DownloadsCommon.isFileOfType method with test parameters + */ +async function test_isFileOfType({ name, typeArg, downloadProps, expected }) { + let download, result; + if (downloadProps) { + let downloadData = { + ...DOWNLOAD_TEMPLATE, + ...downloadProps, + }; + downloadData.target = TESTFILES[downloadData.target]; + Assert.ok(downloadData.target instanceof Ci.nsIFile, "target is a nsIFile"); + download = await Downloads.createDownload(downloadData); + await gPublicList.add(download); + await download.refresh(); + } + + if (typeof expected == "boolean") { + result = await DownloadsCommon.isFileOfType(download, typeArg); + Assert.equal(result, expected, "Expected result from call to isFileOfType"); + } else { + Assert.throws( + () => DownloadsCommon.isFileOfType(download, typeArg), + expected, + "isFileOfType should throw an exception if either the download object or mime-type arguments are falsey" + ); + } +} diff --git a/browser/components/downloads/test/unit/test_DownloadsViewableInternally.js b/browser/components/downloads/test/unit/test_DownloadsViewableInternally.js new file mode 100644 index 0000000000..16aee6e209 --- /dev/null +++ b/browser/components/downloads/test/unit/test_DownloadsViewableInternally.js @@ -0,0 +1,252 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const PREF_SVG_DISABLED = "svg.disabled"; +const PREF_WEBP_ENABLED = "image.webp.enabled"; +const PDF_MIME = "application/pdf"; +const OCTET_MIME = "application/octet-stream"; +const XML_MIME = "text/xml"; +const SVG_MIME = "image/svg+xml"; +const WEBP_MIME = "image/webp"; + +const { Integration } = ChromeUtils.import( + "resource://gre/modules/Integration.jsm" +); +const { + DownloadsViewableInternally, + PREF_ENABLED_TYPES, + PREF_BRANCH_WAS_REGISTERED, + PREF_BRANCH_PREVIOUS_ACTION, + PREF_BRANCH_PREVIOUS_ASK, +} = ChromeUtils.import("resource:///modules/DownloadsViewableInternally.jsm"); + +/* global DownloadIntegration */ +Integration.downloads.defineModuleGetter( + this, + "DownloadIntegration", + "resource://gre/modules/DownloadIntegration.jsm" +); + +const HandlerService = Cc[ + "@mozilla.org/uriloader/handler-service;1" +].getService(Ci.nsIHandlerService); +const MIMEService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + +function checkPreferInternal(mime, ext, expectedPreferInternal) { + const handler = MIMEService.getFromTypeAndExtension(mime, ext); + if (expectedPreferInternal) { + Assert.equal( + handler?.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + `checking ${mime} preferredAction == handleInternally` + ); + } else { + Assert.notEqual( + handler?.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + `checking ${mime} preferredAction != handleInternally` + ); + } +} + +function shouldView(mime, ext) { + return DownloadIntegration.shouldViewDownloadInternally(mime, ext); +} + +function checkShouldView(mime, ext, expectedShouldView) { + Assert.equal( + shouldView(mime, ext), + expectedShouldView, + `checking ${mime} shouldViewDownloadInternally` + ); +} + +function checkWasRegistered(ext, expectedWasRegistered) { + Assert.equal( + Services.prefs.getBoolPref(PREF_BRANCH_WAS_REGISTERED + ext, false), + expectedWasRegistered, + `checking ${ext} was registered pref` + ); +} + +function checkAll(mime, ext, expected) { + checkPreferInternal(mime, ext, expected); + checkShouldView(mime, ext, expected); + checkWasRegistered(ext, expected); +} + +add_task(async function test_viewable_internally() { + Services.prefs.setCharPref(PREF_ENABLED_TYPES, "xml , svg,webp"); + Services.prefs.setBoolPref(PREF_SVG_DISABLED, false); + Services.prefs.setBoolPref(PREF_WEBP_ENABLED, true); + + checkAll(XML_MIME, "xml", false); + checkAll(SVG_MIME, "svg", false); + checkAll(WEBP_MIME, "webp", false); + + DownloadsViewableInternally.register(); + + checkAll(XML_MIME, "xml", true); + checkAll(SVG_MIME, "svg", true); + checkAll(WEBP_MIME, "webp", true); + + // Remove SVG so it won't be cleared + Services.prefs.clearUserPref(PREF_BRANCH_WAS_REGISTERED + "svg"); + + // Disable xml and svg, check that xml becomes disabled + Services.prefs.setCharPref(PREF_ENABLED_TYPES, "webp"); + + checkAll(XML_MIME, "xml", false); + checkAll(WEBP_MIME, "webp", true); + + // SVG shouldn't be cleared + checkPreferInternal(SVG_MIME, "svg", true); + + Assert.ok( + shouldView(PDF_MIME), + "application/pdf should be unaffected by pref" + ); + Assert.ok( + shouldView(OCTET_MIME, "pdf"), + ".pdf should be accepted by extension" + ); + Assert.ok( + shouldView(OCTET_MIME, "PDF"), + ".pdf should be detected case-insensitively" + ); + Assert.ok(!shouldView(OCTET_MIME, "exe"), ".exe shouldn't be accepted"); + + Assert.ok(!shouldView(XML_MIME), "text/xml should be disabled by pref"); + Assert.ok(!shouldView(SVG_MIME), "image/xml+svg should be disabled by pref"); + + // Enable, check that everything is enabled again + Services.prefs.setCharPref(PREF_ENABLED_TYPES, "xml,svg,webp"); + + checkPreferInternal(XML_MIME, "xml", true); + checkPreferInternal(SVG_MIME, "svg", true); + + Assert.ok( + shouldView(PDF_MIME), + "application/pdf should be unaffected by pref" + ); + Assert.ok(shouldView(XML_MIME), "text/xml should be enabled by pref"); + Assert.ok( + shouldView("application/xml"), + "alternate MIME type application/xml should be accepted" + ); + Assert.ok( + shouldView(OCTET_MIME, "xml"), + ".xml should be accepted by extension" + ); + + // Disable viewable internally, pre-set handlers. + Services.prefs.setCharPref(PREF_ENABLED_TYPES, ""); + + for (const [mime, ext, action, ask] of [ + [XML_MIME, "xml", Ci.nsIHandlerInfo.useSystemDefault, true], + [SVG_MIME, "svg", Ci.nsIHandlerInfo.saveToDisk, true], + [WEBP_MIME, "webp", Ci.nsIHandlerInfo.saveToDisk, false], + ]) { + let handler = MIMEService.getFromTypeAndExtension(mime, ext); + handler.preferredAction = action; + handler.alwaysAskBeforeHandling = ask; + + HandlerService.store(handler); + checkPreferInternal(mime, ext, false); + + // Expect to read back the same values + handler = MIMEService.getFromTypeAndExtension(mime, ext); + Assert.equal(handler.preferredAction, action); + Assert.equal(handler.alwaysAskBeforeHandling, ask); + } + + // Enable viewable internally, XML should not be replaced, SVG and WebP should be saved. + Services.prefs.setCharPref(PREF_ENABLED_TYPES, "svg,webp,xml"); + + Assert.equal( + Services.prefs.getIntPref(PREF_BRANCH_PREVIOUS_ACTION + "svg"), + Ci.nsIHandlerInfo.saveToDisk, + "svg action should be saved" + ); + Assert.equal( + Services.prefs.getBoolPref(PREF_BRANCH_PREVIOUS_ASK + "svg"), + true, + "svg ask should be saved" + ); + Assert.equal( + Services.prefs.getIntPref(PREF_BRANCH_PREVIOUS_ACTION + "webp"), + Ci.nsIHandlerInfo.saveToDisk, + "webp action should be saved" + ); + Assert.equal( + Services.prefs.getBoolPref(PREF_BRANCH_PREVIOUS_ASK + "webp"), + false, + "webp ask should be saved" + ); + + { + let handler = MIMEService.getFromTypeAndExtension(XML_MIME, "xml"); + Assert.equal( + handler.preferredAction, + Ci.nsIHandlerInfo.useSystemDefault, + "svg action should be preserved" + ); + Assert.equal( + !!handler.alwaysAskBeforeHandling, + true, + "svg ask should be preserved" + ); + // Clean up + HandlerService.remove(handler); + } + // It should still be possible to view XML internally + checkShouldView(XML_MIME, "xml", true); + checkWasRegistered("xml", true); + + checkAll(SVG_MIME, "svg", true); + checkAll(WEBP_MIME, "webp", true); + + // Disable SVG to test SVG enabled check (depends on the pref) + Services.prefs.setBoolPref(PREF_SVG_DISABLED, true); + // Should have restored the settings from above + { + let handler = MIMEService.getFromTypeAndExtension(SVG_MIME, "svg"); + Assert.equal(handler.preferredAction, Ci.nsIHandlerInfo.saveToDisk); + Assert.equal(!!handler.alwaysAskBeforeHandling, true); + // Clean up + HandlerService.remove(handler); + } + checkAll(SVG_MIME, "svg", false); + + Services.prefs.setBoolPref(PREF_SVG_DISABLED, false); + checkAll(SVG_MIME, "svg", true); + + // Test WebP enabled check (depends on the pref) + Services.prefs.setBoolPref(PREF_WEBP_ENABLED, false); + // Should have restored the settings from above + { + let handler = MIMEService.getFromTypeAndExtension(WEBP_MIME, "webp"); + Assert.equal(handler.preferredAction, Ci.nsIHandlerInfo.saveToDisk); + Assert.equal(!!handler.alwaysAskBeforeHandling, false); + // Clean up + HandlerService.remove(handler); + } + checkAll(WEBP_MIME, "webp", false); + + Services.prefs.setBoolPref(PREF_WEBP_ENABLED, true); + checkAll(WEBP_MIME, "webp", true); + + Assert.ok(!shouldView(null, "pdf"), "missing MIME shouldn't be accepted"); + Assert.ok(!shouldView(null, "xml"), "missing MIME shouldn't be accepted"); + Assert.ok(!shouldView(OCTET_MIME), "unsupported MIME shouldn't be accepted"); + Assert.ok(!shouldView(OCTET_MIME, "exe"), ".exe shouldn't be accepted"); +}); + +registerCleanupFunction(() => { + // Clear all types to remove any saved values + Services.prefs.setCharPref(PREF_ENABLED_TYPES, ""); + // Reset to the defaults + Services.prefs.clearUserPref(PREF_ENABLED_TYPES); + Services.prefs.clearUserPref(PREF_SVG_DISABLED); + Services.prefs.clearUserPref(PREF_WEBP_ENABLED); +}); diff --git a/browser/components/downloads/test/unit/xpcshell.ini b/browser/components/downloads/test/unit/xpcshell.ini new file mode 100644 index 0000000000..c272510def --- /dev/null +++ b/browser/components/downloads/test/unit/xpcshell.ini @@ -0,0 +1,9 @@ +[DEFAULT] +head = head.js +firefox-appdir = browser +skip-if = toolkit == 'android' + + +[test_DownloadsCommon_getMimeInfo.js] +[test_DownloadsCommon_isFileOfType.js] +[test_DownloadsViewableInternally.js] |