summaryrefslogtreecommitdiffstats
path: root/browser/components/downloads/test
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /browser/components/downloads/test
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-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')
-rw-r--r--browser/components/downloads/test/browser/.eslintrc.js7
-rw-r--r--browser/components/downloads/test/browser/browser.ini15
-rw-r--r--browser/components/downloads/test/browser/browser_basic_functionality.js56
-rw-r--r--browser/components/downloads/test/browser/browser_confirm_unblock_download.js92
-rw-r--r--browser/components/downloads/test/browser/browser_downloads_panel_block.js183
-rw-r--r--browser/components/downloads/test/browser/browser_downloads_panel_footer.js95
-rw-r--r--browser/components/downloads/test/browser/browser_downloads_panel_height.js29
-rw-r--r--browser/components/downloads/test/browser/browser_first_download_panel.js57
-rw-r--r--browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js62
-rw-r--r--browser/components/downloads/test/browser/browser_indicatorDrop.js67
-rw-r--r--browser/components/downloads/test/browser/browser_libraryDrop.js72
-rw-r--r--browser/components/downloads/test/browser/browser_overflow_anchor.js115
-rw-r--r--browser/components/downloads/test/browser/head.js300
-rw-r--r--browser/components/downloads/test/unit/.eslintrc.js7
-rw-r--r--browser/components/downloads/test/unit/head.js18
-rw-r--r--browser/components/downloads/test/unit/test_DownloadsCommon.js37
-rw-r--r--browser/components/downloads/test/unit/xpcshell.ini7
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]