summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js')
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js307
1 files changed, 307 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js
new file mode 100644
index 0000000000..5ede57a08b
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js
@@ -0,0 +1,307 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+let btp;
+let bounceTrackingGracePeriodSec;
+let bounceTrackingActivationLifetimeSec;
+
+/**
+ * Adds brackets to a host if it's an IPv6 address.
+ * @param {string} host - Host which may be an IPv6.
+ * @returns {string} bracketed IPv6 or host if host is not an IPv6.
+ */
+function maybeFixupIpv6(host) {
+ if (!host.includes(":")) {
+ return host;
+ }
+ return `[${host}]`;
+}
+
+/**
+ * Adds cookies and indexedDB test data for the given host.
+ * @param {string} host
+ */
+async function addStateForHost(host) {
+ info(`adding state for host ${host}`);
+ SiteDataTestUtils.addToCookies({ host });
+ await SiteDataTestUtils.addToIndexedDB(`https://${maybeFixupIpv6(host)}`);
+}
+
+/**
+ * Checks if the given host as cookies or indexedDB data.
+ * @param {string} host
+ * @returns {boolean}
+ */
+async function hasStateForHost(host) {
+ let origin = `https://${maybeFixupIpv6(host)}`;
+ if (SiteDataTestUtils.hasCookies(origin)) {
+ return true;
+ }
+ return SiteDataTestUtils.hasIndexedDB(origin);
+}
+
+/**
+ * Assert that there are no bounce tracker candidates or user activations
+ * recorded.
+ */
+function assertEmpty() {
+ Assert.equal(
+ btp.testGetBounceTrackerCandidateHosts({}).length,
+ 0,
+ "No tracker candidates."
+ );
+ Assert.equal(
+ btp.testGetUserActivationHosts({}).length,
+ 0,
+ "No user activation hosts."
+ );
+}
+
+add_setup(function () {
+ // Need a profile to data clearing calls.
+ do_get_profile();
+
+ btp = Cc["@mozilla.org/bounce-tracking-protection;1"].getService(
+ Ci.nsIBounceTrackingProtection
+ );
+
+ // Reset global bounce tracking state.
+ btp.clearAll();
+
+ bounceTrackingGracePeriodSec = Services.prefs.getIntPref(
+ "privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec"
+ );
+ bounceTrackingActivationLifetimeSec = Services.prefs.getIntPref(
+ "privacy.bounceTrackingProtection.bounceTrackingActivationLifetimeSec"
+ );
+});
+
+/**
+ * When both maps are empty running PurgeBounceTrackers should be a no-op.
+ */
+add_task(async function test_empty() {
+ assertEmpty();
+
+ info("Run PurgeBounceTrackers");
+ await btp.testRunPurgeBounceTrackers();
+
+ assertEmpty();
+});
+
+/**
+ * Tests that the PurgeBounceTrackers behaves as expected by adding site state
+ * and adding simulated bounce state and user activations.
+ */
+add_task(async function test_purge() {
+ let now = Date.now();
+
+ // Epoch in MS.
+ let timestampWithinGracePeriod =
+ now - (bounceTrackingGracePeriodSec * 1000) / 2;
+ let timestampWithinGracePeriod2 =
+ now - (bounceTrackingGracePeriodSec * 1000) / 4;
+ let timestampOutsideGracePeriodFiveSeconds =
+ now - (bounceTrackingGracePeriodSec + 5) * 1000;
+ let timestampOutsideGracePeriodThreeDays =
+ now - (bounceTrackingGracePeriodSec + 60 * 60 * 24 * 3) * 1000;
+ let timestampFuture = now + bounceTrackingGracePeriodSec * 1000 * 2;
+
+ let timestampValidUserActivation =
+ now - (bounceTrackingActivationLifetimeSec * 1000) / 2;
+ let timestampExpiredUserActivationFourSeconds =
+ now - (bounceTrackingActivationLifetimeSec + 4) * 1000;
+ let timestampExpiredUserActivationTenDays =
+ now - (bounceTrackingActivationLifetimeSec + 60 * 60 * 24 * 10) * 1000;
+
+ const TEST_TRACKERS = {
+ "example.com": {
+ bounceTime: timestampWithinGracePeriod,
+ userActivationTime: null,
+ message: "Should not purge within grace period.",
+ shouldPurge: bounceTrackingGracePeriodSec == 0,
+ },
+ "example2.com": {
+ bounceTime: timestampWithinGracePeriod2,
+ userActivationTime: null,
+ message: "Should not purge within grace period (2).",
+ shouldPurge: bounceTrackingGracePeriodSec == 0,
+ },
+ "example.net": {
+ bounceTime: timestampOutsideGracePeriodFiveSeconds,
+ userActivationTime: null,
+ message: "Should purge after grace period.",
+ shouldPurge: true,
+ },
+ // Also ensure that clear data calls with IP sites succeed.
+ "1.2.3.4": {
+ bounceTime: timestampOutsideGracePeriodThreeDays,
+ userActivationTime: null,
+ message: "Should purge after grace period (2).",
+ shouldPurge: true,
+ },
+ "2606:4700:4700::1111": {
+ bounceTime: timestampOutsideGracePeriodThreeDays,
+ userActivationTime: null,
+ message: "Should purge after grace period (3).",
+ shouldPurge: true,
+ },
+ "example.org": {
+ bounceTime: timestampWithinGracePeriod,
+ userActivationTime: null,
+ message: "Should not purge within grace period.",
+ shouldPurge: false,
+ },
+ "example2.org": {
+ bounceTime: timestampFuture,
+ userActivationTime: null,
+ message: "Should not purge for future bounce time (within grace period).",
+ shouldPurge: false,
+ },
+ "1.1.1.1": {
+ bounceTime: null,
+ userActivationTime: timestampValidUserActivation,
+ message: "Should not purge without bounce (valid user activation).",
+ shouldPurge: false,
+ },
+ // Also testing domains with trailing ".".
+ "mozilla.org.": {
+ bounceTime: null,
+ userActivationTime: timestampExpiredUserActivationFourSeconds,
+ message: "Should not purge without bounce (expired user activation).",
+ shouldPurge: false,
+ },
+ "firefox.com": {
+ bounceTime: null,
+ userActivationTime: timestampExpiredUserActivationTenDays,
+ message: "Should not purge without bounce (expired user activation) (2).",
+ shouldPurge: false,
+ },
+ };
+
+ info("Assert empty initially.");
+ assertEmpty();
+
+ info("Populate bounce and user activation sets.");
+
+ let expectedBounceTrackerHosts = [];
+ let expectedUserActivationHosts = [];
+
+ let expiredUserActivationHosts = [];
+ let expectedPurgedHosts = [];
+
+ // This would normally happen over time while browsing.
+ let initPromises = Object.entries(TEST_TRACKERS).map(
+ async ([siteHost, { bounceTime, userActivationTime, shouldPurge }]) => {
+ // Add site state so we can later assert it has been purged.
+ await addStateForHost(siteHost);
+
+ if (bounceTime != null) {
+ if (userActivationTime != null) {
+ throw new Error(
+ "Attempting to construct invalid map state. testGetBounceTrackerCandidateHosts({}) and testGetUserActivationHosts({}) must be disjoint."
+ );
+ }
+
+ expectedBounceTrackerHosts.push(siteHost);
+
+ // Convert bounceTime timestamp to nanoseconds (PRTime).
+ info(
+ `Adding bounce. siteHost: ${siteHost}, bounceTime: ${bounceTime} ms`
+ );
+ btp.testAddBounceTrackerCandidate({}, siteHost, bounceTime * 1000);
+ }
+
+ if (userActivationTime != null) {
+ if (bounceTime != null) {
+ throw new Error(
+ "Attempting to construct invalid map state. testGetBounceTrackerCandidateHosts({}) and testGetUserActivationHosts({}) must be disjoint."
+ );
+ }
+
+ expectedUserActivationHosts.push(siteHost);
+ if (
+ userActivationTime + bounceTrackingActivationLifetimeSec * 1000 >
+ now
+ ) {
+ expiredUserActivationHosts.push(siteHost);
+ }
+
+ // Convert userActivationTime timestamp to nanoseconds (PRTime).
+ info(
+ `Adding user interaction. siteHost: ${siteHost}, userActivationTime: ${userActivationTime} ms`
+ );
+ btp.testAddUserActivation({}, siteHost, userActivationTime * 1000);
+ }
+
+ if (shouldPurge) {
+ expectedPurgedHosts.push(siteHost);
+ }
+ }
+ );
+ await Promise.all(initPromises);
+
+ info(
+ "Check that bounce and user activation data has been correctly recorded."
+ );
+ Assert.deepEqual(
+ btp.testGetBounceTrackerCandidateHosts({}).sort(),
+ expectedBounceTrackerHosts.sort(),
+ "Has added bounce tracker hosts."
+ );
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts({}).sort(),
+ expectedUserActivationHosts.sort(),
+ "Has added user activation hosts."
+ );
+
+ info("Run PurgeBounceTrackers");
+ let actualPurgedHosts = await btp.testRunPurgeBounceTrackers();
+
+ Assert.deepEqual(
+ actualPurgedHosts.sort(),
+ expectedPurgedHosts.sort(),
+ "Should have purged all expected hosts."
+ );
+
+ let expectedBounceTrackerHostsAfterPurge = expectedBounceTrackerHosts
+ .filter(host => !expectedPurgedHosts.includes(host))
+ .sort();
+ Assert.deepEqual(
+ btp.testGetBounceTrackerCandidateHosts({}).sort(),
+ expectedBounceTrackerHostsAfterPurge.sort(),
+ "After purge the bounce tracker candidate host set should be updated correctly."
+ );
+
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts({}).sort(),
+ expiredUserActivationHosts.sort(),
+ "After purge any expired user activation records should have been removed"
+ );
+
+ info("Test that we actually purged the correct sites.");
+ for (let siteHost of expectedPurgedHosts) {
+ Assert.ok(
+ !(await hasStateForHost(siteHost)),
+ `Site ${siteHost} should no longer have state.`
+ );
+ }
+ for (let siteHost of expectedBounceTrackerHostsAfterPurge) {
+ Assert.ok(
+ await hasStateForHost(siteHost),
+ `Site ${siteHost} should still have state.`
+ );
+ }
+
+ info("Reset bounce tracking state.");
+ btp.clearAll();
+ assertEmpty();
+
+ info("Clean up site data.");
+ await SiteDataTestUtils.clear();
+});