diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/places/tests/expiration | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/places/tests/expiration')
12 files changed, 1337 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/expiration/head_expiration.js b/toolkit/components/places/tests/expiration/head_expiration.js new file mode 100644 index 0000000000..ce9fe48348 --- /dev/null +++ b/toolkit/components/places/tests/expiration/head_expiration.js @@ -0,0 +1,112 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Import common head. +{ + /* import-globals-from ../head_common.js */ + let commonFile = do_get_file("../head_common.js", false); + let uri = Services.io.newFileURI(commonFile); + Services.scriptloader.loadSubScript(uri.spec, this); +} + +// Put any other stuff relative to this test folder below. + +/** + * Causes expiration component to start, otherwise it would wait for the first + * history notification. + */ +function force_expiration_start() { + Cc["@mozilla.org/places/expiration;1"] + .getService(Ci.nsIObserver) + .observe(null, "testing-mode", null); +} + +/** + * Forces an expiration run. + * + * @param [optional] aLimit + * Limit for the expiration. Pass -1 for unlimited. + * Any other non-positive value will just expire orphans. + * + * @return {Promise} + * @resolves When expiration finishes. + * @rejects Never. + */ +function promiseForceExpirationStep(aLimit) { + let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED); + let expire = Cc["@mozilla.org/places/expiration;1"].getService( + Ci.nsIObserver + ); + expire.observe(null, "places-debug-start-expiration", aLimit); + return promise; +} + +/** + * Expiration preferences helpers. + */ + +function setInterval(aNewInterval) { + Services.prefs.setIntPref( + "places.history.expiration.interval_seconds", + aNewInterval + ); +} +function getInterval() { + return Services.prefs.getIntPref( + "places.history.expiration.interval_seconds" + ); +} +function clearInterval() { + try { + Services.prefs.clearUserPref("places.history.expiration.interval_seconds"); + } catch (ex) {} +} + +function setMaxPages(aNewMaxPages) { + Services.prefs.setIntPref( + "places.history.expiration.max_pages", + aNewMaxPages + ); +} +function getMaxPages() { + return Services.prefs.getIntPref("places.history.expiration.max_pages"); +} +function clearMaxPages() { + try { + Services.prefs.clearUserPref("places.history.expiration.max_pages"); + } catch (ex) {} +} + +function setHistoryEnabled(aHistoryEnabled) { + Services.prefs.setBoolPref("places.history.enabled", aHistoryEnabled); +} +function getHistoryEnabled() { + return Services.prefs.getBoolPref("places.history.enabled"); +} +function clearHistoryEnabled() { + try { + Services.prefs.clearUserPref("places.history.enabled"); + } catch (ex) {} +} + +/** + * Returns a PRTime in the past usable to add expirable visits. + * + * param [optional] daysAgo + * Expiration ignores any visit added in the last 7 days, so by default + * this will be set to 7. + * @note to be safe against DST issues we go back one day more. + */ +function getExpirablePRTime(daysAgo = 7) { + let dateObj = new Date(); + // Normalize to midnight + dateObj.setHours(0); + dateObj.setMinutes(0); + dateObj.setSeconds(0); + dateObj.setMilliseconds(0); + dateObj = new Date(dateObj.getTime() - (daysAgo + 1) * 86400000); + return dateObj.getTime() * 1000; +} diff --git a/toolkit/components/places/tests/expiration/test_annos_expire_never.js b/toolkit/components/places/tests/expiration/test_annos_expire_never.js new file mode 100644 index 0000000000..39c55ecc04 --- /dev/null +++ b/toolkit/components/places/tests/expiration/test_annos_expire_never.js @@ -0,0 +1,72 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * What this is aimed to test: + * + * EXPIRE_NEVER annotations should be expired when a page is removed from the + * database. + * If the annotation is a page annotation this will happen when the page is + * expired, namely when the page has no visits and is not bookmarked. + */ + +add_task(async function test_annos_expire_never() { + // Set interval to a large value so we don't expire on it. + setInterval(3600); // 1h + + // Expire all expirable pages. + setMaxPages(0); + + // Add some visited page and a couple expire never annotations for each. + let now = getExpirablePRTime(); + for (let i = 0; i < 5; i++) { + let pageURI = uri("http://page_anno." + i + ".mozilla.org/"); + await PlacesTestUtils.addVisits({ uri: pageURI, visitDate: now++ }); + await PlacesUtils.history.update({ + url: pageURI, + annotations: new Map([ + ["page_expire1", "test"], + ["page_expire2", "test"], + ]), + }); + } + + let pages = await getPagesWithAnnotation("page_expire1"); + Assert.equal(pages.length, 5); + pages = await getPagesWithAnnotation("page_expire2"); + Assert.equal(pages.length, 5); + + // Add other visited page and a couple expire never annotations for each. + // We won't expire these visits, so the annotations should survive. + for (let i = 0; i < 5; i++) { + let pageURI = uri("http://persist_page_anno." + i + ".mozilla.org/"); + await PlacesTestUtils.addVisits({ uri: pageURI, visitDate: now++ }); + await PlacesUtils.history.update({ + url: pageURI, + annotations: new Map([ + ["page_persist1", "test"], + ["page_persist2", "test"], + ]), + }); + } + + pages = await getPagesWithAnnotation("page_persist1"); + Assert.equal(pages.length, 5); + pages = await getPagesWithAnnotation("page_persist2"); + Assert.equal(pages.length, 5); + + // Expire all visits for the first 5 pages and the bookmarks. + await promiseForceExpirationStep(5); + + pages = await getPagesWithAnnotation("page_expire1"); + Assert.equal(pages.length, 0); + pages = await getPagesWithAnnotation("page_expire2"); + Assert.equal(pages.length, 0); + pages = await getPagesWithAnnotation("page_persist1"); + Assert.equal(pages.length, 5); + pages = await getPagesWithAnnotation("page_persist2"); + Assert.equal(pages.length, 5); +}); diff --git a/toolkit/components/places/tests/expiration/test_clearHistory.js b/toolkit/components/places/tests/expiration/test_clearHistory.js new file mode 100644 index 0000000000..a4684f0269 --- /dev/null +++ b/toolkit/components/places/tests/expiration/test_clearHistory.js @@ -0,0 +1,57 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * What this is aimed to test: + * + * History.clear() should expire everything but bookmarked pages and valid + * annos. + */ + +add_task(async function test_historyClear() { + // Set interval to a large value so we don't expire on it. + setInterval(3600); // 1h + + // Expire all expirable pages. + setMaxPages(0); + + // Add some bookmarked page with visit and annotations. + for (let i = 0; i < 5; i++) { + let pageURI = uri("http://item_anno." + i + ".mozilla.org/"); + // This visit will be expired. + await PlacesTestUtils.addVisits({ uri: pageURI }); + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: pageURI, + title: null, + }); + // Will persist because the page is bookmarked. + await PlacesUtils.history.update({ + url: pageURI, + annotations: new Map([["persist", "test"]]), + }); + } + + // Add some visited page and annotations for each. + for (let i = 0; i < 5; i++) { + // All page annotations related to these expired pages are expected to + // expire as well. + let pageURI = uri("http://page_anno." + i + ".mozilla.org/"); + await PlacesTestUtils.addVisits({ uri: pageURI }); + await PlacesUtils.history.update({ + url: pageURI, + annotations: new Map([["expire", "test"]]), + }); + } + + // Expire all visits for the bookmarks + await PlacesUtils.history.clear(); + + Assert.equal((await getPagesWithAnnotation("expire")).length, 0); + + let pages = await getPagesWithAnnotation("persist"); + Assert.equal(pages.length, 5); +}); diff --git a/toolkit/components/places/tests/expiration/test_debug_expiration.js b/toolkit/components/places/tests/expiration/test_debug_expiration.js new file mode 100644 index 0000000000..204295d46c --- /dev/null +++ b/toolkit/components/places/tests/expiration/test_debug_expiration.js @@ -0,0 +1,469 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * What this is aimed to test: + * + * Expiration can be manually triggered through a debug topic, but that should + * only expire orphan entries, unless -1 is passed as limit. + */ + +const EXPIRE_DAYS = 90; +var gExpirableTime = getExpirablePRTime(EXPIRE_DAYS); +var gNonExpirableTime = getExpirablePRTime(EXPIRE_DAYS - 2); + +add_task(async function test_expire_orphans() { + // Add visits to 2 pages and force a orphan expiration. Visits should survive. + await PlacesTestUtils.addVisits({ + uri: uri("http://page1.mozilla.org/"), + visitDate: gExpirableTime++, + }); + await PlacesTestUtils.addVisits({ + uri: uri("http://page2.mozilla.org/"), + visitDate: gExpirableTime++, + }); + // Create a orphan place. + let bm = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://page3.mozilla.org/", + title: "", + }); + await PlacesUtils.bookmarks.remove(bm); + + // Expire now. + await promiseForceExpirationStep(0); + + // Check that visits survived. + Assert.equal(visits_in_database("http://page1.mozilla.org/"), 1); + Assert.equal(visits_in_database("http://page2.mozilla.org/"), 1); + Assert.ok(!page_in_database("http://page3.mozilla.org/")); + + // Clean up. + await PlacesUtils.history.clear(); +}); + +add_task(async function test_expire_orphans_optionalarg() { + // Add visits to 2 pages and force a orphan expiration. Visits should survive. + await PlacesTestUtils.addVisits({ + uri: uri("http://page1.mozilla.org/"), + visitDate: gExpirableTime++, + }); + await PlacesTestUtils.addVisits({ + uri: uri("http://page2.mozilla.org/"), + visitDate: gExpirableTime++, + }); + // Create a orphan place. + let bm = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://page3.mozilla.org/", + title: "", + }); + await PlacesUtils.bookmarks.remove(bm); + + // Expire now. + await promiseForceExpirationStep(); + + // Check that visits survived. + Assert.equal(visits_in_database("http://page1.mozilla.org/"), 1); + Assert.equal(visits_in_database("http://page2.mozilla.org/"), 1); + Assert.ok(!page_in_database("http://page3.mozilla.org/")); + + // Clean up. + await PlacesUtils.history.clear(); +}); + +add_task(async function test_expire_limited() { + await PlacesTestUtils.addVisits([ + { + // Should be expired cause it's the oldest visit + uri: "http://old.mozilla.org/", + visitDate: gExpirableTime++, + }, + { + // Should not be expired cause we limit 1 + uri: "http://new.mozilla.org/", + visitDate: gExpirableTime++, + }, + ]); + + // Expire now. + await promiseForceExpirationStep(1); + + // Check that newer visit survived. + Assert.equal(visits_in_database("http://new.mozilla.org/"), 1); + // Other visits should have been expired. + Assert.ok(!page_in_database("http://old.mozilla.org/")); + + // Clean up. + await PlacesUtils.history.clear(); +}); + +add_task(async function test_expire_visitcount_longurl() { + let longurl = "http://long.mozilla.org/" + "a".repeat(232); + let longurl2 = "http://long2.mozilla.org/" + "a".repeat(232); + await PlacesTestUtils.addVisits([ + { + // Should be expired cause it's the oldest visit + uri: "http://old.mozilla.org/", + visitDate: gExpirableTime++, + }, + { + // Should not be expired cause it has 2 visits. + uri: longurl, + visitDate: gExpirableTime++, + }, + { + uri: longurl, + visitDate: gNonExpirableTime, + }, + { + // Should be expired cause it has 1 old visit. + uri: longurl2, + visitDate: gExpirableTime++, + }, + ]); + + await promiseForceExpirationStep(1); + + // Check that some visits survived. + Assert.equal(visits_in_database(longurl), 2); + // Check visit has been removed. + Assert.equal(visits_in_database(longurl2), 0); + + // Other visits should have been expired. + Assert.ok(!page_in_database("http://old.mozilla.org/")); + + // Clean up. + await PlacesUtils.history.clear(); +}); + +add_task(async function test_expire_limited_exoticurl() { + await PlacesTestUtils.addVisits([ + { + // Should be expired cause it's the oldest visit + uri: "http://old.mozilla.org/", + visitDate: gExpirableTime++, + }, + { + // Should not be expired cause younger than EXPIRE_DAYS. + uri: "http://nonexpirable-download.mozilla.org", + visitDate: gNonExpirableTime, + transition: PlacesUtils.history.TRANSITIONS.DOWNLOAD, + }, + { + // Should be expired cause it's a long url older than EXPIRE_DAYS. + uri: "http://download.mozilla.org", + visitDate: gExpirableTime++, + transition: 7, + }, + ]); + + await promiseForceExpirationStep(1); + + // Check that some visits survived. + Assert.equal( + visits_in_database("http://nonexpirable-download.mozilla.org/"), + 1 + ); + // The visits are gone, the url is not yet, cause we limited the expiration + // to one entry, and we already removed http://old.mozilla.org/. + // The page normally would be expired by the next expiration run. + Assert.equal(visits_in_database("http://download.mozilla.org/"), 0); + // Other visits should have been expired. + Assert.ok(!page_in_database("http://old.mozilla.org/")); + + // Clean up. + await PlacesUtils.history.clear(); +}); + +add_task(async function test_expire_exotic_hidden() { + let visits = [ + { + // Should be expired cause it's the oldest visit + uri: "http://old.mozilla.org/", + visitDate: gExpirableTime++, + expectedCount: 0, + }, + { + // Expirable typed hidden url. + uri: "https://typedhidden.mozilla.org/", + visitDate: gExpirableTime++, + transition: PlacesUtils.history.TRANSITIONS.FRAMED_LINK, + expectedCount: 2, + }, + { + // Mark as typed. + uri: "https://typedhidden.mozilla.org/", + visitDate: gExpirableTime++, + transition: PlacesUtils.history.TRANSITIONS.TYPED, + expectedCount: 2, + }, + { + // Expirable non-typed hidden url. + uri: "https://hidden.mozilla.org/", + visitDate: gExpirableTime++, + transition: PlacesUtils.history.TRANSITIONS.FRAMED_LINK, + expectedCount: 0, + }, + ]; + await PlacesTestUtils.addVisits(visits); + for (let visit of visits) { + Assert.greater(visits_in_database(visit.uri), 0); + } + + await promiseForceExpirationStep(1); + + for (let visit of visits) { + Assert.equal( + visits_in_database(visit.uri), + visit.expectedCount, + `${visit.uri} should${ + visit.expectedCount == 0 ? " " : " not " + }have been expired` + ); + } + // Clean up. + await PlacesUtils.history.clear(); +}); + +add_task(async function test_expire_unlimited() { + let longurl = "http://long.mozilla.org/" + "a".repeat(232); + await PlacesTestUtils.addVisits([ + { + uri: "http://old.mozilla.org/", + visitDate: gExpirableTime++, + }, + { + uri: "http://new.mozilla.org/", + visitDate: gExpirableTime++, + }, + // Add expirable visits. + { + uri: "http://download.mozilla.org/", + visitDate: gExpirableTime++, + transition: PlacesUtils.history.TRANSITION_DOWNLOAD, + }, + { + uri: longurl, + visitDate: gExpirableTime++, + }, + + // Add non-expirable visits + { + uri: "http://nonexpirable.mozilla.org/", + visitDate: getExpirablePRTime(5), + }, + { + uri: "http://nonexpirable-download.mozilla.org/", + visitDate: getExpirablePRTime(5), + transition: PlacesUtils.history.TRANSITION_DOWNLOAD, + }, + { + uri: longurl, + visitDate: getExpirablePRTime(5), + }, + ]); + + await promiseForceExpirationStep(-1); + + // Check that some visits survived. + Assert.equal(visits_in_database("http://nonexpirable.mozilla.org/"), 1); + Assert.equal( + visits_in_database("http://nonexpirable-download.mozilla.org/"), + 1 + ); + Assert.equal(visits_in_database(longurl), 1); + // Other visits should have been expired. + Assert.ok(!page_in_database("http://old.mozilla.org/")); + Assert.ok(!page_in_database("http://download.mozilla.org/")); + Assert.ok(!page_in_database("http://new.mozilla.org/")); + + // Clean up. + await PlacesUtils.history.clear(); +}); + +add_task(async function test_expire_icons() { + const dataUrl = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" + + "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=="; + + const entries = [ + { + desc: "Not expired because recent", + page: "https://recent.notexpired.org/", + icon: "https://recent.notexpired.org/test_icon.png", + root: "https://recent.notexpired.org/favicon.ico", + iconExpired: false, + removed: false, + }, + { + desc: "Not expired because recent, no root", + page: "https://recentnoroot.notexpired.org/", + icon: "https://recentnoroot.notexpired.org/test_icon.png", + iconExpired: false, + removed: false, + }, + { + desc: "Expired because old with root", + page: "https://oldroot.expired.org/", + icon: "https://oldroot.expired.org/test_icon.png", + root: "https://oldroot.expired.org/favicon.ico", + iconExpired: true, + removed: true, + }, + { + desc: "Not expired because bookmarked, even if old with root", + page: "https://oldrootbm.notexpired.org/", + icon: "https://oldrootbm.notexpired.org/test_icon.png", + root: "https://oldrootbm.notexpired.org/favicon.ico", + bookmarked: true, + iconExpired: true, + removed: false, + }, + { + desc: "Not Expired because old but has no root", + page: "https://old.notexpired.org/", + icon: "https://old.notexpired.org/test_icon.png", + iconExpired: true, + removed: false, + }, + { + desc: "Expired because it's an orphan page", + page: "http://root.ref.org/#test", + icon: undefined, + iconExpired: false, + removed: true, + }, + { + desc: "Expired because it's an orphan page", + page: "http://root.ref.org/#test", + icon: undefined, + skipHistory: true, + iconExpired: false, + removed: true, + }, + ]; + + for (let entry of entries) { + if (!entry.skipHistory) { + await PlacesTestUtils.addVisits(entry.page); + } + if (entry.bookmarked) { + await PlacesUtils.bookmarks.insert({ + url: entry.page, + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + }); + } + + if (entry.icon) { + PlacesUtils.favicons.replaceFaviconDataFromDataURL( + Services.io.newURI(entry.icon), + dataUrl, + 0, + Services.scriptSecurityManager.getSystemPrincipal() + ); + await PlacesTestUtils.addFavicons(new Map([[entry.page, entry.icon]])); + Assert.equal( + await getFaviconUrlForPage(entry.page), + entry.icon, + "Sanity check the icon exists" + ); + } else { + // This is an orphan page entry. + await PlacesUtils.withConnectionWrapper("addOrphanPage", async db => { + await db.execute( + `INSERT INTO moz_pages_w_icons (page_url, page_url_hash) + VALUES (:url, hash(:url))`, + { url: entry.page } + ); + }); + } + + if (entry.root) { + PlacesUtils.favicons.replaceFaviconDataFromDataURL( + Services.io.newURI(entry.root), + dataUrl, + 0, + Services.scriptSecurityManager.getSystemPrincipal() + ); + await PlacesTestUtils.addFavicons(new Map([[entry.page, entry.root]])); + } + + if (entry.iconExpired) { + // Set an expired time on the icon. + await PlacesUtils.withConnectionWrapper("expireFavicon", async db => { + await db.execute( + `UPDATE moz_icons_to_pages SET expire_ms = 1 + WHERE icon_id = (SELECT id FROM moz_icons WHERE icon_url = :url)`, + { url: entry.icon } + ); + if (entry.root) { + await db.execute( + `UPDATE moz_icons SET expire_ms = 1 WHERE icon_url = :url`, + { url: entry.root } + ); + } + }); + } + if (entry.icon) { + Assert.equal( + await getFaviconUrlForPage(entry.page), + entry.icon, + "Sanity check the initial icon value" + ); + } + } + + info("Run expiration"); + await promiseForceExpirationStep(-1); + + info("Check expiration"); + for (let entry of entries) { + Assert.ok(page_in_database(entry.page)); + + if (!entry.removed) { + Assert.equal( + await getFaviconUrlForPage(entry.page), + entry.icon, + entry.desc + ); + continue; + } + + if (entry.root) { + Assert.equal( + await getFaviconUrlForPage(entry.page), + entry.root, + entry.desc + ); + continue; + } + + if (entry.icon) { + await Assert.rejects( + getFaviconUrlForPage(entry.page), + /Unable to find an icon/, + entry.desc + ); + continue; + } + + // This was an orphan page entry. + let db = await PlacesUtils.promiseDBConnection(); + let rows = await db.execute( + `SELECT count(*) FROM moz_pages_w_icons WHERE page_url_hash = hash(:url)`, + { url: entry.page } + ); + Assert.equal(rows[0].getResultByIndex(0), 0, "Orphan page was removed"); + } + + // Clean up. + await PlacesUtils.history.clear(); +}); + +add_setup(async function () { + // Set interval to a large value so we don't expire on it. + setInterval(3600); // 1h + // Set maxPages to a low value, so it's easy to go over it. + setMaxPages(1); +}); diff --git a/toolkit/components/places/tests/expiration/test_idle_daily.js b/toolkit/components/places/tests/expiration/test_idle_daily.js new file mode 100644 index 0000000000..11547e37dc --- /dev/null +++ b/toolkit/components/places/tests/expiration/test_idle_daily.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that expiration runs on idle-daily. + +add_task(async function test_expiration_on_idle_daily() { + // Set interval to a large value so we don't expire on it. + setInterval(3600); // 1h + + let expirationPromise = TestUtils.topicObserved( + PlacesUtils.TOPIC_EXPIRATION_FINISHED + ); + + let expire = Cc["@mozilla.org/places/expiration;1"].getService( + Ci.nsIObserver + ); + expire.observe(null, "idle-daily", null); + + await expirationPromise; +}); diff --git a/toolkit/components/places/tests/expiration/test_interactions_expiration.js b/toolkit/components/places/tests/expiration/test_interactions_expiration.js new file mode 100644 index 0000000000..67b4b466c3 --- /dev/null +++ b/toolkit/components/places/tests/expiration/test_interactions_expiration.js @@ -0,0 +1,102 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests expiration of Places interactions data. + */ +// Number of days in the past where interactions will be expired. +const EXPIRE_DAYS = 60; +// Should be more recent than EXPIRED_DAYS. +const RECENT_DATE = new Date() - (EXPIRE_DAYS - 1) * 86400000; + +add_task(async function setup() { + Services.prefs.setBoolPref("browser.places.interactions.enabled", true); + Services.prefs.setIntPref( + "browser.places.interactions.expireDays", + EXPIRE_DAYS + ); +}); + +add_task(async function test_expire_interactions() { + // Add visits and metadata to 2 pages and force expiration. + await PlacesTestUtils.addVisits([ + "https://expired.mozilla.org/", + "https://interactions-expired.mozilla.org/", + "https://some-interaction-expired.mozilla.org/", + "https://not-expired.mozilla.org/", + ]); + // Insert dummy interactions for all the pages. + await addDummyInteractions("https://removed.mozilla.org/", [0]); + await addDummyInteractions("https://interactions-expired.mozilla.org/", [ + EXPIRE_DAYS + 10, + ]); + await addDummyInteractions("https://some-interactions-expired.mozilla.org/", [ + 0, + EXPIRE_DAYS + 10, + ]); + await addDummyInteractions("https://not-expired.mozilla.org/", [ + 0, + EXPIRE_DAYS / 2, + ]); + + info("Remove a page from history and check interactions are removed"); + await PlacesUtils.history.remove("https://removed.mozilla.org/"); + await checkDummyInteractions("https://removed.mozilla.org/", 0); + + // Expire now. + await promiseForceExpirationStep(-1); + + info("Test interactions expiration result"); + await checkDummyInteractions("https://interactions-expired.mozilla.org/", 0); + await checkDummyInteractions( + "https://some-interactions-expired.mozilla.org/", + 1 + ); + await checkDummyInteractions("https://not-expired.mozilla.org/", 2); + + // Clean up. + await PlacesUtils.history.clear(); +}); + +async function addDummyInteractions(url, interactionDaysAgo) { + await PlacesTestUtils.addVisits(url); + await PlacesUtils.withConnectionWrapper( + "test_interactions_expiration.js: addDummyInteraction", + async db => { + await db.execute( + `INSERT INTO moz_places_metadata (place_id, created_at, updated_at) VALUES ( + (SELECT id FROM moz_places WHERE url_hash = hash(:url)), + strftime('%s','now','localtime','-' || :days || ' day','start of day','utc') * 1000, + strftime('%s','now','localtime','-' || :days || ' day','start of day','utc') * 1000 + )`, + interactionDaysAgo.map(days => ({ url, days })) + ); + } + ); +} + +async function checkDummyInteractions(url, interactionsLen) { + info("Check interactions for " + url); + await PlacesUtils.withConnectionWrapper( + "test_interactions_expiration.js: addDummyInteraction", + async db => { + let rows = await db.execute( + `SELECT updated_at + FROM moz_places_metadata + WHERE place_id = (SELECT id FROM moz_places WHERE url_hash = hash(:url)) + ORDER BY updated_at DESC`, + { url } + ); + let dates = rows.map(r => new Date(r.getResultByName("updated_at"))); + Assert.equal( + rows.length, + interactionsLen, + "Found expected number of interactions" + ); + Assert.ok( + dates.every(d => d > RECENT_DATE), + "All interactions are recent" + ); + } + ); +} diff --git a/toolkit/components/places/tests/expiration/test_notifications.js b/toolkit/components/places/tests/expiration/test_notifications.js new file mode 100644 index 0000000000..d52319a9c9 --- /dev/null +++ b/toolkit/components/places/tests/expiration/test_notifications.js @@ -0,0 +1,36 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * What this is aimed to test: + * + * Ensure that History (through category cache) notifies us just once. + */ + +var gObserver = { + notifications: 0, + observe(aSubject, aTopic, aData) { + this.notifications++; + }, +}; +Services.obs.addObserver(gObserver, PlacesUtils.TOPIC_EXPIRATION_FINISHED); + +add_task(async function test_history_expirations_notify_just_once() { + // Set interval to a large value so we don't expire on it. + setInterval(3600); // 1h + + promiseForceExpirationStep(1); + + await new Promise(resolve => { + do_timeout(2000, resolve); + }); + + Assert.equal(gObserver.notifications, 1); + + Services.obs.removeObserver(gObserver, PlacesUtils.TOPIC_EXPIRATION_FINISHED); +}); diff --git a/toolkit/components/places/tests/expiration/test_notifications_pageRemoved_allVisits.js b/toolkit/components/places/tests/expiration/test_notifications_pageRemoved_allVisits.js new file mode 100644 index 0000000000..172f29cf96 --- /dev/null +++ b/toolkit/components/places/tests/expiration/test_notifications_pageRemoved_allVisits.js @@ -0,0 +1,156 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * What this is aimed to test: + * + * Expiring only visits for a page, but not the full page, should fire an + * page-removed for all visits notification. + */ + +var tests = [ + { + desc: "Add 1 bookmarked page.", + addPages: 1, + visitsPerPage: 1, + addBookmarks: 1, + limitExpiration: -1, + expectedNotifications: 1, // Will expire visits for 1 page. + expectedIsPartialRemoval: true, + }, + + { + desc: "Add 2 pages, 1 bookmarked.", + addPages: 2, + visitsPerPage: 1, + addBookmarks: 1, + limitExpiration: -1, + expectedNotifications: 1, // Will expire visits for 1 page. + expectedIsPartialRemoval: true, + }, + + { + desc: "Add 10 pages, none bookmarked.", + addPages: 10, + visitsPerPage: 1, + addBookmarks: 0, + limitExpiration: -1, + expectedNotifications: 0, // Will expire only full pages. + expectedIsPartialRemoval: false, + }, + + { + desc: "Add 10 pages, all bookmarked.", + addPages: 10, + visitsPerPage: 1, + addBookmarks: 10, + limitExpiration: -1, + expectedNotifications: 10, // Will expire visits for all pages. + expectedIsPartialRemoval: true, + }, + + { + desc: "Add 10 pages with lot of visits, none bookmarked.", + addPages: 10, + visitsPerPage: 10, + addBookmarks: 0, + limitExpiration: 10, + expectedNotifications: 10, // Will expire 1 visit for each page, but won't + // expire pages since they still have visits. + expectedIsPartialRemoval: true, + }, +]; + +add_task(async () => { + // Set interval to a large value so we don't expire on it. + setInterval(3600); // 1h + + // Expire anything that is expirable. + setMaxPages(0); + + for (let testIndex = 1; testIndex <= tests.length; testIndex++) { + let currentTest = tests[testIndex - 1]; + info("TEST " + testIndex + ": " + currentTest.desc); + currentTest.receivedNotifications = 0; + + // Setup visits. + let timeInMicroseconds = getExpirablePRTime(8); + + function newTimeInMicroseconds() { + timeInMicroseconds = timeInMicroseconds + 1000; + return timeInMicroseconds; + } + + for (let j = 0; j < currentTest.visitsPerPage; j++) { + for (let i = 0; i < currentTest.addPages; i++) { + let page = "http://" + testIndex + "." + i + ".mozilla.org/"; + await PlacesTestUtils.addVisits({ + uri: uri(page), + visitDate: newTimeInMicroseconds(), + }); + } + } + + // Setup bookmarks. + currentTest.bookmarks = []; + for (let i = 0; i < currentTest.addBookmarks; i++) { + let page = "http://" + testIndex + "." + i + ".mozilla.org/"; + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + title: null, + url: page, + }); + currentTest.bookmarks.push(page); + } + + // Observe history. + let notificationsHandled = new Promise(resolve => { + const listener = async events => { + for (const event of events) { + Assert.equal(event.type, "page-removed"); + Assert.equal(event.reason, PlacesVisitRemoved.REASON_EXPIRED); + + if (event.isRemovedFromStore) { + // Check this uri was not bookmarked. + Assert.equal(currentTest.bookmarks.indexOf(event.url), -1); + do_check_valid_places_guid(event.pageGuid); + } else { + currentTest.receivedNotifications++; + await check_guid_for_uri( + Services.io.newURI(event.url), + event.pageGuid + ); + Assert.equal( + event.isPartialVisistsRemoval, + currentTest.expectedIsPartialRemoval, + "Should have the correct flag setting for partial removal" + ); + } + } + PlacesObservers.removeListener(["page-removed"], listener); + resolve(); + }; + PlacesObservers.addListener(["page-removed"], listener); + }); + + // Expire now. + await promiseForceExpirationStep(currentTest.limitExpiration); + await notificationsHandled; + + Assert.equal( + currentTest.receivedNotifications, + currentTest.expectedNotifications + ); + + // Clean up. + await PlacesUtils.bookmarks.eraseEverything(); + await PlacesUtils.history.clear(); + } + + clearMaxPages(); + await PlacesUtils.bookmarks.eraseEverything(); + await PlacesUtils.history.clear(); +}); diff --git a/toolkit/components/places/tests/expiration/test_notifications_pageRemoved_fromStore.js b/toolkit/components/places/tests/expiration/test_notifications_pageRemoved_fromStore.js new file mode 100644 index 0000000000..c8f7cf4aa0 --- /dev/null +++ b/toolkit/components/places/tests/expiration/test_notifications_pageRemoved_fromStore.js @@ -0,0 +1,110 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * What this is aimed to test: + * + * Expiring a full page should fire an page-removed event notification. + */ + +var tests = [ + { + desc: "Add 1 bookmarked page.", + addPages: 1, + addBookmarks: 1, + expectedNotifications: 0, // No expirable pages. + }, + + { + desc: "Add 2 pages, 1 bookmarked.", + addPages: 2, + addBookmarks: 1, + expectedNotifications: 1, // Only one expirable page. + }, + + { + desc: "Add 10 pages, none bookmarked.", + addPages: 10, + addBookmarks: 0, + expectedNotifications: 10, // Will expire everything. + }, + + { + desc: "Add 10 pages, all bookmarked.", + addPages: 10, + addBookmarks: 10, + expectedNotifications: 0, // No expirable pages. + }, +]; + +add_task(async () => { + // Set interval to a large value so we don't expire on it. + setInterval(3600); // 1h + + // Expire anything that is expirable. + setMaxPages(0); + + for (let testIndex = 1; testIndex <= tests.length; testIndex++) { + let currentTest = tests[testIndex - 1]; + print("\nTEST " + testIndex + ": " + currentTest.desc); + currentTest.receivedNotifications = 0; + + // Setup visits. + let now = getExpirablePRTime(); + for (let i = 0; i < currentTest.addPages; i++) { + let page = "http://" + testIndex + "." + i + ".mozilla.org/"; + await PlacesTestUtils.addVisits({ uri: uri(page), visitDate: now++ }); + } + + // Setup bookmarks. + currentTest.bookmarks = []; + for (let i = 0; i < currentTest.addBookmarks; i++) { + let page = "http://" + testIndex + "." + i + ".mozilla.org/"; + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + title: null, + url: page, + }); + currentTest.bookmarks.push(page); + } + + // Observe history. + const listener = events => { + for (const event of events) { + Assert.equal(event.type, "page-removed"); + + if (!event.isRemovedFromStore) { + continue; + } + + currentTest.receivedNotifications++; + // Check this uri was not bookmarked. + Assert.equal(currentTest.bookmarks.indexOf(event.url), -1); + do_check_valid_places_guid(event.pageGuid); + Assert.equal(event.reason, PlacesVisitRemoved.REASON_EXPIRED); + } + }; + PlacesObservers.addListener(["page-removed"], listener); + + // Expire now. + await promiseForceExpirationStep(-1); + + PlacesObservers.removeListener(["page-removed"], listener); + + Assert.equal( + currentTest.receivedNotifications, + currentTest.expectedNotifications + ); + + // Clean up. + await PlacesUtils.bookmarks.eraseEverything(); + await PlacesUtils.history.clear(); + } + + clearMaxPages(); + await PlacesUtils.bookmarks.eraseEverything(); + await PlacesUtils.history.clear(); +}); diff --git a/toolkit/components/places/tests/expiration/test_pref_interval.js b/toolkit/components/places/tests/expiration/test_pref_interval.js new file mode 100644 index 0000000000..5bf340e7c4 --- /dev/null +++ b/toolkit/components/places/tests/expiration/test_pref_interval.js @@ -0,0 +1,62 @@ +/** + * What this is aimed to test: + * + * Expiration relies on an interval, that is user-preffable setting + * "places.history.expiration.interval_seconds". + * On pref change it will stop current interval timer and fire a new one, + * that will obey the new value. + * If the pref is set to a number <= 0 we will use the default value. + */ + +// Default timer value for expiration in seconds. Must have same value as +// PREF_INTERVAL_SECONDS_NOTSET in nsPlacesExpiration. +const DEFAULT_TIMER_DELAY_SECONDS = 3 * 60; + +// Sync this with the const value in the component. +const EXPIRE_AGGRESSIVITY_MULTIPLIER = 3; + +var tests = [ + { + desc: "Set interval to 1s.", + interval: 1, + expectedTimerDelay: 1 * EXPIRE_AGGRESSIVITY_MULTIPLIER, + }, + + { + desc: "Set interval to a negative value.", + interval: -1, + expectedTimerDelay: + DEFAULT_TIMER_DELAY_SECONDS * EXPIRE_AGGRESSIVITY_MULTIPLIER, + }, + + { + desc: "Set interval to 0.", + interval: 0, + expectedTimerDelay: + DEFAULT_TIMER_DELAY_SECONDS * EXPIRE_AGGRESSIVITY_MULTIPLIER, + }, + + { + desc: "Set interval to a large value.", + interval: 100, + expectedTimerDelay: 100 * EXPIRE_AGGRESSIVITY_MULTIPLIER, + }, +]; + +add_task(async function test() { + // The pref should not exist by default. + Assert.throws(() => getInterval(), /NS_ERROR_UNEXPECTED/); + + // Force the component, so it will start observing preferences. + force_expiration_start(); + + for (let currentTest of tests) { + currentTest = tests.shift(); + print(currentTest.desc); + let promise = promiseTopicObserved("test-interval-changed"); + setInterval(currentTest.interval); + let [, data] = await promise; + Assert.equal(data, currentTest.expectedTimerDelay); + } + clearInterval(); +}); diff --git a/toolkit/components/places/tests/expiration/test_pref_maxpages.js b/toolkit/components/places/tests/expiration/test_pref_maxpages.js new file mode 100644 index 0000000000..e4583359f1 --- /dev/null +++ b/toolkit/components/places/tests/expiration/test_pref_maxpages.js @@ -0,0 +1,116 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * What this is aimed to test: + * + * Expiration will obey to hardware spec, but user can set a custom maximum + * number of pages to retain, to restrict history, through + * "places.history.expiration.max_pages". + * This limit is used at next expiration run. + * If the pref is set to a number < 0 we will use the default value. + */ + +var tests = [ + { + desc: "Set max_pages to a negative value, with 1 page.", + maxPages: -1, + addPages: 1, + expectedNotifications: 0, // Will ignore and won't expire anything. + }, + + { + desc: "Set max_pages to 0.", + maxPages: 0, + addPages: 1, + expectedNotifications: 1, + }, + + { + desc: "Set max_pages to 0, with 2 pages.", + maxPages: 0, + addPages: 2, + expectedNotifications: 2, // Will expire everything. + }, + + // Notice if we are over limit we do a full step of expiration. So we ensure + // that we will expire if we are over the limit, but we don't ensure that we + // will expire exactly up to the limit. Thus in this case we expire + // everything. + { + desc: "Set max_pages to 1 with 2 pages.", + maxPages: 1, + addPages: 2, + expectedNotifications: 2, // Will expire everything (in this case). + }, + + { + desc: "Set max_pages to 10, with 9 pages.", + maxPages: 10, + addPages: 9, + expectedNotifications: 0, // We are at the limit, won't expire anything. + }, + + { + desc: "Set max_pages to 10 with 10 pages.", + maxPages: 10, + addPages: 10, + expectedNotifications: 0, // We are below the limit, won't expire anything. + }, +]; + +add_task(async function test_pref_maxpages() { + // The pref should not exist by default. + try { + getMaxPages(); + do_throw("interval pref should not exist by default"); + } catch (ex) {} + + // Set interval to a large value so we don't expire on it. + setInterval(3600); // 1h + + for (let testIndex = 1; testIndex <= tests.length; testIndex++) { + let currentTest = tests[testIndex - 1]; + print("\nTEST " + testIndex + ": " + currentTest.desc); + currentTest.receivedNotifications = 0; + + // Setup visits. + let now = getExpirablePRTime(); + for (let i = 0; i < currentTest.addPages; i++) { + let page = "http://" + testIndex + "." + i + ".mozilla.org/"; + await PlacesTestUtils.addVisits({ uri: uri(page), visitDate: now-- }); + } + + const listener = events => { + for (const event of events) { + print("page-removed " + event.url); + Assert.equal(event.type, "page-removed"); + Assert.ok(event.isRemovedFromStore); + Assert.equal(event.reason, PlacesVisitRemoved.REASON_EXPIRED); + currentTest.receivedNotifications++; + } + }; + PlacesObservers.addListener(["page-removed"], listener); + + setMaxPages(currentTest.maxPages); + + // Expire now. + await promiseForceExpirationStep(-1); + + PlacesObservers.removeListener(["page-removed"], listener); + + Assert.equal( + currentTest.receivedNotifications, + currentTest.expectedNotifications + ); + + // Clean up. + await PlacesUtils.history.clear(); + } + + clearMaxPages(); + await PlacesUtils.history.clear(); +}); diff --git a/toolkit/components/places/tests/expiration/xpcshell.toml b/toolkit/components/places/tests/expiration/xpcshell.toml new file mode 100644 index 0000000000..dc9dc7e619 --- /dev/null +++ b/toolkit/components/places/tests/expiration/xpcshell.toml @@ -0,0 +1,23 @@ +[DEFAULT] +head = "head_expiration.js" +skip-if = ["os == 'android'"] + +["test_annos_expire_never.js"] + +["test_clearHistory.js"] + +["test_debug_expiration.js"] + +["test_idle_daily.js"] + +["test_interactions_expiration.js"] + +["test_notifications.js"] + +["test_notifications_pageRemoved_allVisits.js"] + +["test_notifications_pageRemoved_fromStore.js"] + +["test_pref_interval.js"] + +["test_pref_maxpages.js"] |