diff options
Diffstat (limited to 'toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js')
-rw-r--r-- | toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js | 612 |
1 files changed, 612 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js b/toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js new file mode 100644 index 0000000000..f50e515c84 --- /dev/null +++ b/toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js @@ -0,0 +1,612 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { RemoteSettings } = ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "peuService", + "@mozilla.org/partitioning/exception-list-service;1", + "nsIPartitioningExceptionListService" +); + +const TEST_REDIRECT_TOP_PAGE = + TEST_3RD_PARTY_DOMAIN + TEST_PATH + "redirect.sjs?" + TEST_TOP_PAGE; +const TEST_REDIRECT_3RD_PARTY_PAGE = + TEST_DOMAIN + TEST_PATH + "redirect.sjs?" + TEST_3RD_PARTY_PARTITIONED_PAGE; + +const COLLECTION_NAME = "partitioning-exempt-urls"; +const EXCEPTION_LIST_PREF_NAME = "privacy.restrict3rdpartystorage.skip_list"; + +async function cleanup() { + Services.prefs.clearUserPref(EXCEPTION_LIST_PREF_NAME); + await new Promise(resolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => + resolve() + ); + }); +} + +add_setup(async function () { + await SpecialPowers.flushPrefEnv(); + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "network.cookie.cookieBehavior", + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, + ], + [ + "network.cookie.cookieBehavior.pbmode", + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, + ], + ["privacy.restrict3rdpartystorage.heuristic.redirect", false], + ["privacy.trackingprotection.enabled", false], + ["privacy.trackingprotection.pbmode.enabled", false], + ["privacy.trackingprotection.annotate_channels", true], + // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default" + ["network.cookie.sameSite.laxByDefault", false], + ], + }); + registerCleanupFunction(cleanup); +}); + +function executeContentScript(browser, callback, options = {}) { + return SpecialPowers.spawn( + browser, + [ + { + callback: callback.toString(), + ...options, + }, + ], + obj => { + return new content.Promise(async resolve => { + if (obj.page) { + // third-party + let ifr = content.document.createElement("iframe"); + ifr.onload = async () => { + info("Sending code to the 3rd party content"); + ifr.contentWindow.postMessage( + { cb: obj.callback, value: obj.value }, + "*" + ); + }; + + content.addEventListener("message", event => resolve(event.data), { + once: true, + }); + + content.document.body.appendChild(ifr); + ifr.src = obj.page; + } else { + // first-party + let runnableStr = `(() => {return (${obj.callback});})();`; + let runnable = eval(runnableStr); // eslint-disable-line no-eval + resolve(await runnable.call(content, content, obj.value)); + } + }); + } + ); +} + +function readNetworkCookie(win) { + return win + .fetch("cookies.sjs") + .then(r => r.text()) + .then(text => { + return text.substring("cookie:foopy=".length); + }); +} + +async function writeNetworkCookie(win, value) { + await win.fetch("cookies.sjs?" + value).then(r => r.text()); + return true; +} + +function createDataInFirstParty(browser, value) { + return executeContentScript(browser, writeNetworkCookie, { value }); +} +function getDataFromFirstParty(browser) { + return executeContentScript(browser, readNetworkCookie, {}); +} +function createDataInThirdParty(browser, value) { + return executeContentScript(browser, writeNetworkCookie, { + page: TEST_3RD_PARTY_PARTITIONED_PAGE, + value, + }); +} +function getDataFromThirdParty(browser) { + return executeContentScript(browser, readNetworkCookie, { + page: TEST_3RD_PARTY_PARTITIONED_PAGE, + }); +} + +async function redirectWithUserInteraction(browser, url, wait = null) { + await executeContentScript( + browser, + (content, value) => { + content.document.userInteractionForTesting(); + + let link = content.document.createElement("a"); + link.appendChild(content.document.createTextNode("click me!")); + link.href = value; + content.document.body.appendChild(link); + link.click(); + }, + { + value: url, + } + ); + await BrowserTestUtils.browserLoaded(browser, false, wait || url); +} + +async function checkData(browser, options) { + if ("firstParty" in options) { + is( + await getDataFromFirstParty(browser), + options.firstParty, + "correct first-party data" + ); + } + if ("thirdParty" in options) { + is( + await getDataFromThirdParty(browser), + options.thirdParty, + "correct third-party data" + ); + } +} + +async function runTestRedirectHeuristic(disableHeuristics) { + info("Starting Dynamic FPI Redirect Heuristic test..."); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.restrict3rdpartystorage.heuristic.recently_visited", true], + ["privacy.antitracking.enableWebcompat", !disableHeuristics], + ], + }); + + // mark third-party as tracker + await UrlClassifierTestUtils.addTestTrackers(); + + info("Creating a new tab"); + let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + info("initializing..."); + await checkData(browser, { firstParty: "", thirdParty: "" }); + + await Promise.all([ + createDataInFirstParty(browser, "firstParty"), + createDataInThirdParty(browser, "thirdParty"), + ]); + + await checkData(browser, { + firstParty: "firstParty", + thirdParty: "", + }); + + info("load third-party content as first-party"); + await redirectWithUserInteraction( + browser, + TEST_REDIRECT_3RD_PARTY_PAGE, + TEST_3RD_PARTY_PARTITIONED_PAGE + ); + + await checkData(browser, { firstParty: "" }); + await createDataInFirstParty(browser, "heuristicFirstParty"); + await checkData(browser, { firstParty: "heuristicFirstParty" }); + + info("redirect back to first-party page"); + await redirectWithUserInteraction( + browser, + TEST_REDIRECT_TOP_PAGE, + TEST_TOP_PAGE + ); + + info("third-party tracker should NOT able to access first-party data"); + await checkData(browser, { + firstParty: "firstParty", + thirdParty: "", + }); + + // remove third-party from tracker + await UrlClassifierTestUtils.cleanupTestTrackers(); + + info("load third-party content as first-party"); + await redirectWithUserInteraction( + browser, + TEST_REDIRECT_3RD_PARTY_PAGE, + TEST_3RD_PARTY_PARTITIONED_PAGE + ); + + await checkData(browser, { + firstParty: "heuristicFirstParty", + }); + + info("redirect back to first-party page"); + await redirectWithUserInteraction( + browser, + TEST_REDIRECT_TOP_PAGE, + TEST_TOP_PAGE + ); + + info( + `third-party page should ${ + disableHeuristics ? "not " : "" + }be able to access first-party data` + ); + await checkData(browser, { + firstParty: "firstParty", + thirdParty: disableHeuristics ? "" : "heuristicFirstParty", + }); + + info("Removing the tab"); + BrowserTestUtils.removeTab(tab); + + await SpecialPowers.popPrefEnv(); + + await cleanup(); +} + +add_task(async function testRedirectHeuristic() { + await runTestRedirectHeuristic(false); +}); + +add_task(async function testRedirectHeuristicDisabled() { + await runTestRedirectHeuristic(true); +}); + +class UpdateEvent extends EventTarget {} +function waitForEvent(element, eventName) { + return new Promise(function (resolve) { + element.addEventListener(eventName, e => resolve(e.detail), { once: true }); + }); +} + +// The test URLs have a trailing / which means they're not valid origins. +const TEST_ORIGIN = TEST_DOMAIN.substring(0, TEST_DOMAIN.length - 1); +const TEST_3RD_PARTY_ORIGIN = TEST_3RD_PARTY_DOMAIN.substring( + 0, + TEST_3RD_PARTY_DOMAIN.length - 1 +); + +async function runTestExceptionListPref(disableHeuristics) { + info("Starting Dynamic FPI exception list test pref"); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.restrict3rdpartystorage.heuristic.recently_visited", false], + ["privacy.antitracking.enableWebcompat", !disableHeuristics], + ], + }); + + info("Creating new tabs"); + let tabThirdParty = BrowserTestUtils.addTab( + gBrowser, + TEST_3RD_PARTY_PARTITIONED_PAGE + ); + gBrowser.selectedTab = tabThirdParty; + + let browserThirdParty = gBrowser.getBrowserForTab(tabThirdParty); + await BrowserTestUtils.browserLoaded(browserThirdParty); + + let tabFirstParty = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE); + gBrowser.selectedTab = tabFirstParty; + + let browserFirstParty = gBrowser.getBrowserForTab(tabFirstParty); + await BrowserTestUtils.browserLoaded(browserFirstParty); + + info("initializing..."); + await Promise.all([ + checkData(browserFirstParty, { firstParty: "", thirdParty: "" }), + checkData(browserThirdParty, { firstParty: "" }), + ]); + + info("fill default data"); + await Promise.all([ + createDataInFirstParty(browserFirstParty, "firstParty"), + createDataInThirdParty(browserFirstParty, "thirdParty"), + createDataInFirstParty(browserThirdParty, "ExceptionListFirstParty"), + ]); + + info("check data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: "thirdParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("set exception list pref"); + Services.prefs.setStringPref( + EXCEPTION_LIST_PREF_NAME, + `${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN}` + ); + + info("check data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: disableHeuristics ? "thirdParty" : "ExceptionListFirstParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("set incomplete exception list pref"); + Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, `${TEST_ORIGIN}`); + + info("check data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: "thirdParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("set exception list pref, with extra semicolons"); + Services.prefs.setStringPref( + EXCEPTION_LIST_PREF_NAME, + `;${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN};;` + ); + + info("check data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: disableHeuristics ? "thirdParty" : "ExceptionListFirstParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("set exception list pref, with subdomain wildcard"); + Services.prefs.setStringPref( + EXCEPTION_LIST_PREF_NAME, + `${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN.replace("tracking", "*")}` + ); + + info("check data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: disableHeuristics ? "thirdParty" : "ExceptionListFirstParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("Removing the tab"); + BrowserTestUtils.removeTab(tabFirstParty); + BrowserTestUtils.removeTab(tabThirdParty); + + await SpecialPowers.popPrefEnv(); + + await cleanup(); +} + +add_task(async function testExceptionListPref() { + await runTestExceptionListPref(false); +}); + +add_task(async function testExceptionListPrefDisabled() { + await runTestExceptionListPref(true); +}); + +add_task(async function testExceptionListRemoteSettings() { + info("Starting Dynamic FPI exception list test (remote settings)"); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.restrict3rdpartystorage.heuristic.recently_visited", false], + ], + }); + + // Make sure we have a pref initially, since the exception list service + // requires it. + Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, ""); + + // Add some initial data + let db = RemoteSettings(COLLECTION_NAME).db; + await db.importChanges({}, Date.now(), []); + + // make peuSerivce start working by calling + // registerAndRunExceptionListObserver + let updateEvent = new UpdateEvent(); + let obs = data => { + let event = new CustomEvent("update", { detail: data }); + updateEvent.dispatchEvent(event); + }; + let promise = waitForEvent(updateEvent, "update"); + peuService.registerAndRunExceptionListObserver(obs); + await promise; + + info("Creating new tabs"); + let tabThirdParty = BrowserTestUtils.addTab( + gBrowser, + TEST_3RD_PARTY_PARTITIONED_PAGE + ); + gBrowser.selectedTab = tabThirdParty; + + let browserThirdParty = gBrowser.getBrowserForTab(tabThirdParty); + await BrowserTestUtils.browserLoaded(browserThirdParty); + + let tabFirstParty = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE); + gBrowser.selectedTab = tabFirstParty; + + let browserFirstParty = gBrowser.getBrowserForTab(tabFirstParty); + await BrowserTestUtils.browserLoaded(browserFirstParty); + + info("initializing..."); + await Promise.all([ + checkData(browserFirstParty, { firstParty: "", thirdParty: "" }), + checkData(browserThirdParty, { firstParty: "" }), + ]); + + info("fill default data"); + await Promise.all([ + createDataInFirstParty(browserFirstParty, "firstParty"), + createDataInThirdParty(browserFirstParty, "thirdParty"), + createDataInFirstParty(browserThirdParty, "ExceptionListFirstParty"), + ]); + + info("check data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: "thirdParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("set exception list remote settings"); + + // set records + promise = waitForEvent(updateEvent, "update"); + await RemoteSettings(COLLECTION_NAME).emit("sync", { + data: { + current: [ + { + id: "1", + last_modified: 1000000000000001, + firstPartyOrigin: TEST_ORIGIN, + thirdPartyOrigin: TEST_3RD_PARTY_ORIGIN, + }, + ], + }, + }); + + let list = await promise; + is( + list, + `${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN}`, + "exception list is correctly set" + ); + + info("check data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: "ExceptionListFirstParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("Removing the tab"); + BrowserTestUtils.removeTab(tabFirstParty); + BrowserTestUtils.removeTab(tabThirdParty); + + promise = waitForEvent(updateEvent, "update"); + await RemoteSettings(COLLECTION_NAME).emit("sync", { + data: { + current: [], + }, + }); + is(await promise, "", "Exception list is cleared"); + + peuService.unregisterExceptionListObserver(obs); + await cleanup(); +}); + +add_task(async function testWildcardExceptionListPref() { + info("Starting Dynamic FPI wirdcard exception list test pref"); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.restrict3rdpartystorage.heuristic.recently_visited", false], + ], + }); + + info("Creating new tabs"); + let tabThirdParty = BrowserTestUtils.addTab( + gBrowser, + TEST_3RD_PARTY_PARTITIONED_PAGE + ); + gBrowser.selectedTab = tabThirdParty; + + let browserThirdParty = gBrowser.getBrowserForTab(tabThirdParty); + await BrowserTestUtils.browserLoaded(browserThirdParty); + + let tabFirstParty = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE); + gBrowser.selectedTab = tabFirstParty; + + let browserFirstParty = gBrowser.getBrowserForTab(tabFirstParty); + await BrowserTestUtils.browserLoaded(browserFirstParty); + + info("initializing..."); + await Promise.all([ + checkData(browserFirstParty, { firstParty: "", thirdParty: "" }), + checkData(browserThirdParty, { firstParty: "" }), + ]); + + info("fill default data"); + await Promise.all([ + createDataInFirstParty(browserFirstParty, "firstParty"), + createDataInThirdParty(browserFirstParty, "thirdParty"), + createDataInFirstParty(browserThirdParty, "ExceptionListFirstParty"), + ]); + + info("check initial data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: "thirdParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("set wildcard (1st-party) pref"); + Services.prefs.setStringPref( + EXCEPTION_LIST_PREF_NAME, + `*,${TEST_3RD_PARTY_ORIGIN}` + ); + + info("check wildcard (1st-party) data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: "ExceptionListFirstParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("set invalid exception list pref"); + Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, "*,*"); + + info("check initial data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: "thirdParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("set wildcard (3rd-party) pref"); + Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, `${TEST_ORIGIN},*`); + + info("check wildcard (3rd-party) data"); + await Promise.all([ + checkData(browserFirstParty, { + firstParty: "firstParty", + thirdParty: "ExceptionListFirstParty", + }), + checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }), + ]); + + info("Removing the tab"); + BrowserTestUtils.removeTab(tabFirstParty); + BrowserTestUtils.removeTab(tabThirdParty); + + await cleanup(); +}); |