diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/antitracking/bouncetrackingprotection/test/browser | |
parent | Initial commit. (diff) | |
download | firefox-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')
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(); +} |