summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/bouncetrackingprotection/test/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/antitracking/bouncetrackingprotection/test/browser
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/antitracking/bouncetrackingprotection/test/browser')
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser.toml20
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_oa_isolation.js73
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js121
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_simple.js89
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful.js63
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.html59
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.sjs19
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_start.html11
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js275
9 files changed, 730 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser.toml b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser.toml
new file mode 100644
index 0000000000..1c44d7804e
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser.toml
@@ -0,0 +1,20 @@
+[DEFAULT]
+head = "head.js"
+prefs = [
+ "privacy.bounceTrackingProtection.enabled=true",
+ "privacy.bounceTrackingProtection.enableTestMode=true",
+ "privacy.bounceTrackingProtection.bounceTrackingPurgeTimerPeriodSec=0",
+]
+support-files = [
+ "file_start.html",
+ "file_bounce.sjs",
+ "file_bounce.html",
+]
+
+["browser_bouncetracking_oa_isolation.js"]
+
+["browser_bouncetracking_purge.js"]
+
+["browser_bouncetracking_simple.js"]
+
+["browser_bouncetracking_stateful.js"]
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_oa_isolation.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_oa_isolation.js
new file mode 100644
index 0000000000..12c2c943dd
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_oa_isolation.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.bounceTrackingProtection.requireStatefulBounces", true],
+ ["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
+ ],
+ });
+});
+
+// Tests that bounces in PBM don't affect state in normal browsing.
+add_task(async function test_pbm_data_isolated() {
+ await runTestBounce({
+ bounceType: "client",
+ setState: "cookie-client",
+ originAttributes: { privateBrowsingId: 1 },
+ postBounceCallback: () => {
+ // After the PBM bounce assert that we haven't recorded any data for normal browsing.
+ Assert.equal(
+ bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).length,
+ 0,
+ "No bounce tracker candidates for normal browsing."
+ );
+ Assert.equal(
+ bounceTrackingProtection.testGetUserActivationHosts({}).length,
+ 0,
+ "No user activations for normal browsing."
+ );
+ },
+ });
+});
+
+// Tests that bounces in PBM don't affect state in normal browsing.
+add_task(async function test_containers_isolated() {
+ await runTestBounce({
+ bounceType: "server",
+ setState: "cookie-server",
+ originAttributes: { userContextId: 2 },
+ postBounceCallback: () => {
+ // After the bounce in the container tab assert that we haven't recorded any data for normal browsing.
+ Assert.equal(
+ bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).length,
+ 0,
+ "No bounce tracker candidates for normal browsing."
+ );
+ Assert.equal(
+ bounceTrackingProtection.testGetUserActivationHosts({}).length,
+ 0,
+ "No user activations for normal browsing."
+ );
+
+ // Or in another container tab.
+ Assert.equal(
+ bounceTrackingProtection.testGetBounceTrackerCandidateHosts({
+ userContextId: 1,
+ }).length,
+ 0,
+ "No bounce tracker candidates for container tab 1."
+ );
+ Assert.equal(
+ bounceTrackingProtection.testGetUserActivationHosts({
+ userContextId: 1,
+ }).length,
+ 0,
+ "No user activations for container tab 1."
+ );
+ },
+ });
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
new file mode 100644
index 0000000000..a8e98b80f0
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
@@ -0,0 +1,121 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BOUNCE_TRACKING_GRACE_PERIOD_SEC = 30;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec",
+ BOUNCE_TRACKING_GRACE_PERIOD_SEC,
+ ],
+ ["privacy.bounceTrackingProtection.requireStatefulBounces", false],
+ ],
+ });
+});
+
+/**
+ * The following tests ensure that sites that have open tabs are exempt from purging.
+ */
+
+function initBounceTrackerState() {
+ bounceTrackingProtection.clearAll();
+
+ // Bounce time of 1 is out of the grace period which means we should purge.
+ bounceTrackingProtection.testAddBounceTrackerCandidate({}, "example.com", 1);
+ bounceTrackingProtection.testAddBounceTrackerCandidate({}, "example.net", 1);
+
+ // Should not purge because within grace period.
+ let timestampWithinGracePeriod =
+ Date.now() - (BOUNCE_TRACKING_GRACE_PERIOD_SEC * 1000) / 2;
+ bounceTrackingProtection.testAddBounceTrackerCandidate(
+ {},
+ "example.org",
+ timestampWithinGracePeriod * 1000
+ );
+}
+
+add_task(async function test_purging_skip_open_foreground_tab() {
+ initBounceTrackerState();
+
+ // Foreground tab
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.net"],
+ "Should only purge example.net. example.org is within the grace period, example.com has an open tab."
+ );
+
+ info("Close the tab for example.com and test that it gets purged now.");
+ initBounceTrackerState();
+
+ BrowserTestUtils.removeTab(tab);
+ Assert.deepEqual(
+ (await bounceTrackingProtection.testRunPurgeBounceTrackers()).sort(),
+ ["example.net", "example.com"].sort(),
+ "example.com should have been purged now that it no longer has an open tab."
+ );
+
+ bounceTrackingProtection.clearAll();
+});
+
+add_task(async function test_purging_skip_open_background_tab() {
+ initBounceTrackerState();
+
+ // Background tab
+ let tab = BrowserTestUtils.addTab(gBrowser, "https://example.com");
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.net"],
+ "Should only purge example.net. example.org is within the grace period, example.com has an open tab."
+ );
+
+ info("Close the tab for example.com and test that it gets purged now.");
+ initBounceTrackerState();
+
+ BrowserTestUtils.removeTab(tab);
+ Assert.deepEqual(
+ (await bounceTrackingProtection.testRunPurgeBounceTrackers()).sort(),
+ ["example.net", "example.com"].sort(),
+ "example.com should have been purged now that it no longer has an open tab."
+ );
+
+ bounceTrackingProtection.clearAll();
+});
+
+add_task(async function test_purging_skip_open_tab_extra_window() {
+ initBounceTrackerState();
+
+ // Foreground tab in new window.
+ let win = await BrowserTestUtils.openNewBrowserWindow({});
+ await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ "https://example.com"
+ );
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.net"],
+ "Should only purge example.net. example.org is within the grace period, example.com has an open tab."
+ );
+
+ info(
+ "Close the window with the tab for example.com and test that it gets purged now."
+ );
+ initBounceTrackerState();
+
+ await BrowserTestUtils.closeWindow(win);
+ Assert.deepEqual(
+ (await bounceTrackingProtection.testRunPurgeBounceTrackers()).sort(),
+ ["example.net", "example.com"].sort(),
+ "example.com should have been purged now that it no longer has an open tab."
+ );
+
+ bounceTrackingProtection.clearAll();
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_simple.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_simple.js
new file mode 100644
index 0000000000..dfbd4d0fc0
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_simple.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.bounceTrackingProtection.requireStatefulBounces", false],
+ ["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
+ ],
+ });
+});
+
+// Tests a stateless bounce via client redirect.
+add_task(async function test_client_bounce_simple() {
+ await runTestBounce({ bounceType: "client" });
+});
+
+// Tests a stateless bounce via server redirect.
+add_task(async function test_server_bounce_simple() {
+ await runTestBounce({ bounceType: "server" });
+});
+
+// Tests a chained redirect consisting of a server and a client redirect.
+add_task(async function test_bounce_chain() {
+ Assert.equal(
+ bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).length,
+ 0,
+ "No bounce tracker hosts initially."
+ );
+ Assert.equal(
+ bounceTrackingProtection.testGetUserActivationHosts({}).length,
+ 0,
+ "No user activation hosts initially."
+ );
+
+ await BrowserTestUtils.withNewTab(
+ getBaseUrl(ORIGIN_A) + "file_start.html",
+ async browser => {
+ let promiseRecordBounces = waitForRecordBounces(browser);
+
+ // The final destination after the bounces.
+ let targetURL = new URL(getBaseUrl(ORIGIN_B) + "file_start.html");
+
+ // Construct last hop.
+ let bounceChainUrlEnd = getBounceURL({ bounceType: "server", targetURL });
+ // Construct first hop, nesting last hop.
+ let bounceChainUrlFull = getBounceURL({
+ bounceType: "client",
+ redirectDelayMS: 100,
+ bounceOrigin: ORIGIN_TRACKER_B,
+ targetURL: bounceChainUrlEnd,
+ });
+
+ info("bounceChainUrl: " + bounceChainUrlFull.href);
+
+ // Navigate through the bounce chain.
+ await navigateLinkClick(browser, bounceChainUrlFull);
+
+ // Wait for the final site to be loaded which complete the BounceTrackingRecord.
+ await BrowserTestUtils.browserLoaded(browser, false, targetURL);
+
+ // 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(
+ browser,
+ new URL(getBaseUrl(ORIGIN_C) + "file_start.html")
+ );
+
+ await promiseRecordBounces;
+
+ Assert.deepEqual(
+ bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).sort(),
+ [SITE_TRACKER_B, SITE_TRACKER].sort(),
+ `Identified all bounce trackers in the redirect chain.`
+ );
+ Assert.deepEqual(
+ bounceTrackingProtection.testGetUserActivationHosts({}).sort(),
+ [SITE_A, SITE_B].sort(),
+ "Should only have user activation for sites where we clicked links."
+ );
+
+ bounceTrackingProtection.clearAll();
+ }
+ );
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful.js
new file mode 100644
index 0000000000..e7fb4521a7
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_stateful.js
@@ -0,0 +1,63 @@
+/* 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);
+});
+
+// Cookie tests.
+
+add_task(async function test_bounce_stateful_cookies_client() {
+ info("Test client bounce with cookie.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: "cookie-client",
+ });
+ info("Test client bounce without cookie.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: null,
+ expectCandidate: false,
+ expectPurge: false,
+ });
+});
+
+add_task(async function test_bounce_stateful_cookies_server() {
+ info("Test server bounce with cookie.");
+ await runTestBounce({
+ bounceType: "server",
+ setState: "cookie-server",
+ });
+ info("Test server bounce without cookie.");
+ await runTestBounce({
+ bounceType: "server",
+ setState: null,
+ expectCandidate: false,
+ expectPurge: false,
+ });
+});
+
+// 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.");
+ await runTestBounce({
+ bounceType: "client",
+ setState: "localStorage",
+ expectCandidate: false,
+ expectPurge: false,
+ });
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.html b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.html
new file mode 100644
index 0000000000..2756555fa5
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.html
@@ -0,0 +1,59 @@
+<!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;
+ }
+
+ 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 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_bounce.sjs b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.sjs
new file mode 100644
index 0000000000..5e948a899b
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_bounce.sjs
@@ -0,0 +1,19 @@
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ let query = new URLSearchParams(request.queryString);
+
+ let setState = query.get("setState");
+ if (setState == "cookie-server") {
+ response.setHeader("Set-Cookie", "foo=bar");
+ }
+
+ let statusCode = 302;
+ let statusCodeQuery = query.get("statusCode");
+ if (statusCodeQuery) {
+ statusCode = Number.parseInt(statusCodeQuery);
+ }
+
+ response.setStatusLine("1.1", statusCode, "Found");
+ response.setHeader("Location", query.get("target"), false);
+}
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_start.html b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_start.html
new file mode 100644
index 0000000000..ded691023b
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/file_start.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <title>Blank</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js
new file mode 100644
index 0000000000..f5857b6919
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js
@@ -0,0 +1,275 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const SITE_A = "example.com";
+const ORIGIN_A = `https://${SITE_A}`;
+
+const SITE_B = "example.org";
+const ORIGIN_B = `https://${SITE_B}`;
+
+const SITE_C = "example.net";
+const ORIGIN_C = `https://${SITE_C}`;
+
+const SITE_TRACKER = "itisatracker.org";
+const ORIGIN_TRACKER = `https://${SITE_TRACKER}`;
+
+const SITE_TRACKER_B = "trackertest.org";
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const ORIGIN_TRACKER_B = `http://${SITE_TRACKER_B}`;
+
+// Test message used for observing when the record-bounces method in
+// BounceTrackingProtection.cpp has finished.
+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.
+ * @returns {string} - Generated URL as a string.
+ */
+function getBaseUrl(origin) {
+ return ROOT_DIR.replace("chrome://mochitests/content", origin);
+}
+
+/**
+ * Constructs a url for an intermediate "bounce" hop which represents a tracker.
+ * @param {*} options - URL generation options.
+ * @param {('server'|'client')} options.bounceType - Redirect type to use for
+ * 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 {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
+ * redirecting. Only applies to bounceType == "client".
+ * @returns {URL} Generated URL which points to an endpoint performing the
+ * redirect.
+ */
+function getBounceURL({
+ bounceType,
+ bounceOrigin = ORIGIN_TRACKER,
+ targetURL = new URL(getBaseUrl(ORIGIN_B) + "file_start.html"),
+ setState = null,
+ statusCode = 302,
+ redirectDelayMS = 50,
+}) {
+ if (!["server", "client"].includes(bounceType)) {
+ throw new Error("Invalid bounceType");
+ }
+
+ let bounceFile =
+ bounceType == "client" ? "file_bounce.html" : "file_bounce.sjs";
+
+ let bounceUrl = new URL(getBaseUrl(bounceOrigin) + bounceFile);
+
+ let { searchParams } = bounceUrl;
+ searchParams.set("target", targetURL.href);
+ if (setState) {
+ searchParams.set("setState", setState);
+ }
+
+ if (bounceType == "server") {
+ searchParams.set("statusCode", statusCode);
+ } else if (bounceType == "client") {
+ searchParams.set("redirectDelay", redirectDelayMS);
+ }
+
+ return bounceUrl;
+}
+
+/**
+ * Insert an <a href/> element with the given target and perform a synthesized
+ * click on it.
+ * @param {MozBrowser} browser - Browser to insert the link in.
+ * @param {URL} targetURL - Destination for navigation.
+ * @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);
+ });
+
+ await BrowserTestUtils.synthesizeMouseAtCenter("a[href]", {}, browser);
+}
+
+/**
+ * Wait for the record-bounces method to run for the given tab / browser.
+ * @param {browser} browser - Browser element which represents the tab we want
+ * to observe.
+ * @returns {Promise} Promise which resolves once the record-bounces method has
+ * run for the given browser.
+ */
+async function waitForRecordBounces(browser) {
+ return TestUtils.topicObserved(
+ OBSERVER_MSG_RECORD_BOUNCES_FINISHED,
+ subject => {
+ // Ensure the message was dispatched for the browser we're interested in.
+ let propBag = subject.QueryInterface(Ci.nsIPropertyBag2);
+ let browserId = propBag.getProperty("browserId");
+ return browser.browsingContext.browserId == browserId;
+ }
+ );
+}
+
+/**
+ * Test helper which loads an initial blank page, then navigates to a url which
+ * performs a bounce. Checks that the bounce hosts are properly identified as
+ * trackers.
+ * @param {object} options - Test Options.
+ * @param {('server'|'client')} options.bounceType - Whether to perform a client
+ * 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 {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.
+ */
+async function runTestBounce(options = {}) {
+ let {
+ bounceType,
+ setState = null,
+ expectCandidate = true,
+ expectPurge = true,
+ originAttributes = {},
+ postBounceCallback = () => {},
+ } = options;
+ info(`runTestBounce ${JSON.stringify(options)}`);
+
+ Assert.equal(
+ bounceTrackingProtection.testGetBounceTrackerCandidateHosts(
+ originAttributes
+ ).length,
+ 0,
+ "No bounce tracker hosts initially."
+ );
+ Assert.equal(
+ bounceTrackingProtection.testGetUserActivationHosts(originAttributes)
+ .length,
+ 0,
+ "No user activation hosts initially."
+ );
+
+ let win = window;
+ let { privateBrowsingId, userContextId } = originAttributes;
+ let usePrivateWindow =
+ privateBrowsingId != null &&
+ privateBrowsingId !=
+ Services.scriptSecurityManager.DEFAULT_PRIVATE_BROWSING_ID;
+ if (userContextId != null && userContextId > 0 && usePrivateWindow) {
+ throw new Error("userContextId is not supported in private windows");
+ }
+
+ if (usePrivateWindow) {
+ win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ }
+
+ let tab = win.gBrowser.addTab(getBaseUrl(ORIGIN_A) + "file_start.html", {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ userContextId,
+ });
+ win.gBrowser.selectedTab = tab;
+
+ let browser = tab.linkedBrowser;
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let promiseRecordBounces = waitForRecordBounces(browser);
+
+ // The final destination after the bounce.
+ let targetURL = new URL(getBaseUrl(ORIGIN_B) + "file_start.html");
+
+ // Navigate through the bounce chain.
+ await navigateLinkClick(
+ browser,
+ getBounceURL({ bounceType, targetURL, setState })
+ );
+
+ // Wait for the final site to be loaded which complete the BounceTrackingRecord.
+ await BrowserTestUtils.browserLoaded(browser, false, targetURL);
+
+ // 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(
+ browser,
+ new URL(getBaseUrl(ORIGIN_C) + "file_start.html")
+ );
+
+ await promiseRecordBounces;
+
+ Assert.deepEqual(
+ bounceTrackingProtection.testGetBounceTrackerCandidateHosts(
+ originAttributes
+ ),
+ expectCandidate ? [SITE_TRACKER] : [],
+ `Should ${
+ expectCandidate ? "" : "not "
+ }have identified ${SITE_TRACKER} as a bounce tracker.`
+ );
+ Assert.deepEqual(
+ bounceTrackingProtection
+ .testGetUserActivationHosts(originAttributes)
+ .sort(),
+ [SITE_A, SITE_B].sort(),
+ "Should only have user activation for sites where we clicked links."
+ );
+
+ // If the caller specified a function to run after the bounce, run it now.
+ await postBounceCallback();
+
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ expectPurge ? [SITE_TRACKER] : [],
+ `Should ${expectPurge ? "" : "not "}purge state for ${SITE_TRACKER}.`
+ );
+
+ // Clean up
+ BrowserTestUtils.removeTab(tab);
+ if (usePrivateWindow) {
+ await BrowserTestUtils.closeWindow(win);
+
+ info(
+ "Closing the last PBM window should trigger a purge of all PBM state."
+ );
+ Assert.ok(
+ !bounceTrackingProtection.testGetBounceTrackerCandidateHosts(
+ originAttributes
+ ).length,
+ "No bounce tracker hosts after closing private window."
+ );
+ Assert.ok(
+ !bounceTrackingProtection.testGetUserActivationHosts(originAttributes)
+ .length,
+ "No user activation hosts after closing private window."
+ );
+ }
+ bounceTrackingProtection.clearAll();
+}