michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: /** michael@0: * Tests the DownloadList object. michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Globals michael@0: michael@0: /** michael@0: * Returns a PRTime in the past usable to add expirable visits. michael@0: * michael@0: * @note Expiration ignores any visit added in the last 7 days, but it's michael@0: * better be safe against DST issues, by going back one day more. michael@0: */ michael@0: function getExpirablePRTime() michael@0: { michael@0: let dateObj = new Date(); michael@0: // Normalize to midnight michael@0: dateObj.setHours(0); michael@0: dateObj.setMinutes(0); michael@0: dateObj.setSeconds(0); michael@0: dateObj.setMilliseconds(0); michael@0: dateObj = new Date(dateObj.getTime() - 8 * 86400000); michael@0: return dateObj.getTime() * 1000; michael@0: } michael@0: michael@0: /** michael@0: * Adds an expirable history visit for a download. michael@0: * michael@0: * @param aSourceUrl michael@0: * String containing the URI for the download source, or null to use michael@0: * httpUrl("source.txt"). michael@0: * michael@0: * @return {Promise} michael@0: * @rejects JavaScript exception. michael@0: */ michael@0: function promiseExpirableDownloadVisit(aSourceUrl) michael@0: { michael@0: let deferred = Promise.defer(); michael@0: PlacesUtils.asyncHistory.updatePlaces( michael@0: { michael@0: uri: NetUtil.newURI(aSourceUrl || httpUrl("source.txt")), michael@0: visits: [{ michael@0: transitionType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD, michael@0: visitDate: getExpirablePRTime(), michael@0: }] michael@0: }, michael@0: { michael@0: handleError: function handleError(aResultCode, aPlaceInfo) { michael@0: let ex = new Components.Exception("Unexpected error in adding visits.", michael@0: aResultCode); michael@0: deferred.reject(ex); michael@0: }, michael@0: handleResult: function () {}, michael@0: handleCompletion: function handleCompletion() { michael@0: deferred.resolve(); michael@0: } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Tests michael@0: michael@0: /** michael@0: * Checks the testing mechanism used to build different download lists. michael@0: */ michael@0: add_task(function test_construction() michael@0: { michael@0: let downloadListOne = yield promiseNewList(); michael@0: let downloadListTwo = yield promiseNewList(); michael@0: let privateDownloadListOne = yield promiseNewList(true); michael@0: let privateDownloadListTwo = yield promiseNewList(true); michael@0: michael@0: do_check_neq(downloadListOne, downloadListTwo); michael@0: do_check_neq(privateDownloadListOne, privateDownloadListTwo); michael@0: do_check_neq(downloadListOne, privateDownloadListOne); michael@0: }); michael@0: michael@0: /** michael@0: * Checks the methods to add and retrieve items from the list. michael@0: */ michael@0: add_task(function test_add_getAll() michael@0: { michael@0: let list = yield promiseNewList(); michael@0: michael@0: let downloadOne = yield promiseNewDownload(); michael@0: yield list.add(downloadOne); michael@0: michael@0: let itemsOne = yield list.getAll(); michael@0: do_check_eq(itemsOne.length, 1); michael@0: do_check_eq(itemsOne[0], downloadOne); michael@0: michael@0: let downloadTwo = yield promiseNewDownload(); michael@0: yield list.add(downloadTwo); michael@0: michael@0: let itemsTwo = yield list.getAll(); michael@0: do_check_eq(itemsTwo.length, 2); michael@0: do_check_eq(itemsTwo[0], downloadOne); michael@0: do_check_eq(itemsTwo[1], downloadTwo); michael@0: michael@0: // The first snapshot should not have been modified. michael@0: do_check_eq(itemsOne.length, 1); michael@0: }); michael@0: michael@0: /** michael@0: * Checks the method to remove items from the list. michael@0: */ michael@0: add_task(function test_remove() michael@0: { michael@0: let list = yield promiseNewList(); michael@0: michael@0: yield list.add(yield promiseNewDownload()); michael@0: yield list.add(yield promiseNewDownload()); michael@0: michael@0: let items = yield list.getAll(); michael@0: yield list.remove(items[0]); michael@0: michael@0: // Removing an item that was never added should not raise an error. michael@0: yield list.remove(yield promiseNewDownload()); michael@0: michael@0: items = yield list.getAll(); michael@0: do_check_eq(items.length, 1); michael@0: }); michael@0: michael@0: /** michael@0: * Tests that the "add", "remove", and "getAll" methods on the global michael@0: * DownloadCombinedList object combine the contents of the global DownloadList michael@0: * objects for public and private downloads. michael@0: */ michael@0: add_task(function test_DownloadCombinedList_add_remove_getAll() michael@0: { michael@0: let publicList = yield promiseNewList(); michael@0: let privateList = yield Downloads.getList(Downloads.PRIVATE); michael@0: let combinedList = yield Downloads.getList(Downloads.ALL); michael@0: michael@0: let publicDownload = yield promiseNewDownload(); michael@0: let privateDownload = yield Downloads.createDownload({ michael@0: source: { url: httpUrl("source.txt"), isPrivate: true }, michael@0: target: getTempFile(TEST_TARGET_FILE_NAME).path, michael@0: }); michael@0: michael@0: yield publicList.add(publicDownload); michael@0: yield privateList.add(privateDownload); michael@0: michael@0: do_check_eq((yield combinedList.getAll()).length, 2); michael@0: michael@0: yield combinedList.remove(publicDownload); michael@0: yield combinedList.remove(privateDownload); michael@0: michael@0: do_check_eq((yield combinedList.getAll()).length, 0); michael@0: michael@0: yield combinedList.add(publicDownload); michael@0: yield combinedList.add(privateDownload); michael@0: michael@0: do_check_eq((yield publicList.getAll()).length, 1); michael@0: do_check_eq((yield privateList.getAll()).length, 1); michael@0: do_check_eq((yield combinedList.getAll()).length, 2); michael@0: michael@0: yield publicList.remove(publicDownload); michael@0: yield privateList.remove(privateDownload); michael@0: michael@0: do_check_eq((yield combinedList.getAll()).length, 0); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that views receive the download add and remove notifications, and that michael@0: * adding and removing views works as expected, both for a normal and a combined michael@0: * list. michael@0: */ michael@0: add_task(function test_notifications_add_remove() michael@0: { michael@0: for (let isCombined of [false, true]) { michael@0: // Force creating a new list for both the public and combined cases. michael@0: let list = yield promiseNewList(); michael@0: if (isCombined) { michael@0: list = yield Downloads.getList(Downloads.ALL); michael@0: } michael@0: michael@0: let downloadOne = yield promiseNewDownload(); michael@0: let downloadTwo = yield Downloads.createDownload({ michael@0: source: { url: httpUrl("source.txt"), isPrivate: true }, michael@0: target: getTempFile(TEST_TARGET_FILE_NAME).path, michael@0: }); michael@0: yield list.add(downloadOne); michael@0: yield list.add(downloadTwo); michael@0: michael@0: // Check that we receive add notifications for existing elements. michael@0: let addNotifications = 0; michael@0: let viewOne = { michael@0: onDownloadAdded: function (aDownload) { michael@0: // The first download to be notified should be the first that was added. michael@0: if (addNotifications == 0) { michael@0: do_check_eq(aDownload, downloadOne); michael@0: } else if (addNotifications == 1) { michael@0: do_check_eq(aDownload, downloadTwo); michael@0: } michael@0: addNotifications++; michael@0: }, michael@0: }; michael@0: yield list.addView(viewOne); michael@0: do_check_eq(addNotifications, 2); michael@0: michael@0: // Check that we receive add notifications for new elements. michael@0: yield list.add(yield promiseNewDownload()); michael@0: do_check_eq(addNotifications, 3); michael@0: michael@0: // Check that we receive remove notifications. michael@0: let removeNotifications = 0; michael@0: let viewTwo = { michael@0: onDownloadRemoved: function (aDownload) { michael@0: do_check_eq(aDownload, downloadOne); michael@0: removeNotifications++; michael@0: }, michael@0: }; michael@0: yield list.addView(viewTwo); michael@0: yield list.remove(downloadOne); michael@0: do_check_eq(removeNotifications, 1); michael@0: michael@0: // We should not receive remove notifications after the view is removed. michael@0: yield list.removeView(viewTwo); michael@0: yield list.remove(downloadTwo); michael@0: do_check_eq(removeNotifications, 1); michael@0: michael@0: // We should not receive add notifications after the view is removed. michael@0: yield list.removeView(viewOne); michael@0: yield list.add(yield promiseNewDownload()); michael@0: do_check_eq(addNotifications, 3); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Checks that views receive the download change notifications, both for a michael@0: * normal and a combined list. michael@0: */ michael@0: add_task(function test_notifications_change() michael@0: { michael@0: for (let isCombined of [false, true]) { michael@0: // Force creating a new list for both the public and combined cases. michael@0: let list = yield promiseNewList(); michael@0: if (isCombined) { michael@0: list = yield Downloads.getList(Downloads.ALL); michael@0: } michael@0: michael@0: let downloadOne = yield promiseNewDownload(); michael@0: let downloadTwo = yield Downloads.createDownload({ michael@0: source: { url: httpUrl("source.txt"), isPrivate: true }, michael@0: target: getTempFile(TEST_TARGET_FILE_NAME).path, michael@0: }); michael@0: yield list.add(downloadOne); michael@0: yield list.add(downloadTwo); michael@0: michael@0: // Check that we receive change notifications. michael@0: let receivedOnDownloadChanged = false; michael@0: yield list.addView({ michael@0: onDownloadChanged: function (aDownload) { michael@0: do_check_eq(aDownload, downloadOne); michael@0: receivedOnDownloadChanged = true; michael@0: }, michael@0: }); michael@0: yield downloadOne.start(); michael@0: do_check_true(receivedOnDownloadChanged); michael@0: michael@0: // We should not receive change notifications after a download is removed. michael@0: receivedOnDownloadChanged = false; michael@0: yield list.remove(downloadTwo); michael@0: yield downloadTwo.start(); michael@0: do_check_false(receivedOnDownloadChanged); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Checks that the reference to "this" is correct in the view callbacks. michael@0: */ michael@0: add_task(function test_notifications_this() michael@0: { michael@0: let list = yield promiseNewList(); michael@0: michael@0: // Check that we receive change notifications. michael@0: let receivedOnDownloadAdded = false; michael@0: let receivedOnDownloadChanged = false; michael@0: let receivedOnDownloadRemoved = false; michael@0: let view = { michael@0: onDownloadAdded: function () { michael@0: do_check_eq(this, view); michael@0: receivedOnDownloadAdded = true; michael@0: }, michael@0: onDownloadChanged: function () { michael@0: // Only do this check once. michael@0: if (!receivedOnDownloadChanged) { michael@0: do_check_eq(this, view); michael@0: receivedOnDownloadChanged = true; michael@0: } michael@0: }, michael@0: onDownloadRemoved: function () { michael@0: do_check_eq(this, view); michael@0: receivedOnDownloadRemoved = true; michael@0: }, michael@0: }; michael@0: yield list.addView(view); michael@0: michael@0: let download = yield promiseNewDownload(); michael@0: yield list.add(download); michael@0: yield download.start(); michael@0: yield list.remove(download); michael@0: michael@0: // Verify that we executed the checks. michael@0: do_check_true(receivedOnDownloadAdded); michael@0: do_check_true(receivedOnDownloadChanged); michael@0: do_check_true(receivedOnDownloadRemoved); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that download is removed on history expiration. michael@0: */ michael@0: add_task(function test_history_expiration() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: function cleanup() { michael@0: Services.prefs.clearUserPref("places.history.expiration.max_pages"); michael@0: } michael@0: do_register_cleanup(cleanup); michael@0: michael@0: // Set max pages to 0 to make the download expire. michael@0: Services.prefs.setIntPref("places.history.expiration.max_pages", 0); michael@0: michael@0: let list = yield promiseNewList(); michael@0: let downloadOne = yield promiseNewDownload(); michael@0: let downloadTwo = yield promiseNewDownload(httpUrl("interruptible.txt")); michael@0: michael@0: let deferred = Promise.defer(); michael@0: let removeNotifications = 0; michael@0: let downloadView = { michael@0: onDownloadRemoved: function (aDownload) { michael@0: if (++removeNotifications == 2) { michael@0: deferred.resolve(); michael@0: } michael@0: }, michael@0: }; michael@0: yield list.addView(downloadView); michael@0: michael@0: // Work with one finished download and one canceled download. michael@0: yield downloadOne.start(); michael@0: downloadTwo.start(); michael@0: yield downloadTwo.cancel(); michael@0: michael@0: // We must replace the visits added while executing the downloads with visits michael@0: // that are older than 7 days, otherwise they will not be expired. michael@0: yield promiseClearHistory(); michael@0: yield promiseExpirableDownloadVisit(); michael@0: yield promiseExpirableDownloadVisit(httpUrl("interruptible.txt")); michael@0: michael@0: // After clearing history, we can add the downloads to be removed to the list. michael@0: yield list.add(downloadOne); michael@0: yield list.add(downloadTwo); michael@0: michael@0: // Force a history expiration. michael@0: Cc["@mozilla.org/places/expiration;1"] michael@0: .getService(Ci.nsIObserver).observe(null, "places-debug-start-expiration", -1); michael@0: michael@0: // Wait for both downloads to be removed. michael@0: yield deferred.promise; michael@0: michael@0: cleanup(); michael@0: }); michael@0: michael@0: /** michael@0: * Checks all downloads are removed after clearing history. michael@0: */ michael@0: add_task(function test_history_clear() michael@0: { michael@0: let list = yield promiseNewList(); michael@0: let downloadOne = yield promiseNewDownload(); michael@0: let downloadTwo = yield promiseNewDownload(); michael@0: yield list.add(downloadOne); michael@0: yield list.add(downloadTwo); michael@0: michael@0: let deferred = Promise.defer(); michael@0: let removeNotifications = 0; michael@0: let downloadView = { michael@0: onDownloadRemoved: function (aDownload) { michael@0: if (++removeNotifications == 2) { michael@0: deferred.resolve(); michael@0: } michael@0: }, michael@0: }; michael@0: yield list.addView(downloadView); michael@0: michael@0: yield downloadOne.start(); michael@0: yield downloadTwo.start(); michael@0: michael@0: yield promiseClearHistory(); michael@0: michael@0: // Wait for the removal notifications that may still be pending. michael@0: yield deferred.promise; michael@0: }); michael@0: michael@0: /** michael@0: * Tests the removeFinished method to ensure that it only removes michael@0: * finished downloads. michael@0: */ michael@0: add_task(function test_removeFinished() michael@0: { michael@0: let list = yield promiseNewList(); michael@0: let downloadOne = yield promiseNewDownload(); michael@0: let downloadTwo = yield promiseNewDownload(); michael@0: let downloadThree = yield promiseNewDownload(); michael@0: let downloadFour = yield promiseNewDownload(); michael@0: yield list.add(downloadOne); michael@0: yield list.add(downloadTwo); michael@0: yield list.add(downloadThree); michael@0: yield list.add(downloadFour); michael@0: michael@0: let deferred = Promise.defer(); michael@0: let removeNotifications = 0; michael@0: let downloadView = { michael@0: onDownloadRemoved: function (aDownload) { michael@0: do_check_true(aDownload == downloadOne || michael@0: aDownload == downloadTwo || michael@0: aDownload == downloadThree); michael@0: do_check_true(removeNotifications < 3); michael@0: if (++removeNotifications == 3) { michael@0: deferred.resolve(); michael@0: } michael@0: }, michael@0: }; michael@0: yield list.addView(downloadView); michael@0: michael@0: // Start three of the downloads, but don't start downloadTwo, then set michael@0: // downloadFour to have partial data. All downloads except downloadFour michael@0: // should be removed. michael@0: yield downloadOne.start(); michael@0: yield downloadThree.start(); michael@0: yield downloadFour.start(); michael@0: downloadFour.hasPartialData = true; michael@0: michael@0: list.removeFinished(); michael@0: yield deferred.promise; michael@0: michael@0: let downloads = yield list.getAll() michael@0: do_check_eq(downloads.length, 1); michael@0: }); michael@0: michael@0: /** michael@0: * Tests the global DownloadSummary objects for the public, private, and michael@0: * combined download lists. michael@0: */ michael@0: add_task(function test_DownloadSummary() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let publicList = yield promiseNewList(); michael@0: let privateList = yield Downloads.getList(Downloads.PRIVATE); michael@0: michael@0: let publicSummary = yield Downloads.getSummary(Downloads.PUBLIC); michael@0: let privateSummary = yield Downloads.getSummary(Downloads.PRIVATE); michael@0: let combinedSummary = yield Downloads.getSummary(Downloads.ALL); michael@0: michael@0: // Add a public download that has succeeded. michael@0: let succeededPublicDownload = yield promiseNewDownload(); michael@0: yield succeededPublicDownload.start(); michael@0: yield publicList.add(succeededPublicDownload); michael@0: michael@0: // Add a public download that has been canceled midway. michael@0: let canceledPublicDownload = michael@0: yield promiseNewDownload(httpUrl("interruptible.txt")); michael@0: canceledPublicDownload.start(); michael@0: yield promiseDownloadMidway(canceledPublicDownload); michael@0: yield canceledPublicDownload.cancel(); michael@0: yield publicList.add(canceledPublicDownload); michael@0: michael@0: // Add a public download that is in progress. michael@0: let inProgressPublicDownload = michael@0: yield promiseNewDownload(httpUrl("interruptible.txt")); michael@0: inProgressPublicDownload.start(); michael@0: yield promiseDownloadMidway(inProgressPublicDownload); michael@0: yield publicList.add(inProgressPublicDownload); michael@0: michael@0: // Add a private download that is in progress. michael@0: let inProgressPrivateDownload = yield Downloads.createDownload({ michael@0: source: { url: httpUrl("interruptible.txt"), isPrivate: true }, michael@0: target: getTempFile(TEST_TARGET_FILE_NAME).path, michael@0: }); michael@0: inProgressPrivateDownload.start(); michael@0: yield promiseDownloadMidway(inProgressPrivateDownload); michael@0: yield privateList.add(inProgressPrivateDownload); michael@0: michael@0: // Verify that the summary includes the total number of bytes and the michael@0: // currently transferred bytes only for the downloads that are not stopped. michael@0: // For simplicity, we assume that after a download is added to the list, its michael@0: // current state is immediately propagated to the summary object, which is michael@0: // true in the current implementation, though it is not guaranteed as all the michael@0: // download operations may happen asynchronously. michael@0: do_check_false(publicSummary.allHaveStopped); michael@0: do_check_eq(publicSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2); michael@0: do_check_eq(publicSummary.progressCurrentBytes, TEST_DATA_SHORT.length); michael@0: michael@0: do_check_false(privateSummary.allHaveStopped); michael@0: do_check_eq(privateSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2); michael@0: do_check_eq(privateSummary.progressCurrentBytes, TEST_DATA_SHORT.length); michael@0: michael@0: do_check_false(combinedSummary.allHaveStopped); michael@0: do_check_eq(combinedSummary.progressTotalBytes, TEST_DATA_SHORT.length * 4); michael@0: do_check_eq(combinedSummary.progressCurrentBytes, TEST_DATA_SHORT.length * 2); michael@0: michael@0: yield inProgressPublicDownload.cancel(); michael@0: michael@0: // Stopping the download should have excluded it from the summary. michael@0: do_check_true(publicSummary.allHaveStopped); michael@0: do_check_eq(publicSummary.progressTotalBytes, 0); michael@0: do_check_eq(publicSummary.progressCurrentBytes, 0); michael@0: michael@0: do_check_false(privateSummary.allHaveStopped); michael@0: do_check_eq(privateSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2); michael@0: do_check_eq(privateSummary.progressCurrentBytes, TEST_DATA_SHORT.length); michael@0: michael@0: do_check_false(combinedSummary.allHaveStopped); michael@0: do_check_eq(combinedSummary.progressTotalBytes, TEST_DATA_SHORT.length * 2); michael@0: do_check_eq(combinedSummary.progressCurrentBytes, TEST_DATA_SHORT.length); michael@0: michael@0: yield inProgressPrivateDownload.cancel(); michael@0: michael@0: // All the downloads should be stopped now. michael@0: do_check_true(publicSummary.allHaveStopped); michael@0: do_check_eq(publicSummary.progressTotalBytes, 0); michael@0: do_check_eq(publicSummary.progressCurrentBytes, 0); michael@0: michael@0: do_check_true(privateSummary.allHaveStopped); michael@0: do_check_eq(privateSummary.progressTotalBytes, 0); michael@0: do_check_eq(privateSummary.progressCurrentBytes, 0); michael@0: michael@0: do_check_true(combinedSummary.allHaveStopped); michael@0: do_check_eq(combinedSummary.progressTotalBytes, 0); michael@0: do_check_eq(combinedSummary.progressCurrentBytes, 0); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that views receive the summary change notification. This is tested on michael@0: * the combined summary when adding a public download, as we assume that if we michael@0: * pass the test in this case we will also pass it in the others. michael@0: */ michael@0: add_task(function test_DownloadSummary_notifications() michael@0: { michael@0: let list = yield promiseNewList(); michael@0: let summary = yield Downloads.getSummary(Downloads.ALL); michael@0: michael@0: let download = yield promiseNewDownload(); michael@0: yield list.add(download); michael@0: michael@0: // Check that we receive change notifications. michael@0: let receivedOnSummaryChanged = false; michael@0: yield summary.addView({ michael@0: onSummaryChanged: function () { michael@0: receivedOnSummaryChanged = true; michael@0: }, michael@0: }); michael@0: yield download.start(); michael@0: do_check_true(receivedOnSummaryChanged); michael@0: }); michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Termination michael@0: michael@0: let tailFile = do_get_file("tail.js"); michael@0: Services.scriptloader.loadSubScript(NetUtil.newURI(tailFile).spec);