From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../bouncetrackingprotection/test/browser/head.js | 275 +++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js (limited to 'toolkit/components/antitracking/bouncetrackingprotection/test/browser/head.js') 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 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(); +} -- cgit v1.2.3