Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android'
- Manifest: toolkit/components/downloads/test/unit/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
/**
* Tests the DownloadHistory module.
*/
"use strict";
const { DownloadHistory } = ChromeUtils.importESModule(
"resource://gre/modules/DownloadHistory.sys.mjs"
);
let baseDate = new Date("2000-01-01");
/**
* Non-fatal assertion used to test whether the downloads in the list already
* match the expected state.
*/
function areEqual(a, b) {
if (a === b) {
Assert.equal(a, b);
return true;
}
info(a + " !== " + b);
return false;
}
/**
* This allows waiting for an expected list at various points during the test.
*/
class TestView {
constructor(expected) {
this.expected = [...expected];
this.downloads = [];
this.resolveWhenExpected = () => {};
}
onDownloadAdded(download, options = {}) {
if (options.insertBefore) {
let index = this.downloads.indexOf(options.insertBefore);
this.downloads.splice(index, 0, download);
} else {
this.downloads.push(download);
}
this.checkForExpectedDownloads();
}
onDownloadChanged() {
this.checkForExpectedDownloads();
}
onDownloadRemoved(download) {
let index = this.downloads.indexOf(download);
this.downloads.splice(index, 1);
this.checkForExpectedDownloads();
}
checkForExpectedDownloads() {
// Wait for all the expected downloads to be added or removed before doing
// the detailed tests. This is done to avoid creating irrelevant output.
if (this.downloads.length != this.expected.length) {
return;
}
for (let i = 0; i < this.downloads.length; i++) {
if (
this.downloads[i].source.url != this.expected[i].source.url ||
this.downloads[i].target.path != this.expected[i].target.path
) {
return;
}
}
// Check and report the actual state of the downloads. Even if the items
// are in the expected order, the metadata for history downloads might not
// have been updated to the final state yet.
for (let i = 0; i < this.downloads.length; i++) {
let download = this.downloads[i];
let testDownload = this.expected[i];
info(
"Checking download source " +
download.source.url +
" with target " +
download.target.path
);
if (
!areEqual(download.succeeded, !!testDownload.succeeded) ||
!areEqual(download.canceled, !!testDownload.canceled) ||
!areEqual(download.hasPartialData, !!testDownload.hasPartialData) ||
!areEqual(!!download.error, !!testDownload.error)
) {
return;
}
// If the above properties match, the error details should be correct.
if (download.error) {
if (testDownload.error.becauseSourceFailed) {
Assert.equal(download.error.message, "History download failed.");
}
Assert.equal(
download.error.becauseBlockedByParentalControls,
testDownload.error.becauseBlockedByParentalControls
);
Assert.equal(
download.error.becauseBlockedByReputationCheck,
testDownload.error.becauseBlockedByReputationCheck
);
}
}
this.resolveWhenExpected();
}
async waitForExpected() {
let promise = new Promise(resolve => (this.resolveWhenExpected = resolve));
this.checkForExpectedDownloads();
await promise;
}
}
/**
* Tests that various operations on session and history downloads are reflected
* by the DownloadHistoryList object, and that the order of results is correct.
*/
add_task(async function test_DownloadHistory() {
// Clean up at the beginning and at the end of the test.
async function cleanup() {
await PlacesUtils.history.clear();
}
registerCleanupFunction(cleanup);
await cleanup();
let testDownloads = [
// History downloads should appear in order at the beginning of the list.
{ offset: 10, canceled: true },
{ offset: 20, succeeded: true },
{ offset: 30, error: { becauseSourceFailed: true } },
{ offset: 40, error: { becauseBlockedByParentalControls: true } },
{ offset: 50, error: { becauseBlockedByReputationCheck: true } },
// Session downloads should show up after all the history download, in the
// same order as they were added.
{ offset: 45, canceled: true, inSession: true },
{ offset: 35, canceled: true, hasPartialData: true, inSession: true },
{ offset: 55, succeeded: true, inSession: true },
];
const NEXT_OFFSET = 60;
let publicList = await promiseNewList();
let allList = await Downloads.getList(Downloads.ALL);
async function addTestDownload(properties) {
properties.source = {
url: httpUrl("source" + properties.offset),
isPrivate: properties.isPrivate,
};
let targetFile = getTempFile(TEST_TARGET_FILE_NAME + properties.offset);
properties.target = { path: targetFile.path };
properties.startTime = new Date(baseDate.getTime() + properties.offset);
let download = await Downloads.createDownload(properties);
if (properties.inSession) {
await allList.add(download);
}
if (properties.isPrivate) {
return;
}
// Add the download to history using the XPCOM service, then use the
// DownloadHistory module to save the associated metadata.
let promiseFileAnnotation = waitForAnnotation(
properties.source.url,
"downloads/destinationFileURI"
);
let promiseMetaAnnotation = waitForAnnotation(
properties.source.url,
"downloads/metaData"
);
let promiseVisit = promiseWaitForVisit(properties.source.url);
await DownloadHistory.addDownloadToHistory(download);
await promiseVisit;
await DownloadHistory.updateMetaData(download);
await Promise.all([promiseFileAnnotation, promiseMetaAnnotation]);
}
// Add all the test downloads to history.
for (let properties of testDownloads) {
await addTestDownload(properties);
}
// Initialize DownloadHistoryList only after having added the history and
// session downloads, and check that they are loaded in the correct order.
let historyList = await DownloadHistory.getList();
let view = new TestView(testDownloads);
await historyList.addView(view);
await view.waitForExpected();
// Remove a download from history and verify that the change is reflected.
let downloadToRemove = view.expected[1];
view.expected.splice(1, 1);
await PlacesUtils.history.remove(downloadToRemove.source.url);
await view.waitForExpected();
// Add a download to history and verify it's placed before session downloads,
// even if the start date is more recent.
let downloadToAdd = { offset: NEXT_OFFSET, canceled: true };
view.expected.splice(
view.expected.findIndex(d => d.inSession),
0,
downloadToAdd
);
await addTestDownload(downloadToAdd);
await view.waitForExpected();
// Add a session download and verify it's placed after all session downloads,
// even if the start date is less recent.
let sessionDownloadToAdd = { offset: 0, inSession: true, succeeded: true };
view.expected.push(sessionDownloadToAdd);
await addTestDownload(sessionDownloadToAdd);
await view.waitForExpected();
// Add a session download for the same URI without a history entry, and verify
// it's visible and placed after all session downloads.
view.expected.push(sessionDownloadToAdd);
await publicList.add(await Downloads.createDownload(sessionDownloadToAdd));
await view.waitForExpected();
// Create a new DownloadHistoryList that also shows private downloads. Since
// we only have public downloads, the two lists should contain the same items.
let allHistoryList = await DownloadHistory.getList({ type: Downloads.ALL });
let allView = new TestView(view.expected);
await allHistoryList.addView(allView);
await allView.waitForExpected();
// Add a new private download and verify it appears only on the complete list.
let privateDownloadToAdd = {
offset: NEXT_OFFSET + 10,
inSession: true,
succeeded: true,
isPrivate: true,
};
allView.expected.push(privateDownloadToAdd);
await addTestDownload(privateDownloadToAdd);
await view.waitForExpected();
await allView.waitForExpected();
// Now test the maxHistoryResults parameter.
let allHistoryList2 = await DownloadHistory.getList({
type: Downloads.ALL,
maxHistoryResults: 3,
});
// Prepare the set of downloads to contain fewer history downloads by removing
// the oldest ones.
let allView2 = new TestView(allView.expected.slice(3));
await allHistoryList2.addView(allView2);
await allView2.waitForExpected();
// Create a dummy list and view like the previous limited one to just add and
// remove its view to make sure it doesn't break other lists' updates.
let dummyList = await DownloadHistory.getList({
type: Downloads.ALL,
maxHistoryResults: 3,
});
let dummyView = new TestView([]);
await dummyList.addView(dummyView);
await dummyList.removeView(dummyView);
// Clear history and check that session downloads with partial data remain.
// Private downloads are also not cleared when clearing history.
view.expected = view.expected.filter(d => d.hasPartialData);
allView.expected = allView.expected.filter(
d => d.hasPartialData || d.isPrivate
);
await PlacesUtils.history.clear();
await view.waitForExpected();
await allView.waitForExpected();
// Check that the dummy view above did not prevent the limited from updating.
allView2.expected = allView.expected;
await allView2.waitForExpected();
});