summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/tests/expiration
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /toolkit/components/places/tests/expiration
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/places/tests/expiration')
-rw-r--r--toolkit/components/places/tests/expiration/head_expiration.js112
-rw-r--r--toolkit/components/places/tests/expiration/test_annos_expire_never.js72
-rw-r--r--toolkit/components/places/tests/expiration/test_clearHistory.js57
-rw-r--r--toolkit/components/places/tests/expiration/test_debug_expiration.js405
-rw-r--r--toolkit/components/places/tests/expiration/test_idle_daily.js22
-rw-r--r--toolkit/components/places/tests/expiration/test_interactions_expiration.js102
-rw-r--r--toolkit/components/places/tests/expiration/test_notifications.js36
-rw-r--r--toolkit/components/places/tests/expiration/test_notifications_pageRemoved_allVisits.js150
-rw-r--r--toolkit/components/places/tests/expiration/test_notifications_pageRemoved_fromStore.js110
-rw-r--r--toolkit/components/places/tests/expiration/test_pref_interval.js62
-rw-r--r--toolkit/components/places/tests/expiration/test_pref_maxpages.js116
-rw-r--r--toolkit/components/places/tests/expiration/xpcshell.ini14
12 files changed, 1258 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..3509386444
--- /dev/null
+++ b/toolkit/components/places/tests/expiration/test_debug_expiration.js
@@ -0,0 +1,405 @@
+/* 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.
+ */
+
+var gNow = getExpirablePRTime(60);
+
+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: gNow++,
+ });
+ await PlacesTestUtils.addVisits({
+ uri: uri("http://page2.mozilla.org/"),
+ visitDate: gNow++,
+ });
+ // 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: gNow++,
+ });
+ await PlacesTestUtils.addVisits({
+ uri: uri("http://page2.mozilla.org/"),
+ visitDate: gNow++,
+ });
+ // 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: gNow++,
+ },
+ {
+ // Should not be expired cause we limit 1
+ uri: "http://new.mozilla.org/",
+ visitDate: gNow++,
+ },
+ ]);
+
+ // 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_limited_longurl() {
+ let longurl = "http://long.mozilla.org/" + "a".repeat(232);
+ await PlacesTestUtils.addVisits([
+ {
+ // Should be expired cause it's the oldest visit
+ uri: "http://old.mozilla.org/",
+ visitDate: gNow++,
+ },
+ {
+ // Should be expired cause it's a long url older than 60 days.
+ uri: longurl,
+ visitDate: gNow++,
+ },
+ {
+ // Should not be expired cause younger than 60 days.
+ uri: longurl,
+ visitDate: getExpirablePRTime(58),
+ },
+ ]);
+
+ await promiseForceExpirationStep(1);
+
+ // Check that some visits survived.
+ Assert.equal(visits_in_database(longurl), 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_limited_exoticurl() {
+ await PlacesTestUtils.addVisits([
+ {
+ // Should be expired cause it's the oldest visit
+ uri: "http://old.mozilla.org/",
+ visitDate: gNow++,
+ },
+ {
+ // Should be expired cause it's a long url older than 60 days.
+ uri: "http://download.mozilla.org",
+ visitDate: gNow++,
+ transition: 7,
+ },
+ {
+ // Should not be expired cause younger than 60 days.
+ uri: "http://nonexpirable-download.mozilla.org",
+ visitDate: getExpirablePRTime(58),
+ 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_unlimited() {
+ let longurl = "http://long.mozilla.org/" + "a".repeat(232);
+ await PlacesTestUtils.addVisits([
+ {
+ uri: "http://old.mozilla.org/",
+ visitDate: gNow++,
+ },
+ {
+ uri: "http://new.mozilla.org/",
+ visitDate: gNow++,
+ },
+ // Add expirable visits.
+ {
+ uri: "http://download.mozilla.org/",
+ visitDate: gNow++,
+ transition: PlacesUtils.history.TRANSITION_DOWNLOAD,
+ },
+ {
+ uri: longurl,
+ visitDate: gNow++,
+ },
+
+ // 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: "Expired because it redirects",
+ page: "http://source.old.org/",
+ icon: "http://source.old.org/test_icon.png",
+ iconExpired: true,
+ redirect: "http://dest.old.org/",
+ removed: true,
+ },
+ {
+ desc: "Not expired because recent",
+ page: "http://source.new.org/",
+ icon: "http://source.new.org/test_icon.png",
+ iconExpired: false,
+ redirect: "http://dest.new.org/",
+ removed: false,
+ },
+ {
+ desc: "Not expired because does not match, even if old",
+ page: "http://stay.moz.org/",
+ icon: "http://stay.moz.org/test_icon.png",
+ iconExpired: true,
+ removed: false,
+ },
+ {
+ desc: "Not expired because does not have a root icon, even if old",
+ page: "http://noroot.ref.org/#test",
+ icon: "http://noroot.ref.org/test_icon.png",
+ iconExpired: true,
+ removed: false,
+ },
+ {
+ desc: "Expired because has a root icon",
+ page: "http://root.ref.org/#test",
+ icon: "http://root.ref.org/test_icon.png",
+ root: "http://root.ref.org/favicon.ico",
+ iconExpired: true,
+ removed: true,
+ },
+ {
+ desc: "Not expired because recent",
+ page: "http://new.ref.org/#test",
+ icon: "http://new.ref.org/test_icon.png",
+ iconExpired: false,
+ root: "http://new.ref.org/favicon.ico",
+ 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.redirect) {
+ await PlacesTestUtils.addVisits(entry.page);
+ await PlacesTestUtils.addVisits({
+ uri: entry.redirect,
+ transition: TRANSITION_REDIRECT_PERMANENT,
+ referrer: entry.page,
+ });
+ } else if (!entry.skipHistory) {
+ await PlacesTestUtils.addVisits(entry.page);
+ }
+
+ 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 SET expire_ms = 1 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 }
+ );
+ }
+ });
+ }
+ }
+
+ info("Run expiration");
+ await promiseForceExpirationStep(-1);
+
+ info("Check expiration");
+ // Remove the root icons before checking the associated icons have been expired.
+ await PlacesUtils.withConnectionWrapper("test_debug_expiration.js", db =>
+ db.execute(`DELETE FROM moz_icons WHERE root = 1`)
+ );
+ 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.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();
+});
+
+function run_test() {
+ // 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);
+
+ run_next_test();
+}
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..07108617c7
--- /dev/null
+++ b/toolkit/components/places/tests/expiration/test_notifications_pageRemoved_allVisits.js
@@ -0,0 +1,150 @@
+/* -*- 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];
+ print("\nTEST " + 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.
+ const listener = 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++;
+ do_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.addListener(["page-removed"], listener);
+
+ // Expire now.
+ await promiseForceExpirationStep(currentTest.limitExpiration);
+
+ 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_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.ini b/toolkit/components/places/tests/expiration/xpcshell.ini
new file mode 100644
index 0000000000..72d276685c
--- /dev/null
+++ b/toolkit/components/places/tests/expiration/xpcshell.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+head = head_expiration.js
+skip-if = toolkit == '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]