Bug 1843308 - Add tests for bounce tracking using popups and new tabs. r=bvandersloot,anti-tracking-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D206758
This commit is contained in:
Paul Zuehlcke 2024-04-11 19:03:19 +00:00
Родитель f4706ee481
Коммит 10905769fd
4 изменённых файлов: 189 добавлений и 22 удалений

Просмотреть файл

@ -16,6 +16,8 @@ support-files = [
["browser_bouncetracking_oa_isolation.js"]
["browser_bouncetracking_popup.js"]
["browser_bouncetracking_purge.js"]
["browser_bouncetracking_schemes.js"]

Просмотреть файл

@ -3,10 +3,6 @@
"use strict";
const { SiteDataTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/SiteDataTestUtils.sys.mjs"
);
const TEST_ORIGIN = "https://itisatracker.org";
const TEST_BASE_DOMAIN = "itisatracker.org";
@ -28,6 +24,7 @@ async function runPurgeTest(expectPurge) {
await runTestBounce({
bounceType: "client",
setState: "localStorage",
skipSiteDataCleanup: true,
postBounceCallback: () => {
info(
"Test that after the bounce but before purging cookies and localStorage are present."

Просмотреть файл

@ -0,0 +1,124 @@
/* 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({}),
[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");
});

Просмотреть файл

@ -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.
@ -122,23 +126,56 @@ 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");
async function navigateLinkClick(
browser,
targetURL,
{ spawnWindow = null } = {}
) {
if (spawnWindow && !["newTab", "popup"].includes(spawnWindow)) {
throw new Error(`Invalid option '${spawnWindow}' for spawnWindow`);
}
link.href = targetURL;
link.textContent = targetURL;
// The link needs display: block, otherwise synthesizeMouseAtCenter doesn't
// hit it.
link.style.display = "block";
await SpecialPowers.spawn(
browser,
[targetURL.href, spawnWindow],
async (targetURL, spawnWindow) => {
let link = content.document.createElement("a");
content.document.body.appendChild(link);
});
// 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;
}
await BrowserTestUtils.synthesizeMouseAtCenter("a[href]", {}, browser);
link.textContent = targetURL;
// The link needs display: block, otherwise synthesizeMouseAtCenter doesn't
// hit it.
link.style.display = "block";
content.document.body.appendChild(link);
await EventUtils.synthesizeMouse(link, 1, 1, {}, content);
}
);
}
/**
@ -185,6 +222,9 @@ async function waitForRecordBounces(browser) {
* 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 {
@ -197,6 +237,7 @@ async function runTestBounce(options = {}) {
expectPurge = true,
originAttributes = {},
postBounceCallback = () => {},
skipSiteDataCleanup = false,
} = options;
info(`runTestBounce ${JSON.stringify(options)}`);
@ -316,4 +357,7 @@ async function runTestBounce(options = {}) {
);
}
bounceTrackingProtection.clearAll();
if (!skipSiteDataCleanup) {
await SiteDataTestUtils.clear();
}
}