summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/bouncetrackingprotection/test
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/antitracking/bouncetrackingprotection/test')
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser.toml16
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_dry_run.js101
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_popup.js126
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js45
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_schemes.js75
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_simple.js10
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_cookies.js (renamed from toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful.js)23
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_storage.js54
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_web_worker.js38
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_telemetry_purge_duration.js66
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.html187
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_web_worker.js40
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js174
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/marionette/test_bouncetracking_storage_persistence.py6
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js10
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_importUserActivationPermissions.js153
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js62
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml3
18 files changed, 1074 insertions, 115 deletions
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser.toml b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser.toml
index 1c44d7804e..0e8a01db4a 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser.toml
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser.toml
@@ -4,17 +4,31 @@ prefs = [
"privacy.bounceTrackingProtection.enabled=true",
"privacy.bounceTrackingProtection.enableTestMode=true",
"privacy.bounceTrackingProtection.bounceTrackingPurgeTimerPeriodSec=0",
+ "privacy.bounceTrackingProtection.enableDryRunMode=false",
]
support-files = [
"file_start.html",
"file_bounce.sjs",
"file_bounce.html",
+ "file_web_worker.js",
]
+["browser_bouncetracking_dry_run.js"]
+
["browser_bouncetracking_oa_isolation.js"]
+["browser_bouncetracking_popup.js"]
+
["browser_bouncetracking_purge.js"]
+["browser_bouncetracking_schemes.js"]
+
["browser_bouncetracking_simple.js"]
-["browser_bouncetracking_stateful.js"]
+["browser_bouncetracking_stateful_cookies.js"]
+
+["browser_bouncetracking_stateful_storage.js"]
+
+["browser_bouncetracking_stateful_web_worker.js"]
+
+["browser_bouncetracking_telemetry_purge_duration.js"]
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_dry_run.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_dry_run.js
new file mode 100644
index 0000000000..627a5f233a
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_dry_run.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_ORIGIN = "https://itisatracker.org";
+const TEST_BASE_DOMAIN = "itisatracker.org";
+
+async function runPurgeTest(expectPurge) {
+ ok(!SiteDataTestUtils.hasCookies(TEST_ORIGIN), "No cookies initially.");
+
+ // Adding a cookie that should later be purged.
+ info("Add a test cookie to be purged later.");
+ SiteDataTestUtils.addToCookies({ origin: TEST_ORIGIN });
+ ok(SiteDataTestUtils.hasCookies(TEST_ORIGIN), "Cookie added.");
+
+ // The bounce adds localStorage. Test that there is none initially.
+ ok(
+ !SiteDataTestUtils.hasLocalStorage(TEST_ORIGIN),
+ "No localStorage initially."
+ );
+
+ info("Test client bounce with cookie.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: "localStorage",
+ skipSiteDataCleanup: true,
+ postBounceCallback: () => {
+ info(
+ "Test that after the bounce but before purging cookies and localStorage are present."
+ );
+ ok(SiteDataTestUtils.hasCookies(TEST_ORIGIN), "Cookies not purged.");
+ ok(
+ SiteDataTestUtils.hasLocalStorage(TEST_ORIGIN),
+ "localStorage not purged."
+ );
+
+ Assert.deepEqual(
+ bounceTrackingProtection
+ .testGetBounceTrackerCandidateHosts({})
+ .map(entry => entry.siteHost),
+ [TEST_BASE_DOMAIN],
+ `Bounce tracker candidate '${TEST_BASE_DOMAIN}' added`
+ );
+ },
+ });
+
+ if (expectPurge) {
+ info("After purging the site shouldn't have any data.");
+ ok(!SiteDataTestUtils.hasCookies(TEST_ORIGIN), "Cookies purged.");
+ ok(!SiteDataTestUtils.hasLocalStorage(TEST_ORIGIN), "localStorage purged.");
+ } else {
+ info("Purging did not run meaning the site should still have data.");
+
+ ok(SiteDataTestUtils.hasCookies(TEST_ORIGIN), "Cookies still set.");
+ ok(
+ SiteDataTestUtils.hasLocalStorage(TEST_ORIGIN),
+ "localStorage still set."
+ );
+ }
+
+ info(
+ "Candidates should have been removed after running the purging algorithm. This is true for both regular and dry-run mode where we pretend to purge."
+ );
+ Assert.deepEqual(
+ bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}),
+ [],
+ "No bounce tracker candidates after purging."
+ );
+
+ // Cleanup.
+ bounceTrackingProtection.clearAll();
+ await SiteDataTestUtils.clear();
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.bounceTrackingProtection.requireStatefulBounces", true],
+ ["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
+ // Required to use SiteDataTestUtils localStorage helpers.
+ ["dom.storage.client_validation", false],
+ ],
+ });
+});
+
+add_task(async function test_purge_in_regular_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.bounceTrackingProtection.enableDryRunMode", false]],
+ });
+
+ await runPurgeTest(true);
+});
+
+add_task(async function test_purge_in_dry_run_mode() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.bounceTrackingProtection.enableDryRunMode", true]],
+ });
+
+ await runPurgeTest(false);
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_popup.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_popup.js
new file mode 100644
index 0000000000..9e6fa8caf1
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_popup.js
@@ -0,0 +1,126 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let bounceTrackingProtection;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.bounceTrackingProtection.requireStatefulBounces", true],
+ ["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
+ ],
+ });
+ bounceTrackingProtection = Cc[
+ "@mozilla.org/bounce-tracking-protection;1"
+ ].getService(Ci.nsIBounceTrackingProtection);
+});
+
+async function runTest(spawnWindowType) {
+ if (!spawnWindowType || !["newTab", "popup"].includes(spawnWindowType)) {
+ throw new Error(`Invalid option '${spawnWindowType}' for spawnWindowType`);
+ }
+
+ Assert.equal(
+ bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).length,
+ 0,
+ "No bounce tracker hosts initially."
+ );
+ Assert.equal(
+ bounceTrackingProtection.testGetUserActivationHosts({}).length,
+ 0,
+ "No user activation hosts initially."
+ );
+
+ // Spawn a tab with A, the start of the bounce chain.
+ await BrowserTestUtils.withNewTab(
+ getBaseUrl(ORIGIN_A) + "file_start.html",
+ async browser => {
+ // The destination site C to navigate to after the bounce.
+ let finalURL = new URL(getBaseUrl(ORIGIN_B) + "file_start.html");
+ // The middle hop in the bounce chain B that redirects to finalURL C.
+ let bounceURL = getBounceURL({
+ bounceType: "client",
+ targetURL: finalURL,
+ setState: "cookie-client",
+ });
+
+ // Register a promise for the new popup window. This resolves once the popup
+ // has opened and the final url (C) has been loaded.
+ let openPromise;
+
+ if (spawnWindowType == "newTab") {
+ openPromise = BrowserTestUtils.waitForNewTab(gBrowser, finalURL.href);
+ } else {
+ openPromise = BrowserTestUtils.waitForNewWindow({ url: finalURL.href });
+ }
+
+ // Navigate through the bounce chain by opening a popup to the bounce URL.
+ await navigateLinkClick(browser, bounceURL, {
+ spawnWindow: spawnWindowType,
+ });
+
+ let tabOrWindow = await openPromise;
+
+ let tabOrWindowBrowser;
+ if (spawnWindowType == "newTab") {
+ tabOrWindowBrowser = tabOrWindow.linkedBrowser;
+ } else {
+ tabOrWindowBrowser = tabOrWindow.gBrowser.selectedBrowser;
+ }
+
+ let promiseRecordBounces = waitForRecordBounces(tabOrWindowBrowser);
+
+ // Navigate again with user gesture which triggers
+ // BounceTrackingProtection::RecordStatefulBounces. We could rely on the
+ // timeout (mClientBounceDetectionTimeout) here but that can cause races
+ // in debug where the load is quite slow.
+ await navigateLinkClick(
+ tabOrWindowBrowser,
+ new URL(getBaseUrl(ORIGIN_C) + "file_start.html")
+ );
+
+ info("Wait for bounce trackers to be recorded.");
+ await promiseRecordBounces;
+
+ // Cleanup popup or tab.
+ if (spawnWindowType == "newTab") {
+ await BrowserTestUtils.removeTab(tabOrWindow);
+ } else {
+ await BrowserTestUtils.closeWindow(tabOrWindow);
+ }
+ }
+ );
+
+ // Check that the bounce tracker was detected.
+ Assert.deepEqual(
+ bounceTrackingProtection
+ .testGetBounceTrackerCandidateHosts({})
+ .map(entry => entry.siteHost),
+ [SITE_TRACKER],
+ "Bounce tracker in popup detected."
+ );
+
+ // Cleanup.
+ bounceTrackingProtection.clearAll();
+ await SiteDataTestUtils.clear();
+}
+
+/**
+ * Tests that bounce trackers which use popups as the first hop in the bounce
+ * chain can not bypass detection.
+ *
+ * A -> popup -> B -> C
+ *
+ * A opens a popup and loads B in it. B is the tracker that performs a
+ * short-lived redirect and C is the final destination.
+ */
+
+add_task(async function test_popup() {
+ await runTest("popup");
+});
+
+add_task(async function test_new_tab() {
+ await runTest("newTab");
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
index eedd374197..676ac5fe92 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
@@ -133,8 +133,21 @@ add_task(async function test_purging_skip_content_blocking_allow_list() {
"Should only purge example.net. example.org is within the grace period, example.com is allow-listed."
);
+ // example.net is removed because it is purged, example.com is removed because
+ // it is allow-listed.
+ Assert.deepEqual(
+ bounceTrackingProtection
+ .testGetBounceTrackerCandidateHosts({})
+ .map(entry => entry.siteHost),
+ ["example.org"],
+ "Should have removed example.net and example.com from bounce tracker candidate list."
+ );
+
+ info("Add example.com as a bounce tracker candidate again.");
+ bounceTrackingProtection.testAddBounceTrackerCandidate({}, "example.com", 1);
+
info(
- "Remove the allow-list entry for example.com and test that it gets purged now."
+ "Remove the allow-list entry for example.com and test that it gets purged now."
);
await BrowserTestUtils.withNewTab("https://example.com", async browser => {
@@ -146,6 +159,16 @@ add_task(async function test_purging_skip_content_blocking_allow_list() {
"example.com should have been purged now that it is no longer allow-listed."
);
+ // example.org is still in the grace period so it neither gets purged nor
+ // removed from the candidate list.
+ Assert.deepEqual(
+ bounceTrackingProtection
+ .testGetBounceTrackerCandidateHosts({})
+ .map(entry => entry.siteHost),
+ ["example.org"],
+ "Should have removed example.com from bounce tracker candidate list."
+ );
+
bounceTrackingProtection.clearAll();
});
@@ -166,8 +189,24 @@ add_task(
"Should only purge example.net. example.org is within the grace period, example.com is allow-listed via test1.example.com."
);
+ // example.net is removed because it is purged, example.com is removed because it is allow-listed.
+ Assert.deepEqual(
+ bounceTrackingProtection
+ .testGetBounceTrackerCandidateHosts({})
+ .map(entry => entry.siteHost),
+ ["example.org"],
+ "Should have removed example.net and example.com from bounce tracker candidate list."
+ );
+
+ info("Add example.com as a bounce tracker candidate again.");
+ bounceTrackingProtection.testAddBounceTrackerCandidate(
+ {},
+ "example.com",
+ 1
+ );
+
info(
- "Remove the allow-list entry for test1.example.com and test that it gets purged now."
+ "Remove the allow-list entry for test1.example.com and test that it gets purged now."
);
await BrowserTestUtils.withNewTab(
@@ -179,7 +218,7 @@ add_task(
Assert.deepEqual(
await bounceTrackingProtection.testRunPurgeBounceTrackers(),
["example.com"],
- "example.com should have been purged now that test1.example.com it is no longer allow-listed."
+ "example.com should have been purged now that test1.example.com is no longer allow-listed."
);
bounceTrackingProtection.clearAll();
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_schemes.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_schemes.js
new file mode 100644
index 0000000000..21b72cdc45
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_schemes.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let bounceTrackingProtection;
+
+add_setup(async function () {
+ bounceTrackingProtection = Cc[
+ "@mozilla.org/bounce-tracking-protection;1"
+ ].getService(Ci.nsIBounceTrackingProtection);
+ bounceTrackingProtection.clearAll();
+});
+
+async function testInteractWithSite(origin, expectRecorded) {
+ is(
+ bounceTrackingProtection.testGetUserActivationHosts({}).length,
+ 0,
+ "No user activation hosts initially"
+ );
+
+ let baseDomain;
+ let scheme;
+
+ await BrowserTestUtils.withNewTab(origin, async browser => {
+ baseDomain = browser.contentPrincipal.baseDomain;
+ scheme = browser.contentPrincipal.URI.scheme;
+
+ info(
+ `Trigger a user activation, which should ${
+ expectRecorded ? "" : "not "
+ }be recorded.`
+ );
+ // We intentionally turn off this a11y check, because the following click
+ // is purposefully sent on an arbitrary web content that is not expected
+ // to be tested by itself with the browser mochitests, therefore this rule
+ // check shall be ignored by a11y_checks suite.
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
+ await BrowserTestUtils.synthesizeMouseAtPoint(50, 50, {}, browser);
+ AccessibilityUtils.resetEnv();
+ });
+ if (expectRecorded) {
+ Assert.deepEqual(
+ bounceTrackingProtection
+ .testGetUserActivationHosts({})
+ .map(entry => entry.siteHost),
+ [baseDomain],
+ `User activation should be recorded for ${scheme} scheme.`
+ );
+ } else {
+ Assert.deepEqual(
+ bounceTrackingProtection.testGetUserActivationHosts({}),
+ [],
+ `User activation should not be recorded for ${scheme} scheme.`
+ );
+ }
+
+ bounceTrackingProtection.clearAll();
+}
+
+/**
+ * Test that we only record user activation for supported schemes.
+ */
+add_task(async function test_userActivationSchemes() {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await testInteractWithSite("http://example.com", true);
+ await testInteractWithSite("https://example.com", true);
+
+ await testInteractWithSite("about:blank", false);
+ await testInteractWithSite("about:robots", false);
+ await testInteractWithSite(
+ "file://" + Services.dirsvc.get("TmpD", Ci.nsIFile).path,
+ false
+ );
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_simple.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_simple.js
index dfbd4d0fc0..cdc21eb788 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_simple.js
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_simple.js
@@ -73,12 +73,18 @@ add_task(async function test_bounce_chain() {
await promiseRecordBounces;
Assert.deepEqual(
- bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).sort(),
+ bounceTrackingProtection
+ .testGetBounceTrackerCandidateHosts({})
+ .map(entry => entry.siteHost)
+ .sort(),
[SITE_TRACKER_B, SITE_TRACKER].sort(),
`Identified all bounce trackers in the redirect chain.`
);
Assert.deepEqual(
- bounceTrackingProtection.testGetUserActivationHosts({}).sort(),
+ bounceTrackingProtection
+ .testGetUserActivationHosts({})
+ .map(entry => entry.siteHost)
+ .sort(),
[SITE_A, SITE_B].sort(),
"Should only have user activation for sites where we clicked links."
);
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_cookies.js
index e7fb4521a7..d4940e668d 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful.js
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_cookies.js
@@ -34,6 +34,15 @@ add_task(async function test_bounce_stateful_cookies_client() {
});
});
+add_task(async function test_bounce_stateful_cookies_client_sameSiteFrame() {
+ info("Test client bounce with cookie set in same site frame.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: "cookie-client",
+ setStateSameSiteFrame: true,
+ });
+});
+
add_task(async function test_bounce_stateful_cookies_server() {
info("Test server bounce with cookie.");
await runTestBounce({
@@ -49,15 +58,11 @@ add_task(async function test_bounce_stateful_cookies_server() {
});
});
-// Storage tests.
-
-// TODO: Bug 1848406: Implement stateful bounce detection for localStorage.
-add_task(async function test_bounce_stateful_localStorage() {
- info("TODO: client bounce with localStorage.");
+add_task(async function test_bounce_stateful_cookies_server_sameSiteFrame() {
+ info("Test client bounce with cookie set in same site frame.");
await runTestBounce({
- bounceType: "client",
- setState: "localStorage",
- expectCandidate: false,
- expectPurge: false,
+ bounceType: "server",
+ setState: "cookie-server",
+ setStateSameSiteFrame: true,
});
});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_storage.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_storage.js
new file mode 100644
index 0000000000..ff9daabcdb
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_storage.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let bounceTrackingProtection;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.bounceTrackingProtection.requireStatefulBounces", true],
+ ["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
+ ],
+ });
+ bounceTrackingProtection = Cc[
+ "@mozilla.org/bounce-tracking-protection;1"
+ ].getService(Ci.nsIBounceTrackingProtection);
+});
+
+// Storage tests.
+
+add_task(async function test_bounce_stateful_localStorage() {
+ info("Client bounce with localStorage.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: "localStorage",
+ });
+});
+
+add_task(async function test_bounce_stateful_localStorage_sameSiteFrame() {
+ info("Client bounce with localStorage set in same site frame.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: "localStorage",
+ setStateSameSiteFrame: true,
+ });
+});
+
+add_task(async function test_bounce_stateful_indexedDB() {
+ info("Client bounce with indexedDB.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: "indexedDB",
+ });
+});
+
+add_task(async function test_bounce_stateful_indexedDB_sameSiteFrame() {
+ info("Client bounce with indexedDB populated in same site frame.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: "indexedDB",
+ setStateSameSiteFrame: true,
+ });
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_web_worker.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_web_worker.js
new file mode 100644
index 0000000000..1e6672e784
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful_web_worker.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let bounceTrackingProtection;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.bounceTrackingProtection.requireStatefulBounces", true],
+ ["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
+ ],
+ });
+ bounceTrackingProtection = Cc[
+ "@mozilla.org/bounce-tracking-protection;1"
+ ].getService(Ci.nsIBounceTrackingProtection);
+});
+
+add_task(async function test_bounce_stateful_indexedDB() {
+ info("Client bounce with indexedDB.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: "indexedDB",
+ setStateInWebWorker: true,
+ });
+});
+
+// FIXME: (Bug 1889898) This test is skipped because it triggers a shutdown
+// hang.
+add_task(async function test_bounce_stateful_indexedDB_nestedWorker() {
+ info("Client bounce with indexedDB access from a nested worker.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: "indexedDB",
+ setStateInNestedWebWorker: true,
+ });
+}).skip();
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_telemetry_purge_duration.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_telemetry_purge_duration.js
new file mode 100644
index 0000000000..74b1fdb30d
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_telemetry_purge_duration.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let bounceTrackingProtection;
+
+async function test_purge_duration(isDryRunMode) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.bounceTrackingProtection.enableDryRunMode", isDryRunMode]],
+ });
+
+ is(
+ Glean.bounceTrackingProtection.purgeDuration.testGetValue(),
+ null,
+ "Histogram should not exist initially."
+ );
+
+ info("Run server bounce with cookie.");
+ await runTestBounce({
+ bounceType: "server",
+ setState: "cookie-server",
+ postBounceCallback: () => {
+ is(
+ Glean.bounceTrackingProtection.purgeDuration.testGetValue(),
+ null,
+ "Histogram should still be empty after bounce, because we haven't purged yet."
+ );
+ },
+ });
+
+ let events = Glean.bounceTrackingProtection.purgeDuration.testGetValue();
+ if (isDryRunMode) {
+ is(events, null, "Should not collect purge timining in dry mode");
+ } else {
+ is(events.count, 1, "Histogram should contain one value.");
+ }
+
+ // Cleanup
+ Services.fog.testResetFOG();
+ await SpecialPowers.popPrefEnv();
+ bounceTrackingProtection.clearAll();
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.bounceTrackingProtection.requireStatefulBounces", true],
+ ["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
+ ],
+ });
+ bounceTrackingProtection = Cc[
+ "@mozilla.org/bounce-tracking-protection;1"
+ ].getService(Ci.nsIBounceTrackingProtection);
+
+ // Clear telemetry before test.
+ Services.fog.testResetFOG();
+});
+
+add_task(async function test_purge_duration_dry_mode() {
+ await test_purge_duration(true);
+});
+
+add_task(async function test_purge_duration_enabled() {
+ await test_purge_duration(false);
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.html b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.html
index 2756555fa5..d7aa117481 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.html
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.html
@@ -1,59 +1,150 @@
<!DOCTYPE html>
<html>
- <head>
- <meta charset="utf-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <title>Bounce!</title>
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- </head>
- <body>
- <p>Nothing to see here...</p>
- <script>
- // Wrap the entire block so we can run async code.
- (async () => {
- let url = new URL(location.href);
-
- let redirectDelay = url.searchParams.get("redirectDelay");
- if(redirectDelay != null) {
- redirectDelay = Number.parseInt(redirectDelay);
- } else {
- redirectDelay = 50;
- }
+<head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <title>Bounce!</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+</head>
+
+<body>
+ <p id="test-config"></p>
+ <script>
+ const SET_STATE_HANDLERS = {
+ "cookie-client": setCookie,
+ "localStorage": setLocalStorage,
+ "indexedDB": setIndexedDB,
+ };
+
+ function setCookie(id) {
+ let cookie = document.cookie;
+ if (cookie) {
+ console.info("Received cookie", cookie);
+ } else {
+ let newCookie = `id=${id}`;
+ console.info("Setting new cookie", newCookie);
+ document.cookie = newCookie;
+ }
+ }
+
+ function setLocalStorage(id) {
+ let entry = localStorage.getItem("id");
+ if (entry) {
+ console.info("Found localStorage entry. id", entry);
+ } else {
+ console.info("Setting new localStorage entry. id", id);
+ localStorage.setItem(id, id);
+ }
+ }
+
+ function setIndexedDB() {
+ return new Promise((resolve, reject) => {
+ let request = window.indexedDB.open("bounce", 1);
+ request.onsuccess = () => {
+ console.info("Opened indexedDB");
+ resolve()
+ };
+ request.onerror = (event) => {
+ console.error("Error opening indexedDB", event);
+ reject();
+ };
+ request.onupgradeneeded = (event) => {
+ console.info("Initializing indexedDB");
+ let db = event.target.result;
+ db.createObjectStore("bounce");
+ };
+ });
+ }
+
+ function setIndexedDBInWorker(nested = false) {
+ let worker = new Worker("file_web_worker.js");
+ let msg = nested ? "setIndexedDBNested" : "setIndexedDB";
+ worker.postMessage(msg);
+ return new Promise((resolve, reject) => {
+ worker.onmessage = () => {
+ console.info("IndexedDB set in worker");
+ resolve();
+ };
+ worker.onerror = (event) => {
+ console.error("Error setting indexedDB in worker", event);
+ reject();
+ };
+ });
+ }
+
+ /**
+ * Set a state in a child frame.
+ */
+ function setStateInFrame() {
+ // Embed self
+ let iframe = document.createElement("iframe");
+
+ let src = new URL(location.href);
+ // Remove search params we don't need for the iframe.
+ src.searchParams.delete("target");
+ src.searchParams.delete("redirectDelay");
+ src.searchParams.delete("setStateSameSiteFrame");
+ iframe.src = src.href;
+
+ let frameReadyPromise = new Promise((resolve) => {
+ iframe.addEventListener("load", () => {
+ iframe.contentWindow.readyPromise.then(resolve);
+ });
+ });
+ document.body.appendChild(iframe);
+
+ return frameReadyPromise;
+ }
+
+ // Wrap the entire block so we can run async code. Store the result in a
+ // promise so that parent windows can wait for us to be ready.
+ window.readyPromise = (async () => {
+ let url = new URL(location.href);
+ // Display the test config in the body.
+ document.getElementById("test-config").innerText = JSON.stringify(Object.fromEntries(url.searchParams), null, 2);
+
+ if (url.searchParams.get("setStateSameSiteFrame") === "true") {
+ // Set state in a child frame.
+ await setStateInFrame(url);
+ } else if(url.searchParams.get("setStateInWebWorker") === "true") {
+ // Set state in a worker.
+ await setIndexedDBInWorker();
+ } else if(url.searchParams.get("setStateInNestedWebWorker") === "true") {
+ // Set state in a nested worker.
+ await setIndexedDBInWorker(true);
+ } else {
+ // Set a state in this window.
let setState = url.searchParams.get("setState");
if (setState) {
let id = Math.random().toString();
- if (setState == "cookie-client") {
- let cookie = document.cookie;
-
- if (cookie) {
- console.info("Received cookie", cookie);
- } else {
- let newCookie = `id=${id}`;
- console.info("Setting new cookie", newCookie);
- document.cookie = newCookie;
- }
- } else if (setState == "localStorage") {
- let entry = localStorage.getItem("id");
-
- if (entry) {
- console.info("Found localStorage entry. id", entry);
- } else {
- console.info("Setting new localStorage entry. id", id);
- localStorage.setItem(id, id);
- }
+ let handler = SET_STATE_HANDLERS[setState];
+ if (!handler) {
+ throw new Error("Unknown state handler: " + setState);
}
+ await handler(id);
}
+ }
+
+ // Redirect to the target URL after a delay.
+ // If no target is specified, do nothing.
+ let redirectDelay = url.searchParams.get("redirectDelay");
+ if (redirectDelay != null) {
+ redirectDelay = Number.parseInt(redirectDelay);
+ } else {
+ redirectDelay = 50;
+ }
+
+ let target = url.searchParams.get("target");
+ if (target) {
+ console.info("Redirecting to", target);
+ setTimeout(() => {
+ location.href = target;
+ }, redirectDelay);
+ }
+ })();
+ </script>
+</body>
- let target = url.searchParams.get("target");
- if (target) {
- console.info("redirecting to", target);
- setTimeout(() => {
- location.href = target;
- }, redirectDelay);
- }
- })();
- </script>
- </body>
</html>
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_web_worker.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_web_worker.js
new file mode 100644
index 0000000000..78fdf2a149
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_web_worker.js
@@ -0,0 +1,40 @@
+// A web worker which can set indexedDB.
+
+function setIndexedDB() {
+ return new Promise((resolve, reject) => {
+ let request = self.indexedDB.open("bounce", 1);
+ request.onsuccess = () => {
+ console.info("Opened indexedDB");
+ resolve();
+ };
+ request.onerror = event => {
+ console.error("Error opening indexedDB", event);
+ reject();
+ };
+ request.onupgradeneeded = event => {
+ console.info("Initializing indexedDB");
+ let db = event.target.result;
+ db.createObjectStore("bounce");
+ };
+ });
+}
+
+self.onmessage = function (event) {
+ console.info("Web worker received message", event.data);
+
+ if (event.data === "setIndexedDB") {
+ setIndexedDB().then(() => {
+ self.postMessage("indexedDBSet");
+ });
+ } else if (event.data === "setIndexedDBNested") {
+ console.info("set state nested");
+ // Rather than setting indexedDB in this worker spawn a nested worker to set
+ // indexedDB.
+ let nestedWorker = new Worker("file_web_worker.js");
+ nestedWorker.postMessage("setIndexedDB");
+ nestedWorker.onmessage = () => {
+ console.info("IndexedDB set in nested worker");
+ self.postMessage("indexedDBSet");
+ };
+ }
+};
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js
index f5857b6919..71d9acedc6 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js
@@ -3,6 +3,17 @@
"use strict";
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "bounceTrackingProtection",
+ "@mozilla.org/bounce-tracking-protection;1",
+ "nsIBounceTrackingProtection"
+);
+
const SITE_A = "example.com";
const ORIGIN_A = `https://${SITE_A}`;
@@ -25,13 +36,6 @@ const OBSERVER_MSG_RECORD_BOUNCES_FINISHED = "test-record-bounces-finished";
const ROOT_DIR = getRootDirectory(gTestPath);
-XPCOMUtils.defineLazyServiceGetter(
- this,
- "bounceTrackingProtection",
- "@mozilla.org/bounce-tracking-protection;1",
- "nsIBounceTrackingProtection"
-);
-
/**
* Get the base url for the current test directory using the given origin.
* @param {string} origin - Origin to use in URL.
@@ -48,8 +52,14 @@ function getBaseUrl(origin) {
* the bounce.
* @param {string} [options.bounceOrigin] - The origin of the bounce URL.
* @param {string} [options.targetURL] - URL to redirect to after the bounce.
- * @param {("cookie"|null)} [options.setState] - What type of state should be set during
- * the bounce. No state by default.
+ * @param {('cookie-server'|'cookie-client'|'localStorage')} [options.setState]
+ * Type of state to set during the redirect. Defaults to non stateful redirect.
+ * @param {boolean} [options.setStateSameSiteFrame=false] - Whether to set the
+ * state in a sub frame that is same site to the top window.
+ * @param {boolean} [options.setStateInWebWorker=false] - Whether to set the
+ * state in a web worker. This only supports setState == "indexedDB".
+ * @param {boolean} [options.setStateInWebWorker=false] - Whether to set the
+ * state in a nested web worker. Otherwise the same as setStateInWebWorker.
* @param {number} [options.statusCode] - HTTP status code to use for server
* side redirect. Only applies to bounceType == "server".
* @param {number} [options.redirectDelayMS] - How long to wait before
@@ -62,6 +72,9 @@ function getBounceURL({
bounceOrigin = ORIGIN_TRACKER,
targetURL = new URL(getBaseUrl(ORIGIN_B) + "file_start.html"),
setState = null,
+ setStateSameSiteFrame = false,
+ setStateInWebWorker = false,
+ setStateInNestedWebWorker = false,
statusCode = 302,
redirectDelayMS = 50,
}) {
@@ -79,6 +92,25 @@ function getBounceURL({
if (setState) {
searchParams.set("setState", setState);
}
+ if (setStateSameSiteFrame) {
+ searchParams.set("setStateSameSiteFrame", setStateSameSiteFrame);
+ }
+ if (setStateInWebWorker) {
+ if (setState != "indexedDB") {
+ throw new Error(
+ "setStateInWebWorker only supports setState == 'indexedDB'"
+ );
+ }
+ searchParams.set("setStateInWebWorker", setStateInWebWorker);
+ }
+ if (setStateInNestedWebWorker) {
+ if (setState != "indexedDB") {
+ throw new Error(
+ "setStateInNestedWebWorker only supports setState == 'indexedDB'"
+ );
+ }
+ searchParams.set("setStateInNestedWebWorker", setStateInNestedWebWorker);
+ }
if (bounceType == "server") {
searchParams.set("statusCode", statusCode);
@@ -94,23 +126,58 @@ function getBounceURL({
* click on it.
* @param {MozBrowser} browser - Browser to insert the link in.
* @param {URL} targetURL - Destination for navigation.
+ * @param {Object} options - Additional options.
+ * @param {string} [options.spawnWindow] - If set to "newTab" or "popup" the
+ * link will be opened in a new tab or popup window respectively. If unset the
+ * link is opened in the given browser.
* @returns {Promise} Resolves once the click is done. Does not wait for
* navigation or load.
*/
-async function navigateLinkClick(browser, targetURL) {
- await SpecialPowers.spawn(browser, [targetURL.href], targetURL => {
- let link = content.document.createElement("a");
-
- link.href = targetURL;
- link.textContent = targetURL;
- // The link needs display: block, otherwise synthesizeMouseAtCenter doesn't
- // hit it.
- link.style.display = "block";
-
- content.document.body.appendChild(link);
- });
+async function navigateLinkClick(
+ browser,
+ targetURL,
+ { spawnWindow = null } = {}
+) {
+ if (spawnWindow && !["newTab", "popup"].includes(spawnWindow)) {
+ throw new Error(`Invalid option '${spawnWindow}' for spawnWindow`);
+ }
- await BrowserTestUtils.synthesizeMouseAtCenter("a[href]", {}, browser);
+ await SpecialPowers.spawn(
+ browser,
+ [targetURL.href, spawnWindow],
+ async (targetURL, spawnWindow) => {
+ let link = content.document.createElement("a");
+ link.id = "link";
+ link.textContent = "Click Me";
+ link.style.display = "block";
+ link.style.fontSize = "40px";
+
+ // For opening a popup we attach an event listener to trigger via click.
+ if (spawnWindow) {
+ link.href = "#";
+ link.addEventListener("click", event => {
+ event.preventDefault();
+ if (spawnWindow == "newTab") {
+ // Open a new tab.
+ content.window.open(targetURL, "bounce");
+ } else {
+ // Open a popup window.
+ content.window.open(targetURL, "bounce", "height=200,width=200");
+ }
+ });
+ } else {
+ // For regular navigation add href and click.
+ link.href = targetURL;
+ }
+
+ content.document.body.appendChild(link);
+
+ // TODO: Bug 1892091: Use EventUtils.synthesizeMouse instead for a real click.
+ SpecialPowers.wrap(content.document).notifyUserGestureActivation();
+ content.document.userInteractionForTesting();
+ link.click();
+ }
+ );
}
/**
@@ -141,25 +208,38 @@ async function waitForRecordBounces(browser) {
* or server side redirect.
* @param {('cookie-server'|'cookie-client'|'localStorage')} [options.setState]
* Type of state to set during the redirect. Defaults to non stateful redirect.
- * @param {boolean} [options.expectCandidate=true] - Expect the redirecting site to be
- * identified as a bounce tracker (candidate).
- * @param {boolean} [options.expectPurge=true] - Expect the redirecting site to have
- * its storage purged.
+ * @param {boolean} [options.setStateSameSiteFrame=false] - Whether to set the
+ * state in a sub frame that is same site to the top window.
+ * @param {boolean} [options.setStateInWebWorker=false] - Whether to set the
+ * state in a web worker. This only supports setState == "indexedDB".
+ * @param {boolean} [options.setStateInWebWorker=false] - Whether to set the
+ * state in a nested web worker. Otherwise the same as setStateInWebWorker.
+ * @param {boolean} [options.expectCandidate=true] - Expect the redirecting site
+ * to be identified as a bounce tracker (candidate).
+ * @param {boolean} [options.expectPurge=true] - Expect the redirecting site to
+ * have its storage purged.
* @param {OriginAttributes} [options.originAttributes={}] - Origin attributes
* to use for the test. This determines whether the test is run in normal
- * browsing, a private window or a container tab. By default the test is run
- * in normal browsing.
- * @param {function} [options.postBounceCallback] - Optional function to run after the
- * bounce has completed.
+ * browsing, a private window or a container tab. By default the test is run in
+ * normal browsing.
+ * @param {function} [options.postBounceCallback] - Optional function to run
+ * after the bounce has completed.
+ * @param {boolean} [options.skipSiteDataCleanup=false] - Skip the cleanup of
+ * site data after the test. When this is enabled the caller is responsible for
+ * cleaning up site data.
*/
async function runTestBounce(options = {}) {
let {
bounceType,
setState = null,
+ setStateSameSiteFrame = false,
+ setStateInWebWorker = false,
+ setStateInNestedWebWorker = false,
expectCandidate = true,
expectPurge = true,
originAttributes = {},
postBounceCallback = () => {},
+ skipSiteDataCleanup = false,
} = options;
info(`runTestBounce ${JSON.stringify(options)}`);
@@ -191,28 +271,42 @@ async function runTestBounce(options = {}) {
win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
}
- let tab = win.gBrowser.addTab(getBaseUrl(ORIGIN_A) + "file_start.html", {
+ let initialURL = getBaseUrl(ORIGIN_A) + "file_start.html";
+ let tab = win.gBrowser.addTab(initialURL, {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
userContextId,
});
win.gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
- await BrowserTestUtils.browserLoaded(browser);
+ await BrowserTestUtils.browserLoaded(browser, true, initialURL);
let promiseRecordBounces = waitForRecordBounces(browser);
// The final destination after the bounce.
let targetURL = new URL(getBaseUrl(ORIGIN_B) + "file_start.html");
+ // Wait for the final site to be loaded which complete the BounceTrackingRecord.
+ let targetURLLoadedPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ targetURL
+ );
+
// Navigate through the bounce chain.
await navigateLinkClick(
browser,
- getBounceURL({ bounceType, targetURL, setState })
+ getBounceURL({
+ bounceType,
+ targetURL,
+ setState,
+ setStateSameSiteFrame,
+ setStateInWebWorker,
+ setStateInNestedWebWorker,
+ })
);
- // Wait for the final site to be loaded which complete the BounceTrackingRecord.
- await BrowserTestUtils.browserLoaded(browser, false, targetURL);
+ await targetURLLoadedPromise;
// Navigate again with user gesture which triggers
// BounceTrackingProtection::RecordStatefulBounces. We could rely on the
@@ -226,9 +320,9 @@ async function runTestBounce(options = {}) {
await promiseRecordBounces;
Assert.deepEqual(
- bounceTrackingProtection.testGetBounceTrackerCandidateHosts(
- originAttributes
- ),
+ bounceTrackingProtection
+ .testGetBounceTrackerCandidateHosts(originAttributes)
+ .map(entry => entry.siteHost),
expectCandidate ? [SITE_TRACKER] : [],
`Should ${
expectCandidate ? "" : "not "
@@ -237,6 +331,7 @@ async function runTestBounce(options = {}) {
Assert.deepEqual(
bounceTrackingProtection
.testGetUserActivationHosts(originAttributes)
+ .map(entry => entry.siteHost)
.sort(),
[SITE_A, SITE_B].sort(),
"Should only have user activation for sites where we clicked links."
@@ -272,4 +367,7 @@ async function runTestBounce(options = {}) {
);
}
bounceTrackingProtection.clearAll();
+ if (!skipSiteDataCleanup) {
+ await SiteDataTestUtils.clear();
+ }
}
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/marionette/test_bouncetracking_storage_persistence.py b/toolkit/components/antitracking/bouncetrackingprotection/test/marionette/test_bouncetracking_storage_persistence.py
index afc3239839..3b0fe351e2 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/marionette/test_bouncetracking_storage_persistence.py
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/marionette/test_bouncetracking_storage_persistence.py
@@ -48,7 +48,7 @@ class BounceTrackingStoragePersistenceTestCase(MarionetteTestCase):
let bounceTrackingProtection = Cc["@mozilla.org/bounce-tracking-protection;1"].getService(
Ci.nsIBounceTrackingProtection
);
- return bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).sort();
+ return bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).map(entry => entry.siteHost).sort();
""",
)
self.assertEqual(
@@ -64,7 +64,7 @@ class BounceTrackingStoragePersistenceTestCase(MarionetteTestCase):
let bounceTrackingProtection = Cc["@mozilla.org/bounce-tracking-protection;1"].getService(
Ci.nsIBounceTrackingProtection
);
- return bounceTrackingProtection.testGetBounceTrackerCandidateHosts({ userContextId: 3 }).sort();
+ return bounceTrackingProtection.testGetBounceTrackerCandidateHosts({ userContextId: 3 }).map(entry => entry.siteHost).sort();
""",
)
self.assertEqual(
@@ -109,7 +109,7 @@ class BounceTrackingStoragePersistenceTestCase(MarionetteTestCase):
let bounceTrackingProtection = Cc["@mozilla.org/bounce-tracking-protection;1"].getService(
Ci.nsIBounceTrackingProtection
);
- return bounceTrackingProtection.testGetUserActivationHosts({}).sort();
+ return bounceTrackingProtection.testGetUserActivationHosts({}).map(entry => entry.siteHost).sort();
""",
)
self.assertEqual(
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js
index 28a1350b3e..a52fb7fd46 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js
@@ -69,13 +69,19 @@ add_task(async function test() {
// Assert that expired user activations have been cleared.
Assert.deepEqual(
- btp.testGetUserActivationHosts({}).sort(),
+ btp
+ .testGetUserActivationHosts({})
+ .map(entry => entry.siteHost)
+ .sort(),
["not-expired1.com", "not-expired2.com"],
"Expired user activation flags have been cleared for normal browsing."
);
Assert.deepEqual(
- btp.testGetUserActivationHosts({ privateBrowsingId: 1 }).sort(),
+ btp
+ .testGetUserActivationHosts({ privateBrowsingId: 1 })
+ .map(entry => entry.siteHost)
+ .sort(),
["pbm-not-expired.com"],
"Expired user activation flags have been cleared for private browsing."
);
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_importUserActivationPermissions.js b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_importUserActivationPermissions.js
new file mode 100644
index 0000000000..5150d074c2
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_importUserActivationPermissions.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+const DOMAIN_A = "example.com";
+const SUB_DOMAIN_A = "sub." + DOMAIN_A;
+const DOMAIN_B = "example.org";
+const DOMAIN_C = "example.net";
+
+const ORIGIN_A = "https://" + DOMAIN_A;
+const ORIGIN_SUB_A = "https://" + SUB_DOMAIN_A;
+const ORIGIN_B = "https://" + DOMAIN_B;
+const ORIGIN_C = "https://" + DOMAIN_C;
+const ORIGIN_NON_HTTP = "file:///foo/bar.html";
+
+const OA_PBM = { privateBrowsingId: 1 };
+const PRINCIPAL_C_PBM = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(ORIGIN_C),
+ OA_PBM
+);
+
+let btp;
+let userActivationLifetimeSec = Services.prefs.getIntPref(
+ "privacy.bounceTrackingProtection.bounceTrackingActivationLifetimeSec"
+);
+
+function cleanup() {
+ btp.clearAll();
+ Services.perms.removeAll();
+ Services.prefs.setBoolPref(
+ "privacy.bounceTrackingProtection.hasMigratedUserActivationData",
+ false
+ );
+}
+
+add_setup(function () {
+ // Need a profile to data clearing calls.
+ do_get_profile();
+
+ btp = Cc["@mozilla.org/bounce-tracking-protection;1"].getService(
+ Ci.nsIBounceTrackingProtection
+ );
+
+ // Clean initial state.
+ cleanup();
+});
+
+add_task(async function test_user_activation_perm_migration() {
+ // Assert initial test state.
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts({}),
+ [],
+ "No user activation hosts initially."
+ );
+ Assert.equal(
+ Services.perms.getAllByTypes(["storageAccessAPI"]).length,
+ 0,
+ "No user activation permissions initially."
+ );
+
+ info("Add test user activation permissions.");
+
+ let now = Date.now();
+
+ // Non-expired permissions.
+ PermissionTestUtils.addWithModificationTime(
+ ORIGIN_A,
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION,
+ now
+ );
+ PermissionTestUtils.addWithModificationTime(
+ ORIGIN_C,
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION,
+ now - 1000
+ );
+
+ // A non expired permission for a subdomain of DOMAIN_A that has an older modification time.
+ PermissionTestUtils.addWithModificationTime(
+ ORIGIN_SUB_A,
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION,
+ now - 500
+ );
+
+ // An expired permission.
+ PermissionTestUtils.addWithModificationTime(
+ ORIGIN_B,
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION,
+ now - userActivationLifetimeSec * 1.2 * 1000
+ );
+
+ // A non-HTTP permission.
+ PermissionTestUtils.addWithModificationTime(
+ ORIGIN_NON_HTTP,
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION,
+ now
+ );
+
+ // A permission for PBM. Ideally we'd test a more persistent permission type
+ // here with custom oa, but permission seperation by userContextId isn't
+ // enabled yet (Bug 1641584).
+ PermissionTestUtils.addWithModificationTime(
+ PRINCIPAL_C_PBM,
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION,
+ now
+ );
+
+ info("Trigger migration.");
+ btp.testMaybeMigrateUserInteractionPermissions();
+
+ Assert.deepEqual(
+ btp
+ .testGetUserActivationHosts({})
+ .map(entry => entry.siteHost)
+ .sort(),
+ [DOMAIN_A, DOMAIN_C].sort(),
+ "Should have imported the correct user activation flags."
+ );
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts(OA_PBM).map(entry => entry.siteHost),
+ [DOMAIN_C],
+ "Should have imported the correct user activation flags for PBM."
+ );
+
+ info("Reset the BTP user activation store");
+ btp.clearAll();
+
+ info("Trigger migration again.");
+ btp.testMaybeMigrateUserInteractionPermissions();
+
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts({}),
+ [],
+ "Should not have imported the user activation flags again."
+ );
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts(OA_PBM),
+ [],
+ "Should not have imported the user activation flags again for PBM."
+ );
+
+ cleanup();
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js
index 5ede57a08b..2fbd8a0a02 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_purge.js
@@ -6,6 +6,9 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
const { SiteDataTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/SiteDataTestUtils.sys.mjs"
);
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
let btp;
let bounceTrackingGracePeriodSec;
@@ -138,6 +141,14 @@ add_task(async function test_purge() {
message: "Should purge after grace period.",
shouldPurge: true,
},
+ // Don't purge if the site is allowlisted.
+ "example2.net": {
+ bounceTime: timestampOutsideGracePeriodFiveSeconds,
+ userActivationTime: null,
+ isAllowListed: true,
+ message: "Should not purge after grace period if allowlisted.",
+ shouldPurge: false,
+ },
// Also ensure that clear data calls with IP sites succeed.
"1.2.3.4": {
bounceTime: timestampOutsideGracePeriodThreeDays,
@@ -191,16 +202,30 @@ add_task(async function test_purge() {
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, { bounceTime, userActivationTime, shouldPurge }]) => {
+ async ([
+ siteHost,
+ { bounceTime, userActivationTime, isAllowListed, shouldPurge },
+ ]) => {
// Add site state so we can later assert it has been purged.
await addStateForHost(siteHost);
+ // Add allowlist entry if needed.
+ if (isAllowListed) {
+ PermissionTestUtils.add(
+ `https://${siteHost}`,
+ "trackingprotection",
+ Services.perms.ALLOW_ACTION
+ );
+ allowListedHosts.push(siteHost);
+ }
+
if (bounceTime != null) {
if (userActivationTime != null) {
throw new Error(
@@ -210,7 +235,7 @@ add_task(async function test_purge() {
expectedBounceTrackerHosts.push(siteHost);
- // Convert bounceTime timestamp to nanoseconds (PRTime).
+ // Convert bounceTime timestamp to microseconds (PRTime).
info(
`Adding bounce. siteHost: ${siteHost}, bounceTime: ${bounceTime} ms`
);
@@ -232,7 +257,7 @@ add_task(async function test_purge() {
expiredUserActivationHosts.push(siteHost);
}
- // Convert userActivationTime timestamp to nanoseconds (PRTime).
+ // Convert userActivationTime timestamp to microseconds (PRTime).
info(
`Adding user interaction. siteHost: ${siteHost}, userActivationTime: ${userActivationTime} ms`
);
@@ -250,12 +275,18 @@ add_task(async function test_purge() {
"Check that bounce and user activation data has been correctly recorded."
);
Assert.deepEqual(
- btp.testGetBounceTrackerCandidateHosts({}).sort(),
+ btp
+ .testGetBounceTrackerCandidateHosts({})
+ .map(entry => entry.siteHost)
+ .sort(),
expectedBounceTrackerHosts.sort(),
"Has added bounce tracker hosts."
);
Assert.deepEqual(
- btp.testGetUserActivationHosts({}).sort(),
+ btp
+ .testGetUserActivationHosts({})
+ .map(entry => entry.siteHost)
+ .sort(),
expectedUserActivationHosts.sort(),
"Has added user activation hosts."
);
@@ -269,17 +300,29 @@ add_task(async function test_purge() {
"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))
+ .filter(
+ host =>
+ !expectedPurgedHosts.includes(host) && !allowListedHosts.includes(host)
+ )
.sort();
Assert.deepEqual(
- btp.testGetBounceTrackerCandidateHosts({}).sort(),
+ 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({}).sort(),
+ btp
+ .testGetUserActivationHosts({})
+ .map(entry => entry.siteHost)
+ .sort(),
expiredUserActivationHosts.sort(),
"After purge any expired user activation records should have been removed"
);
@@ -302,6 +345,7 @@ add_task(async function test_purge() {
btp.clearAll();
assertEmpty();
- info("Clean up site data.");
+ info("Clean up site data and permissions.");
await SiteDataTestUtils.clear();
+ Services.perms.removeAll();
});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml
index c3aeee502f..8195ea7224 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml
@@ -3,8 +3,11 @@ prefs = [
"privacy.bounceTrackingProtection.enabled=true",
"privacy.bounceTrackingProtection.enableTestMode=true",
"privacy.bounceTrackingProtection.bounceTrackingPurgeTimerPeriodSec=0",
+ "privacy.bounceTrackingProtection.enableDryRunMode=false",
]
["test_bouncetracking_clearExpiredUserActivation.js"]
+["test_bouncetracking_importUserActivationPermissions.js"]
+
["test_bouncetracking_purge.js"]