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_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(); | ||||
|   } | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Paul Zuehlcke
						Paul Zuehlcke