"use strict"; /* This test will ideally test the following cases (each with and without a callback associated with it) Case A: Tests which should remove pages (Positives) Case A 1: Page has multiple visits both in/out of timeframe, all get deleted Case A 2: Page has single uri, removed by host Case A 3: Page has random subhost, with same host, removed by wildcard Case A 4: Page is localhost and localhost:port, removed by host Case A 5: Page is a `file://` type address, removed by empty host Cases A 1,2,3 will be tried with and without bookmarks added (which prevent page deletion) Case B: Tests in which no pages are removed (Inverses) Case B 1 (inverse): Page has no visits in timeframe, and nothing is deleted Case B 2: Page has single uri, not removed since hostname is different Case B 3: Page has multiple subhosts, not removed since wildcard doesn't match Case C: Combinations tests Case C 1: Single hostname, multiple visits, at least one in timeframe and hostname Case C 2: Random subhosts, multiple visits, at least one in timeframe and hostname-wildcard */ add_task(async function test_removeByFilter() { // Cleanup await PlacesUtils.history.clear(); await PlacesUtils.bookmarks.eraseEverything(); // Adding a witness URI let witnessURI = NetUtil.newURI( "http://witnessmozilla.org/test_browserhistory/test_removeByFilter" + Math.random() ); await PlacesTestUtils.addVisits(witnessURI); Assert.ok( await PlacesTestUtils.isPageInDB(witnessURI), "Witness URI is in database" ); let removeByFilterTester = async function ( visits, filter, checkBeforeRemove, checkAfterRemove, useCallback, bookmarkedUri ) { // Add visits for URIs await PlacesTestUtils.addVisits(visits); if ( bookmarkedUri !== null && visits.map(v => v.uri).includes(bookmarkedUri) ) { await PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, url: bookmarkedUri, title: "test bookmark", }); } await checkBeforeRemove(); // Take care of any observers (due to bookmarks) let { placesEventListener, promiseObserved } = getObserverPromise(bookmarkedUri); if (placesEventListener) { PlacesObservers.addListener( ["page-title-changed", "history-cleared", "page-removed"], placesEventListener ); } // Perfom delete operation on database let removed = false; if (useCallback) { // The amount of callbacks will be the unique URIs to remove from the database let netCallbacksRequired = new Set(visits.map(v => v.uri)).size; removed = await PlacesUtils.history.removeByFilter(filter, pageInfo => { Assert.ok( PlacesUtils.validatePageInfo(pageInfo, false), "pageInfo should follow a basic format" ); Assert.ok( netCallbacksRequired > 0, "Callback called as many times as required" ); netCallbacksRequired--; }); } else { removed = await PlacesUtils.history.removeByFilter(filter); } await checkAfterRemove(); await promiseObserved; if (placesEventListener) { await PlacesUtils.bookmarks.eraseEverything(); PlacesObservers.removeListener( ["page-title-changed", "history-cleared", "page-removed"], placesEventListener ); } Assert.ok( await PlacesTestUtils.isPageInDB(witnessURI), "Witness URI is still in database" ); return removed; }; const remoteUriList = [ "http://mozilla.org/test_browserhistory/test_removeByFilter/" + Math.random(), "http://subdomain1.mozilla.org/test_browserhistory/test_removeByFilter/" + Math.random(), "http://subdomain2.mozilla.org/test_browserhistory/test_removeByFilter/" + Math.random(), ]; const localhostUriList = [ "http://localhost:4500/" + Math.random(), "http://localhost/" + Math.random(), ]; const fileUriList = ["file:///home/user/files" + Math.random()]; const title = "Title " + Math.random(); let sameHostVisits = [ { uri: remoteUriList[0], title, visitDate: new Date(2005, 1, 1) * 1000, }, { uri: remoteUriList[0], title, visitDate: new Date(2005, 3, 3) * 1000, }, { uri: remoteUriList[0], title, visitDate: new Date(2007, 1, 1) * 1000, }, ]; let randomHostVisits = [ { uri: remoteUriList[0], title, visitDate: new Date(2005, 1, 1) * 1000, }, { uri: remoteUriList[1], title, visitDate: new Date(2005, 3, 3) * 1000, }, { uri: remoteUriList[2], title, visitDate: new Date(2007, 1, 1) * 1000, }, ]; let localhostVisits = [ { uri: localhostUriList[0], title, }, { uri: localhostUriList[1], title, }, ]; let fileVisits = [ { uri: fileUriList[0], title, }, ]; let assertInDB = async function (aUri) { Assert.ok(await PlacesTestUtils.isPageInDB(aUri)); }; let assertNotInDB = async function (aUri) { Assert.ok(!(await PlacesTestUtils.isPageInDB(aUri))); }; for (let callbackUse of [true, false]) { // Case A Positives for (let bookmarkUse of [true, false]) { let bookmarkedUri = arr => undefined; let checkableArray = arr => arr; let checkClosure = assertNotInDB; if (bookmarkUse) { bookmarkedUri = arr => arr[0]; checkableArray = arr => arr.slice(1); checkClosure = function (aUri) {}; } // Case A 1: Dates await removeByFilterTester( sameHostVisits, { beginDate: new Date(2004, 1, 1), endDate: new Date(2006, 1, 1) }, () => assertInDB(remoteUriList[0]), () => checkClosure(remoteUriList[0]), callbackUse, bookmarkedUri(remoteUriList) ); // Case A 2: Single Sub-host await removeByFilterTester( sameHostVisits, { host: "mozilla.org" }, () => assertInDB(remoteUriList[0]), () => checkClosure(remoteUriList[0]), callbackUse, bookmarkedUri(remoteUriList) ); // Case A 3: Multiple subhost await removeByFilterTester( randomHostVisits, { host: ".mozilla.org" }, async () => { for (let uri of remoteUriList) { await assertInDB(uri); } }, async () => { for (let uri of checkableArray(remoteUriList)) { await checkClosure(uri); } }, callbackUse, bookmarkedUri(remoteUriList) ); } // Case A 4: Localhost await removeByFilterTester( localhostVisits, { host: "localhost" }, async () => { for (let uri of localhostUriList) { await assertInDB(uri); } }, async () => { for (let uri of localhostUriList) { await assertNotInDB(uri); } }, callbackUse ); // Case A 5: Local Files await removeByFilterTester( fileVisits, { host: "." }, async () => { for (let uri of fileUriList) { await assertInDB(uri); } }, async () => { for (let uri of fileUriList) { await assertNotInDB(uri); } }, callbackUse ); // Case B: Tests which do not remove anything (inverses) // Case B 1: Date await removeByFilterTester( sameHostVisits, { beginDate: new Date(2001, 1, 1), endDate: new Date(2002, 1, 1) }, () => assertInDB(remoteUriList[0]), () => assertInDB(remoteUriList[0]), callbackUse ); // Case B 2 : Single subhost await removeByFilterTester( sameHostVisits, { host: "notthere.org" }, () => assertInDB(remoteUriList[0]), () => assertInDB(remoteUriList[0]), callbackUse ); // Case B 3 : Multiple subhosts await removeByFilterTester( randomHostVisits, { host: ".notthere.org" }, async () => { for (let uri of remoteUriList) { await assertInDB(uri); } }, async () => { for (let uri of remoteUriList) { await assertInDB(uri); } }, callbackUse ); // Case B 4 : invalid local subhost await removeByFilterTester( randomHostVisits, { host: ".org" }, async () => { for (let uri of remoteUriList) { await assertInDB(uri); } }, async () => { for (let uri of remoteUriList) { await assertInDB(uri); } }, callbackUse ); // Case C: Combination Cases // Case C 1: single subhost await removeByFilterTester( sameHostVisits, { host: "mozilla.org", beginDate: new Date(2004, 1, 1), endDate: new Date(2006, 1, 1), }, () => assertInDB(remoteUriList[0]), () => assertNotInDB(remoteUriList[0]), callbackUse ); // Case C 2: multiple subhost await removeByFilterTester( randomHostVisits, { host: ".mozilla.org", beginDate: new Date(2005, 1, 1), endDate: new Date(2017, 1, 1), }, async () => { for (let uri of remoteUriList) { await assertInDB(uri); } }, async () => { for (let uri of remoteUriList) { await assertNotInDB(uri); } }, callbackUse ); } }); // Test various error cases add_task(async function test_error_cases() { Assert.throws( () => PlacesUtils.history.removeByFilter(), /TypeError: Expected a filter/ ); Assert.throws( () => PlacesUtils.history.removeByFilter("obviously, not a filter"), /TypeError: Expected a filter/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({}), /TypeError: Expected a non-empty filter/ ); Assert.throws( () => PlacesUtils.history.removeVisitsByFilter({ beginDate: "now" }), /TypeError: Expected a valid Date/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ beginDate: Date.now() }), /TypeError: Expected a valid Date/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ beginDate: new Date(NaN) }), /TypeError: Expected a valid Date/ ); Assert.throws( () => PlacesUtils.history.removeByFilter( { beginDate: new Date() }, "obviously, not a callback" ), /TypeError: Invalid function/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ beginDate: new Date(1000), endDate: new Date(0), }), /TypeError: `beginDate` should be at least as old/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ host: "#" }), /TypeError: Expected well formed hostname string for/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ host: "www..org" }), /TypeError: Expected well formed hostname string for/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ host: {} }), /TypeError: `host` should be a string/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ host: "*.mozilla.org" }), /TypeError: Expected well formed hostname string for/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ host: "*" }), /TypeError: Expected well formed hostname string for/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ host: "local.host." }), /TypeError: Expected well formed hostname string for/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ host: "(local files)" }), /TypeError: Expected well formed hostname string for/ ); Assert.throws( () => PlacesUtils.history.removeByFilter({ host: "" }), /TypeError: Expected a non-empty filter/ ); }); add_task(async function test_chunking() { await PlacesUtils.history.clear(); await PlacesUtils.bookmarks.eraseEverything(); info("Insert many visited pages"); let pages = []; for (let i = 1; i <= 1500; i++) { let visits = [ { date: new Date(Date.now() - (86400 + i) * 1000), transition: PlacesUtils.history.TRANSITIONS.TYPED, }, ]; pages.push( { url: `http://example.com/${i}`, title: `Page ${i}`, visits, }, { url: `http://subdomain.example.com/${i}`, title: `Subdomain ${i}`, visits, } ); } await PlacesUtils.history.insertMany(pages); info("Remove all visited pages"); await PlacesUtils.history.removeByFilter({ host: ".example.com", }); }); // Helper functions function getObserverPromise(bookmarkedUri) { if (!bookmarkedUri) { return { promiseObserved: Promise.resolve() }; } let placesEventListener; let promiseObserved = new Promise((resolve, reject) => { placesEventListener = events => { for (const event of events) { switch (event.type) { case "page-title-changed": { reject(new Error("Unexpected page-title-changed event happens")); break; } case "history-cleared": { reject(new Error("Unexpected history-cleared event happens")); break; } case "page-removed": { if (event.isRemovedFromStore) { Assert.notEqual( event.url, bookmarkedUri, "Bookmarked URI should not be deleted" ); } else { Assert.equal( event.isPartialVisistsRemoval, false, "Observing page-removed deletes all visits" ); Assert.equal( event.url, bookmarkedUri, "Bookmarked URI should have all visits removed but not the page itself" ); } resolve(); break; } } } }; }); return { placesEventListener, promiseObserved }; }