Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"use strict";
const { SiteDataTestUtils } = ChromeUtils.importESModule(
);
const { PermissionTestUtils } = ChromeUtils.importESModule(
);
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(`Populating cookies and indexedDB 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,
},
// Don't purge if the site is allowlisted.
// Allowlist via "trackingprotection" permission (ETP toggle).
"example2.net": {
bounceTime: timestampOutsideGracePeriodFiveSeconds,
userActivationTime: null,
allowListedPerm: "trackingprotection",
message:
"Should not purge after grace period if allowlisted via 'trackingprotection' permission.",
shouldPurge: false,
},
// Allowlist via "cookie" permission
"example3.net": {
bounceTime: timestampOutsideGracePeriodFiveSeconds,
userActivationTime: null,
allowListedPerm: "cookie",
permissionState: Services.perms.ALLOW_ACTION,
message:
"Should not purge after grace period if allowlisted via 'cookie' permission.",
shouldPurge: false,
},
// "cookie" permission but not allowlisted (deny action).
"example4.net": {
bounceTime: timestampOutsideGracePeriodFiveSeconds,
userActivationTime: null,
allowListedPerm: "cookie",
permissionState: Services.perms.DENY_ACTION,
message:
"Should get purged outside of grace period with a 'cookie' DENY permission.",
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 allowListedHosts = [];
let expiredUserActivationHosts = [];
let expectedPurgedHosts = [];
// This would normally happen over time while browsing.
let initPromises = Object.entries(TEST_TRACKERS).map(
async ([
siteHost,
{
message,
bounceTime,
userActivationTime,
allowListedPerm,
permissionState = Services.perms.ALLOW_ACTION,
shouldPurge,
},
]) => {
info(`Initializing state for test ${siteHost}: ${message}`);
// Add site state so we can later assert it has been purged.
await addStateForHost(siteHost);
// Add allowlist entry if needed.
if (allowListedPerm == "trackingprotection") {
PermissionTestUtils.add(
`https://${siteHost}`,
"trackingprotection",
permissionState
);
allowListedHosts.push(siteHost);
} else if (allowListedPerm == "cookie") {
PermissionTestUtils.add(
`https://${siteHost}`,
"cookie",
permissionState
);
allowListedHosts.push(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 microseconds (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 microseconds (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({})
.map(entry => entry.siteHost)
.sort(),
expectedBounceTrackerHosts.sort(),
"Has added bounce tracker hosts."
);
Assert.deepEqual(
btp
.testGetUserActivationHosts({})
.map(entry => entry.siteHost)
.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."
);
// After the purge only the bounce trackers that have not been purged should
// remain in the candidate map. Additionally the allowlisted hosts should be
// removed from the candidate map.
let expectedBounceTrackerHostsAfterPurge = expectedBounceTrackerHosts
.filter(
host =>
!expectedPurgedHosts.includes(host) && !allowListedHosts.includes(host)
)
.sort();
Assert.deepEqual(
btp
.testGetBounceTrackerCandidateHosts({})
.map(entry => entry.siteHost)
.sort(),
expectedBounceTrackerHostsAfterPurge.sort(),
"After purge the bounce tracker candidate host set should be updated correctly."
);
Assert.deepEqual(
btp
.testGetUserActivationHosts({})
.map(entry => entry.siteHost)
.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 and permissions.");
await SiteDataTestUtils.clear();
Services.perms.removeAll();
});