diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /browser/components/downloads/test | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'browser/components/downloads/test')
17 files changed, 1219 insertions, 0 deletions
diff --git a/browser/components/downloads/test/browser/.eslintrc.js b/browser/components/downloads/test/browser/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/components/downloads/test/browser/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/components/downloads/test/browser/browser.ini b/browser/components/downloads/test/browser/browser.ini new file mode 100644 index 000000000..76f026c78 --- /dev/null +++ b/browser/components/downloads/test/browser/browser.ini @@ -0,0 +1,15 @@ +[DEFAULT] +support-files = head.js + +[browser_basic_functionality.js] +[browser_first_download_panel.js] +skip-if = os == "linux" # Bug 949434 +[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] +[browser_downloads_panel_block.js] +[browser_downloads_panel_footer.js] +[browser_downloads_panel_height.js] 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 000000000..564a344a7 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_basic_functionality.js @@ -0,0 +1,56 @@ +/* -*- 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(function*() { + yield task_resetState(); +}); + +/** + * Make sure the downloads panel can display items in the right order and + * contains the expected data. + */ +add_task(function* test_basic_functionality() { + // Display one of each download state. + const DownloadData = [ + { state: nsIDM.DOWNLOAD_NOTSTARTED }, + { state: nsIDM.DOWNLOAD_PAUSED }, + { state: nsIDM.DOWNLOAD_FINISHED }, + { state: nsIDM.DOWNLOAD_FAILED }, + { state: nsIDM.DOWNLOAD_CANCELED }, + ]; + + // Wait for focus first + yield promiseFocus(); + + // Ensure that state is reset in case previous tests didn't finish. + yield 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. + yield task_addDownloads(DownloadData); + + // Open the user interface and wait for data to be fully loaded. + yield 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.children.length, DownloadData.length, + "There is the correct number of richlistitems"); + */ + let itemCount = richlistbox.children.length; + for (let i = 0; i < itemCount; i++) { + let element = richlistbox.children[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 000000000..8ba37ba64 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_confirm_unblock_download.js @@ -0,0 +1,92 @@ +/* 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(() => {}); + +function* assertDialogResult({ args, buttonToClick, expectedResult }) { + promiseAlertDialogOpen(buttonToClick); + is(yield DownloadsCommon.confirmUnblockDownload(args), expectedResult); +} + +/** + * Tests the "unblock" dialog, for each of the possible verdicts. + */ +add_task(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. + yield assertDialogResult({ + args, + buttonToClick: "accept", + expectedResult: "unblock", + }); + yield assertDialogResult({ + args, + buttonToClick: "cancel", + expectedResult: "cancel", + }); + } +}); + +/** + * Tests the "chooseUnblock" dialog for potentially unwanted downloads. + */ +add_task(function* test_chooseUnblock_dialog() { + let args = { + verdict: Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED, + window, + dialogType: "chooseUnblock", + }; + + // Test each of the three buttons. + yield assertDialogResult({ + args, + buttonToClick: "accept", + expectedResult: "unblock", + }); + yield assertDialogResult({ + args, + buttonToClick: "cancel", + expectedResult: "cancel", + }); + yield assertDialogResult({ + args, + buttonToClick: "extra1", + expectedResult: "confirmBlock", + }); +}); + +/** + * Tests the "chooseOpen" dialog for uncommon downloads. + */ +add_task(function* test_chooseOpen_dialog() { + let args = { + verdict: Downloads.Error.BLOCK_VERDICT_UNCOMMON, + window, + dialogType: "chooseOpen", + }; + + // Test each of the three buttons. + yield assertDialogResult({ + args, + buttonToClick: "accept", + expectedResult: "open", + }); + yield assertDialogResult({ + args, + buttonToClick: "cancel", + expectedResult: "cancel", + }); + yield assertDialogResult({ + args, + buttonToClick: "extra1", + expectedResult: "confirmBlock", + }); +}); 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 000000000..05056e842 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_panel_block.js @@ -0,0 +1,183 @@ +"use strict"; + +add_task(function* mainTest() { + yield task_resetState(); + + let verdicts = [ + Downloads.Error.BLOCK_VERDICT_UNCOMMON, + Downloads.Error.BLOCK_VERDICT_MALWARE, + Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED, + ]; + yield task_addDownloads(verdicts.map(v => makeDownload(v))); + + // Check that the richlistitem for each download is correct. + for (let i = 0; i < verdicts.length; i++) { + yield 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.firstChild; + + // Open the panel and click the item to show the subview. + EventUtils.sendMouseEvent({ type: "click" }, item); + yield promiseSubviewShown(true); + + // 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]); + + // Click the sliver of the main view that's still showing on the left to go + // back to it. + EventUtils.synthesizeMouse(DownloadsPanel.panel, 10, 10, {}, window); + yield promiseSubviewShown(false); + + // Show the subview again. + EventUtils.sendMouseEvent({ type: "click" }, item); + yield promiseSubviewShown(true); + + // 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.openButton, + 10, 10, {}, window); + yield unblockOpenPromise; + yield hidePromise; + + window.focus(); + yield SimpleTest.promiseFocus(window); + + // Reopen the panel and show the subview again. + yield openPanel(); + + EventUtils.sendMouseEvent({ type: "click" }, item); + yield promiseSubviewShown(true); + + // Click the Remove button. The panel should close and the item should be + // removed from it. + EventUtils.synthesizeMouse(DownloadsBlockedSubview.elements.deleteButton, + 10, 10, {}, window); + yield promisePanelHidden(); + yield openPanel(); + + Assert.ok(!item.parentNode); + DownloadsPanel.hidePanel(); + yield promisePanelHidden(); + } + + yield task_resetState(); +}); + +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. + + yield promiseFocus(); + yield 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 new Promise(resolve => { + if (!DownloadsPanel.panel || DownloadsPanel.panel.state == "closed") { + resolve(); + return; + } + DownloadsPanel.panel.addEventListener("popuphidden", function onHidden() { + DownloadsPanel.panel.removeEventListener("popuphidden", onHidden); + setTimeout(resolve, 0); + }); + }); +} + +function makeDownload(verdict) { + return { + state: nsIDM.DOWNLOAD_DIRTY, + hasBlockedData: true, + errorObj: { + result: Components.results.NS_ERROR_FAILURE, + message: "Download blocked.", + becauseBlocked: true, + becauseBlockedByReputationCheck: true, + reputationCheckVerdict: verdict, + }, + }; +} + +function promiseSubviewShown(shown) { + // More terribleness, but I'm tired of fighting intermittent timeouts on try. + // Just poll for the subview and wait a second before resolving the promise. + return new Promise(resolve => { + let interval = setInterval(() => { + if (shown == DownloadsBlockedSubview.view.showingSubView && + !DownloadsBlockedSubview.view._transitioning) { + clearInterval(interval); + setTimeout(resolve, 1000); + return; + } + }, 0); + }); +} + +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_footer.js b/browser/components/downloads/test/browser/browser_downloads_panel_footer.js new file mode 100644 index 000000000..4083dde98 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_panel_footer.js @@ -0,0 +1,95 @@ +"use strict"; + +function *task_openDownloadsSubPanel() { + let downloadSubPanel = document.getElementById("downloadSubPanel"); + let popupShownPromise = BrowserTestUtils.waitForEvent(downloadSubPanel, "popupshown"); + + let downloadsDropmarker = document.getElementById("downloadsFooterDropmarker"); + EventUtils.synthesizeMouseAtCenter(downloadsDropmarker, {}, window); + + yield popupShownPromise; +} + +add_task(function* test_openDownloadsFolder() { + yield SpecialPowers.pushPrefEnv({"set": [["browser.download.showPanelDropmarker", true]]}); + yield task_openPanel(); + + yield task_openDownloadsSubPanel(); + + yield new Promise(resolve => { + sinon.stub(DownloadsCommon, "showDirectory", file => { + resolve(Downloads.getPreferredDownloadsDirectory().then(downloadsPath => { + is(file.path, downloadsPath, "Check the download folder path."); + })); + }); + + let itemOpenDownloadsFolder = + document.getElementById("downloadsDropdownItemOpenDownloadsFolder"); + EventUtils.synthesizeMouseAtCenter(itemOpenDownloadsFolder, {}, window); + }); + + yield task_resetState(); +}); + +add_task(function* test_clearList() { + const kTestCases = [{ + downloads: [ + { state: nsIDM.DOWNLOAD_NOTSTARTED }, + { state: nsIDM.DOWNLOAD_FINISHED }, + { state: nsIDM.DOWNLOAD_FAILED }, + { state: nsIDM.DOWNLOAD_CANCELED }, + ], + expectClearListShown: true, + expectedItemNumber: 0, + },{ + downloads: [ + { state: nsIDM.DOWNLOAD_NOTSTARTED }, + { state: nsIDM.DOWNLOAD_FINISHED }, + { state: nsIDM.DOWNLOAD_FAILED }, + { state: nsIDM.DOWNLOAD_PAUSED }, + { state: nsIDM.DOWNLOAD_CANCELED }, + ], + expectClearListShown: true, + expectedItemNumber: 1, + },{ + downloads: [ + { state: nsIDM.DOWNLOAD_PAUSED }, + ], + expectClearListShown: false, + expectedItemNumber: 1, + }]; + + for (let testCase of kTestCases) { + yield verify_clearList(testCase); + } +}); + +function *verify_clearList(testCase) { + let downloads = testCase.downloads; + yield task_addDownloads(downloads); + + yield task_openPanel(); + is(DownloadsView._downloads.length, downloads.length, + "Expect the number of download items"); + + yield task_openDownloadsSubPanel(); + + let itemClearList = document.getElementById("downloadsDropdownItemClearList"); + let itemNumberPromise = BrowserTestUtils.waitForCondition(() => { + return DownloadsView._downloads.length === testCase.expectedItemNumber; + }); + if (testCase.expectClearListShown) { + isnot("true", itemClearList.getAttribute("hidden"), + "Should show Clear Preview Panel button"); + EventUtils.synthesizeMouseAtCenter(itemClearList, {}, window); + } else { + is("true", itemClearList.getAttribute("hidden"), + "Should not show Clear Preview Panel button"); + } + + yield itemNumberPromise; + is(DownloadsView._downloads.length, testCase.expectedItemNumber, + "Download items remained."); + + yield task_resetState(); +} 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 000000000..1638e4f0e --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_panel_height.js @@ -0,0 +1,29 @@ +/* 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(function* test_height_reduced_after_removal() { + yield task_addDownloads([ + { state: nsIDM.DOWNLOAD_FINISHED }, + ]); + + yield 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(); + yield task_resetState(); + + yield task_openPanel(); + let heightAfterRemoval = panel.getBoundingClientRect().height; + Assert.greater(heightBeforeRemoval, heightAfterRemoval); + + yield task_resetState(); +}); 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 000000000..2cd871360 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_first_download_panel.js @@ -0,0 +1,57 @@ +/* -*- 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/ */ + +/** + * 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(function* test_first_download_panel() { + // 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(function*() { + // Clean up when the test finishes. + yield 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. + yield 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; + + let promise = promisePanelOpened(); + DownloadsCommon.getData(window)._notifyDownloadEvent("start"); + yield 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. + yield new Promise(resolve => setTimeout(resolve, 2000)); + DownloadsPanel.onPopupShown = originalOnPopupShown; +}); 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 000000000..ebdd4f9af --- /dev/null +++ b/browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js @@ -0,0 +1,62 @@ +const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite"; + +function test_deleted_iframe(perSitePref, windowOptions={}) { + return function*() { + Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, perSitePref); + let {DownloadLastDir} = Cu.import("resource://gre/modules/DownloadLastDir.jsm", {}); + + let win = yield promiseOpenAndLoadWindow(windowOptions); + let tab = win.gBrowser.addTab(); + yield promiseTabLoadEvent(tab, "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", false); + }); + iframe.remove(); + yield promiseIframeWindowGone; + cw = null; + ok(!iframe.contentWindow, "Managed to destroy iframe"); + + let someDir = "blah"; + try { + someDir = yield 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); + } + + yield promiseWindowClosed(win); + Services.prefs.clearUserPref(SAVE_PER_SITE_PREF); + }; +} + +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_indicatorDrop.js b/browser/components/downloads/test/browser/browser_indicatorDrop.js new file mode 100644 index 000000000..368d85ccf --- /dev/null +++ b/browser/components/downloads/test/browser/browser_indicatorDrop.js @@ -0,0 +1,67 @@ +/* -*- 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/ */ + +XPCOMUtils.defineLazyModuleGetter(this, "HttpServer", + "resource://testing-common/httpd.js"); + +registerCleanupFunction(function*() { + yield task_resetState(); + yield task_clearHistory(); +}); + +add_task(function* test_indicatorDrop() { + let downloadButton = document.getElementById("downloads-button"); + ok(downloadButton, "download button present"); + + let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + let EventUtils = {}; + scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + + function* task_drop(urls) { + let dragData = [[{type: "text/plain", data: urls.join("\n")}]]; + + let list = yield Downloads.getList(Downloads.ALL); + + let added = new Set(); + let succeeded = new Set(); + yield new Promise(function(resolve) { + let view = { + onDownloadAdded: function(download) { + added.add(download.source.url); + }, + onDownloadChanged: function(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(downloadButton, downloadButton, dragData, "link", window); + }); + }); + + for (let url of urls) { + ok(added.has(url), url + " is added to download"); + } + } + + // Ensure that state is reset in case previous tests didn't finish. + yield task_resetState(); + + yield setDownloadDir(); + + startServer(); + + yield* task_drop([httpUrl("file1.txt")]); + yield* task_drop([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 000000000..fa7df8a87 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_libraryDrop.js @@ -0,0 +1,72 @@ +/* -*- 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/ */ + +XPCOMUtils.defineLazyModuleGetter(this, "HttpServer", + "resource://testing-common/httpd.js"); + +registerCleanupFunction(function*() { + yield task_resetState(); + yield task_clearHistory(); +}); + +add_task(function* test_indicatorDrop() { + let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + let EventUtils = {}; + scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + + function task_drop(win, urls) { + let dragData = [[{type: "text/plain", data: urls.join("\n")}]]; + + let listBox = win.document.getElementById("downloadsRichListBox"); + ok(listBox, "download list box present"); + + let list = yield Downloads.getList(Downloads.ALL); + + let added = new Set(); + let succeeded = new Set(); + yield new Promise(function(resolve) { + let view = { + onDownloadAdded: function(download) { + added.add(download.source.url); + }, + onDownloadChanged: function(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(listBox, listBox, dragData, "link", win); + }); + }); + + for (let url of urls) { + ok(added.has(url), url + " is added to download"); + } + } + + // Ensure that state is reset in case previous tests didn't finish. + yield task_resetState(); + + setDownloadDir(); + + startServer(); + + let win = yield openLibrary("Downloads"); + registerCleanupFunction(function() { + win.close(); + }); + + yield* task_drop(win, [httpUrl("file1.txt")]); + yield* task_drop(win, [httpUrl("file1.txt"), + httpUrl("file2.txt"), + httpUrl("file3.txt")]); +}); 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 000000000..a293a81cf --- /dev/null +++ b/browser/components/downloads/test/browser/browser_overflow_anchor.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +registerCleanupFunction(function*() { + // Clean up when the test finishes. + yield 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. + */ +add_task(function* test_overflow_anchor() { + // Ensure that state is reset in case previous tests didn't finish. + yield task_resetState(); + + // Record the original width of the window so we can put it back when + // this test finishes. + let oldWidth = window.outerWidth; + + // 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."); + + // Hack - we lock the size of the default flex-y items in the nav-bar, + // namely, the URL and search inputs. That way we can resize the + // window without worrying about them flexing. + const kFlexyItems = ["urlbar-container", "search-container"]; + registerCleanupFunction(() => unlockWidth(kFlexyItems)); + lockWidth(kFlexyItems); + + // Resize the window to half of its original size. That should + // be enough to overflow the downloads button. + window.resizeTo(oldWidth / 2, window.outerHeight); + yield waitForOverflowed(button, true); + + let promise = promisePanelOpened(); + button.node.doCommand(); + yield promise; + + let panel = DownloadsPanel.panel; + let chevron = document.getElementById("nav-bar-overflow-button"); + is(panel.anchorNode, chevron, "Panel should be anchored to the chevron."); + + DownloadsPanel.hidePanel(); + + // Unlock the widths on the flex-y items. + unlockWidth(kFlexyItems); + + // Put the window back to its original dimensions. + window.resizeTo(oldWidth, window.outerHeight); + + // The downloads button should eventually be un-overflowed. + yield waitForOverflowed(button, false); + + // Now try opening the panel again. + promise = promisePanelOpened(); + button.node.doCommand(); + yield promise; + + is(panel.anchorNode.id, "downloads-indicator-anchor"); + + DownloadsPanel.hidePanel(); +}); + +/** + * For some node IDs, finds the nodes and sets their min-width's to their + * current width, preventing them from flex-shrinking. + * + * @param aItemIDs an array of item IDs to set min-width on. + */ +function lockWidth(aItemIDs) { + for (let itemID of aItemIDs) { + let item = document.getElementById(itemID); + let curWidth = item.getBoundingClientRect().width + "px"; + item.style.minWidth = curWidth; + } +} + +/** + * Clears the min-width's set on a set of IDs by lockWidth. + * + * @param aItemIDs an array of ItemIDs to remove min-width on. + */ +function unlockWidth(aItemIDs) { + for (let itemID of aItemIDs) { + let item = document.getElementById(itemID); + item.style.minWidth = ""; + } +} + +/** + * Waits for a node to enter or exit the overflowed state. + * + * @param aItem the node to wait for. + * @param aIsOverflowed if we're waiting for the item to be overflowed. + */ +function waitForOverflowed(aItem, aIsOverflowed) { + let deferOverflow = Promise.defer(); + if (aItem.overflowed == aIsOverflowed) { + return deferOverflow.resolve(); + } + + let observer = new MutationObserver(function(aMutations) { + if (aItem.overflowed == aIsOverflowed) { + observer.disconnect(); + deferOverflow.resolve(); + } + }); + observer.observe(aItem.node, {attributes: true}); + + return deferOverflow.promise; +} diff --git a/browser/components/downloads/test/browser/head.js b/browser/components/downloads/test/browser/head.js new file mode 100644 index 000000000..bcf703eb6 --- /dev/null +++ b/browser/components/downloads/test/browser/head.js @@ -0,0 +1,300 @@ +/* -*- 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 + +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", + "resource:///modules/DownloadsCommon.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm"); +const nsIDM = Ci.nsIDownloadManager; + +var gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]); +gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); + +// Load mocking/stubbing library, sinon +// docs: http://sinonjs.org/docs/ +Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js"); + +registerCleanupFunction(function () { + gTestTargetFile.remove(false); + + delete window.sinon; + delete window.setImmediate; + delete window.clearImmediate; +}); + +//////////////////////////////////////////////////////////////////////////////// +//// Asynchronous support subroutines + +function promiseOpenAndLoadWindow(aOptions) +{ + return new Promise((resolve, reject) => { + let win = OpenBrowserWindow(aOptions); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad); + resolve(win); + }); + }); +} + +/** + * Waits for a load (or custom) event to finish in a given tab. If provided + * load an uri into the tab. + * + * @param tab + * The tab to load into. + * @param [optional] url + * The url to load, or the current url. + * @param [optional] event + * The load event type to wait for. Defaults to "load". + * @return {Promise} resolved when the event is handled. + * @resolves to the received event + * @rejects if a valid load event is not received within a meaningful interval + */ +function promiseTabLoadEvent(tab, url, eventType="load") +{ + let deferred = Promise.defer(); + info("Wait tab event: " + eventType); + + function handle(event) { + if (event.originalTarget != tab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank" || + (url && event.target.location.href != url)) { + info("Skipping spurious '" + eventType + "'' event" + + " for " + event.target.location.href); + return; + } + // Remove reference to tab from the cleanup function: + realCleanup = () => {}; + tab.linkedBrowser.removeEventListener(eventType, handle, true); + info("Tab event received: " + eventType); + deferred.resolve(event); + } + + // Juggle a bit to avoid leaks: + let realCleanup = () => tab.linkedBrowser.removeEventListener(eventType, handle, true); + registerCleanupFunction(() => realCleanup()); + + tab.linkedBrowser.addEventListener(eventType, handle, true, true); + if (url) + tab.linkedBrowser.loadURI(url); + return deferred.promise; +} + +function promiseWindowClosed(win) +{ + let promise = new Promise((resolve, reject) => { + Services.obs.addObserver(function obs(subject, topic) { + if (subject == win) { + Services.obs.removeObserver(obs, topic); + resolve(); + } + }, "domwindowclosed", false); + }); + win.close(); + return promise; +} + + +function promiseFocus() +{ + let deferred = Promise.defer(); + waitForFocus(deferred.resolve); + return deferred.promise; +} + +function promisePanelOpened() +{ + let deferred = Promise.defer(); + + if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") { + return deferred.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(deferred.resolve, 0); + }; + + return deferred.promise; +} + +function* task_resetState() +{ + // Remove all downloads. + let publicList = yield Downloads.getList(Downloads.PUBLIC); + let downloads = yield publicList.getAll(); + for (let download of downloads) { + publicList.remove(download); + yield download.finalize(true); + } + + DownloadsPanel.hidePanel(); + + yield promiseFocus(); +} + +function* task_addDownloads(aItems) +{ + let startTimeMs = Date.now(); + + let publicList = yield Downloads.getList(Downloads.PUBLIC); + for (let item of aItems) { + let download = { + source: { + url: "http://www.example.com/test-download.txt", + }, + target: { + path: gTestTargetFile.path, + }, + succeeded: item.state == nsIDM.DOWNLOAD_FINISHED, + canceled: item.state == nsIDM.DOWNLOAD_CANCELED || + item.state == nsIDM.DOWNLOAD_PAUSED, + error: item.state == nsIDM.DOWNLOAD_FAILED ? new Error("Failed.") : null, + hasPartialData: item.state == nsIDM.DOWNLOAD_PAUSED, + hasBlockedData: item.hasBlockedData || false, + startTime: new Date(startTimeMs++), + }; + // `"errorObj" in download` must be false when there's no error. + if (item.errorObj) { + download.errorObj = item.errorObj; + } + yield publicList.add(yield Downloads.createDownload(download)); + } +} + +function* task_openPanel() +{ + yield promiseFocus(); + + let promise = promisePanelOpened(); + DownloadsPanel.showPanel(); + yield promise; +} + +function* setDownloadDir() { + let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + tmpDir.append("testsavedir"); + if (!tmpDir.exists()) { + tmpDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + registerCleanupFunction(function () { + try { + tmpDir.remove(true); + } catch (e) { + // On Windows debug build this may fail. + } + }); + } + + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [ + ["browser.download.folderList", 2], + ["browser.download.dir", tmpDir, Ci.nsIFile], + ]}, resolve); + }); +} + + +let gHttpServer = null; +function startServer() { + gHttpServer = new HttpServer(); + gHttpServer.start(-1); + registerCleanupFunction(function*() { + yield 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 task_clearHistory() { + return new Promise(function(resolve) { + Services.obs.addObserver(function observeCH(aSubject, aTopic, aData) { + Services.obs.removeObserver(observeCH, PlacesUtils.TOPIC_EXPIRATION_FINISHED); + resolve(); + }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false); + PlacesUtils.history.clear(); + }); +} + +function openLibrary(aLeftPaneRoot) { + let library = window.openDialog("chrome://browser/content/places/places.xul", + "", "chrome,toolbar=yes,dialog=no,resizable", + aLeftPaneRoot); + + return new Promise(resolve => { + waitForFocus(resolve, library); + }); +} + +function promiseAlertDialogOpen(buttonAction) { + return new Promise(resolve => { + Services.ww.registerNotification(function onOpen(subj, topic, data) { + if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) { + // The test listens for the "load" event which guarantees that the alert + // class has already been added (it is added when "DOMContentLoaded" is + // fired). + subj.addEventListener("load", function onLoad() { + subj.removeEventListener("load", onLoad); + if (subj.document.documentURI == + "chrome://global/content/commonDialog.xul") { + Services.ww.unregisterNotification(onOpen); + + let dialog = subj.document.getElementById("commonDialog"); + ok(dialog.classList.contains("alert-dialog"), + "The dialog element should contain an alert class."); + + let doc = subj.document.documentElement; + doc.getButton(buttonAction).click(); + resolve(); + } + }); + } + }); + }); +} diff --git a/browser/components/downloads/test/unit/.eslintrc.js b/browser/components/downloads/test/unit/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/browser/components/downloads/test/unit/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/browser/components/downloads/test/unit/head.js b/browser/components/downloads/test/unit/head.js new file mode 100644 index 000000000..d7ce4d48a --- /dev/null +++ b/browser/components/downloads/test/unit/head.js @@ -0,0 +1,18 @@ +/* -*- 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 + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource:///modules/DownloadsCommon.jsm"); diff --git a/browser/components/downloads/test/unit/test_DownloadsCommon.js b/browser/components/downloads/test/unit/test_DownloadsCommon.js new file mode 100644 index 000000000..46afbaef9 --- /dev/null +++ b/browser/components/downloads/test/unit/test_DownloadsCommon.js @@ -0,0 +1,37 @@ +/* -*- 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/ */ + +/** + * Tests for the functions located directly in the "DownloadsCommon" object. + */ + +function testFormatTimeLeft(aSeconds, aExpectedValue, aExpectedUnitString) +{ + let expected = ""; + if (aExpectedValue) { + // Format the expected result based on the current language. + expected = DownloadsCommon.strings[aExpectedUnitString](aExpectedValue); + } + do_check_eq(DownloadsCommon.formatTimeLeft(aSeconds), expected); +} + +function run_test() +{ + testFormatTimeLeft( 0, "", ""); + testFormatTimeLeft( 1, "1", "shortTimeLeftSeconds"); + testFormatTimeLeft( 29, "29", "shortTimeLeftSeconds"); + testFormatTimeLeft( 30, "30", "shortTimeLeftSeconds"); + testFormatTimeLeft( 31, "1", "shortTimeLeftMinutes"); + testFormatTimeLeft( 60, "1", "shortTimeLeftMinutes"); + testFormatTimeLeft( 89, "1", "shortTimeLeftMinutes"); + testFormatTimeLeft( 90, "2", "shortTimeLeftMinutes"); + testFormatTimeLeft( 91, "2", "shortTimeLeftMinutes"); + testFormatTimeLeft( 3600, "1", "shortTimeLeftHours"); + testFormatTimeLeft( 86400, "24", "shortTimeLeftHours"); + testFormatTimeLeft( 169200, "47", "shortTimeLeftHours"); + testFormatTimeLeft( 172800, "2", "shortTimeLeftDays"); + testFormatTimeLeft(8553600, "99", "shortTimeLeftDays"); + testFormatTimeLeft(8640000, "99", "shortTimeLeftDays"); +} diff --git a/browser/components/downloads/test/unit/xpcshell.ini b/browser/components/downloads/test/unit/xpcshell.ini new file mode 100644 index 000000000..f53a8cf89 --- /dev/null +++ b/browser/components/downloads/test/unit/xpcshell.ini @@ -0,0 +1,7 @@ +[DEFAULT] +head = head.js +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' + +[test_DownloadsCommon.js] |