forked from mirrors/gecko-dev
		
	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:
		
							parent
							
								
									f4706ee481
								
							
						
					
					
						commit
						10905769fd
					
				
					 4 changed files with 189 additions and 22 deletions
				
			
		|  | @ -16,6 +16,8 @@ support-files = [ | ||||||
| 
 | 
 | ||||||
| ["browser_bouncetracking_oa_isolation.js"] | ["browser_bouncetracking_oa_isolation.js"] | ||||||
| 
 | 
 | ||||||
|  | ["browser_bouncetracking_popup.js"] | ||||||
|  | 
 | ||||||
| ["browser_bouncetracking_purge.js"] | ["browser_bouncetracking_purge.js"] | ||||||
| 
 | 
 | ||||||
| ["browser_bouncetracking_schemes.js"] | ["browser_bouncetracking_schemes.js"] | ||||||
|  |  | ||||||
|  | @ -3,10 +3,6 @@ | ||||||
| 
 | 
 | ||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| const { SiteDataTestUtils } = ChromeUtils.importESModule( |  | ||||||
|   "resource://testing-common/SiteDataTestUtils.sys.mjs" |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| const TEST_ORIGIN = "https://itisatracker.org"; | const TEST_ORIGIN = "https://itisatracker.org"; | ||||||
| const TEST_BASE_DOMAIN = "itisatracker.org"; | const TEST_BASE_DOMAIN = "itisatracker.org"; | ||||||
| 
 | 
 | ||||||
|  | @ -28,6 +24,7 @@ async function runPurgeTest(expectPurge) { | ||||||
|   await runTestBounce({ |   await runTestBounce({ | ||||||
|     bounceType: "client", |     bounceType: "client", | ||||||
|     setState: "localStorage", |     setState: "localStorage", | ||||||
|  |     skipSiteDataCleanup: true, | ||||||
|     postBounceCallback: () => { |     postBounceCallback: () => { | ||||||
|       info( |       info( | ||||||
|         "Test that after the bounce but before purging cookies and localStorage are present." |         "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"; | "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 SITE_A = "example.com"; | ||||||
| const ORIGIN_A = `https://${SITE_A}`; | 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); | 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. |  * Get the base url for the current test directory using the given origin. | ||||||
|  * @param {string} origin - Origin to use in URL. |  * @param {string} origin - Origin to use in URL. | ||||||
|  | @ -122,23 +126,56 @@ function getBounceURL({ | ||||||
|  * click on it. |  * click on it. | ||||||
|  * @param {MozBrowser} browser - Browser to insert the link in. |  * @param {MozBrowser} browser - Browser to insert the link in. | ||||||
|  * @param {URL} targetURL - Destination for navigation. |  * @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 |  * @returns {Promise} Resolves once the click is done. Does not wait for | ||||||
|  * navigation or load. |  * navigation or load. | ||||||
|  */ |  */ | ||||||
| async function navigateLinkClick(browser, targetURL) { | async function navigateLinkClick( | ||||||
|   await SpecialPowers.spawn(browser, [targetURL.href], targetURL => { |   browser, | ||||||
|  |   targetURL, | ||||||
|  |   { spawnWindow = null } = {} | ||||||
|  | ) { | ||||||
|  |   if (spawnWindow && !["newTab", "popup"].includes(spawnWindow)) { | ||||||
|  |     throw new Error(`Invalid option '${spawnWindow}' for spawnWindow`); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   await SpecialPowers.spawn( | ||||||
|  |     browser, | ||||||
|  |     [targetURL.href, spawnWindow], | ||||||
|  |     async (targetURL, spawnWindow) => { | ||||||
|       let link = content.document.createElement("a"); |       let link = content.document.createElement("a"); | ||||||
| 
 | 
 | ||||||
|  |       // 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; |         link.href = targetURL; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       link.textContent = targetURL; |       link.textContent = targetURL; | ||||||
|       // The link needs display: block, otherwise synthesizeMouseAtCenter doesn't
 |       // The link needs display: block, otherwise synthesizeMouseAtCenter doesn't
 | ||||||
|       // hit it.
 |       // hit it.
 | ||||||
|       link.style.display = "block"; |       link.style.display = "block"; | ||||||
| 
 | 
 | ||||||
|       content.document.body.appendChild(link); |       content.document.body.appendChild(link); | ||||||
|   }); |  | ||||||
| 
 | 
 | ||||||
|   await BrowserTestUtils.synthesizeMouseAtCenter("a[href]", {}, browser); |       await EventUtils.synthesizeMouse(link, 1, 1, {}, content); | ||||||
|  |     } | ||||||
|  |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -185,6 +222,9 @@ async function waitForRecordBounces(browser) { | ||||||
|  * normal browsing. |  * normal browsing. | ||||||
|  * @param {function} [options.postBounceCallback] - Optional function to run |  * @param {function} [options.postBounceCallback] - Optional function to run | ||||||
|  * after the bounce has completed. |  * 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 = {}) { | async function runTestBounce(options = {}) { | ||||||
|   let { |   let { | ||||||
|  | @ -197,6 +237,7 @@ async function runTestBounce(options = {}) { | ||||||
|     expectPurge = true, |     expectPurge = true, | ||||||
|     originAttributes = {}, |     originAttributes = {}, | ||||||
|     postBounceCallback = () => {}, |     postBounceCallback = () => {}, | ||||||
|  |     skipSiteDataCleanup = false, | ||||||
|   } = options; |   } = options; | ||||||
|   info(`runTestBounce ${JSON.stringify(options)}`); |   info(`runTestBounce ${JSON.stringify(options)}`); | ||||||
| 
 | 
 | ||||||
|  | @ -316,4 +357,7 @@ async function runTestBounce(options = {}) { | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   bounceTrackingProtection.clearAll(); |   bounceTrackingProtection.clearAll(); | ||||||
|  |   if (!skipSiteDataCleanup) { | ||||||
|  |     await SiteDataTestUtils.clear(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Paul Zuehlcke
						Paul Zuehlcke