summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/test/browser/antitracking_head.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/antitracking/test/browser/antitracking_head.js')
-rw-r--r--toolkit/components/antitracking/test/browser/antitracking_head.js1392
1 files changed, 1392 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/test/browser/antitracking_head.js b/toolkit/components/antitracking/test/browser/antitracking_head.js
new file mode 100644
index 0000000000..52871b60c2
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/antitracking_head.js
@@ -0,0 +1,1392 @@
+/* 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/. */
+
+/* import-globals-from head.js */
+
+"use strict";
+
+var gFeatures = undefined;
+var gTestTrackersCleanedUp = false;
+var gTestTrackersCleanupRegistered = false;
+
+/**
+ * Force garbage collection.
+ */
+function forceGC() {
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+}
+
+this.AntiTracking = {
+ runTestInNormalAndPrivateMode(
+ name,
+ callbackTracking,
+ callbackNonTracking,
+ cleanupFunction,
+ extraPrefs,
+ windowOpenTest = true,
+ userInteractionTest = true,
+ expectedBlockingNotifications = Ci.nsIWebProgressListener
+ .STATE_COOKIES_BLOCKED_TRACKER,
+ iframeSandbox = null,
+ accessRemoval = null,
+ callbackAfterRemoval = null,
+ iframeAllow = null
+ ) {
+ // Normal mode
+ this.runTest(
+ name,
+ callbackTracking,
+ callbackNonTracking,
+ cleanupFunction,
+ extraPrefs,
+ windowOpenTest,
+ userInteractionTest,
+ expectedBlockingNotifications,
+ false,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval,
+ iframeAllow
+ );
+
+ // Private mode
+ this.runTest(
+ name,
+ callbackTracking,
+ callbackNonTracking,
+ cleanupFunction,
+ extraPrefs,
+ windowOpenTest,
+ userInteractionTest,
+ expectedBlockingNotifications,
+ true,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval,
+ iframeAllow
+ );
+ },
+
+ runTest(
+ name,
+ callbackTracking,
+ callbackNonTracking,
+ cleanupFunction,
+ extraPrefs,
+ windowOpenTest = true,
+ userInteractionTest = true,
+ expectedBlockingNotifications = Ci.nsIWebProgressListener
+ .STATE_COOKIES_BLOCKED_TRACKER,
+ runInPrivateWindow = false,
+ iframeSandbox = null,
+ accessRemoval = null,
+ callbackAfterRemoval = null,
+ iframeAllow = null
+ ) {
+ let runExtraTests = true;
+ let options = {};
+ if (typeof callbackNonTracking == "object" && !!callbackNonTracking) {
+ options.callback = callbackNonTracking.callback;
+ runExtraTests = callbackNonTracking.runExtraTests;
+ if ("cookieBehavior" in callbackNonTracking) {
+ options.cookieBehavior = callbackNonTracking.cookieBehavior;
+ } else {
+ options.cookieBehavior = BEHAVIOR_ACCEPT;
+ }
+ if ("expectedBlockingNotifications" in callbackNonTracking) {
+ options.expectedBlockingNotifications =
+ callbackNonTracking.expectedBlockingNotifications;
+ } else {
+ options.expectedBlockingNotifications = 0;
+ }
+ if ("blockingByAllowList" in callbackNonTracking) {
+ options.blockingByAllowList = callbackNonTracking.blockingByAllowList;
+ if (options.blockingByAllowList) {
+ // If we're on the allow list, there won't be any blocking!
+ options.expectedBlockingNotifications = 0;
+ }
+ } else {
+ options.blockingByAllowList = false;
+ }
+ callbackNonTracking = options.callback;
+ options.accessRemoval = null;
+ options.callbackAfterRemoval = null;
+ }
+
+ // Here we want to test that a 3rd party context is simply blocked.
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ allowList: false,
+ callback: callbackTracking,
+ extraPrefs,
+ expectedBlockingNotifications,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval,
+ iframeAllow,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ if (callbackNonTracking) {
+ // Phase 1: Here we want to test that a 3rd party context is not blocked if pref is off.
+ if (runExtraTests) {
+ // There are five ways in which the third-party context may not be blocked:
+ // * If the cookieBehavior pref causes it to not be blocked.
+ // * If the contentBlocking pref causes it to not be blocked.
+ // * If both of these prefs cause it to not be blocked.
+ // * If the top-level page is on the content blocking allow list.
+ // * If the contentBlocking third-party cookies UI pref is off, the allow list will be ignored.
+ // All of these cases are tested here.
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_ACCEPT,
+ allowList: false,
+ callback: callbackNonTracking,
+ extraPrefs,
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ iframeAllow,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_ACCEPT,
+ allowList: true,
+ callback: callbackNonTracking,
+ extraPrefs,
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ iframeAllow,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT,
+ allowList: false,
+ callback: callbackTracking,
+ extraPrefs,
+ expectedBlockingNotifications: expectedBlockingNotifications
+ ? Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL
+ : 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ iframeAllow,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_LIMIT_FOREIGN,
+ allowList: true,
+ callback: callbackNonTracking,
+ extraPrefs,
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ iframeAllow,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name: name + " reject foreign with exception",
+ cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+ allowList: true,
+ callback: callbackNonTracking,
+ extraPrefs: [...(extraPrefs || [])],
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval,
+ iframeAllow,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
+ allowList: false,
+ callback: callbackTracking,
+ extraPrefs: [...(extraPrefs || [])],
+ expectedBlockingNotifications: expectedBlockingNotifications
+ ? Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN
+ : 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval,
+ iframeAllow,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ allowList: true,
+ callback: callbackNonTracking,
+ extraPrefs,
+ expectedBlockingNotifications: 0,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval,
+ callbackAfterRemoval,
+ iframeAllow,
+ });
+ this._createCleanupTask(cleanupFunction);
+
+ this._createTask({
+ name,
+ cookieBehavior: BEHAVIOR_REJECT_TRACKER,
+ allowList: false,
+ callback: callbackNonTracking,
+ extraPrefs,
+ expectedBlockingNotifications: false,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: null, // only passed with non-blocking callback
+ callbackAfterRemoval: null,
+ thirdPartyPage: TEST_ANOTHER_3RD_PARTY_PAGE,
+ iframeAllow,
+ });
+ this._createCleanupTask(cleanupFunction);
+ } else {
+ // This is only used for imageCacheWorker.js tests
+ this._createTask({
+ name,
+ cookieBehavior: options.cookieBehavior,
+ allowList: options.blockingByAllowList,
+ callback: options.callback,
+ extraPrefs,
+ expectedBlockingNotifications: options.expectedBlockingNotifications,
+ runInPrivateWindow,
+ iframeSandbox,
+ accessRemoval: options.accessRemoval,
+ callbackAfterRemoval: options.callbackAfterRemoval,
+ iframeAllow,
+ });
+ this._createCleanupTask(cleanupFunction);
+ }
+
+ // Phase 2: Here we want to test that a third-party context doesn't
+ // get blocked with when the same origin is opened through window.open().
+ if (windowOpenTest) {
+ this._createWindowOpenTask(
+ name,
+ BEHAVIOR_REJECT_TRACKER,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ false,
+ extraPrefs,
+ iframeAllow
+ );
+ this._createCleanupTask(cleanupFunction);
+
+ // Now, check if it works for nested iframes.
+ this._createWindowOpenTask(
+ name,
+ BEHAVIOR_REJECT_TRACKER,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ true,
+ extraPrefs,
+ iframeAllow
+ );
+ this._createCleanupTask(cleanupFunction);
+ }
+
+ // Phase 3: Here we want to test that a third-party context doesn't
+ // get blocked with user interaction present
+ if (userInteractionTest) {
+ this._createUserInteractionTask(
+ name,
+ BEHAVIOR_REJECT_TRACKER,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ false,
+ extraPrefs,
+ iframeAllow
+ );
+ this._createCleanupTask(cleanupFunction);
+
+ // Now, check if it works for nested iframes.
+ this._createUserInteractionTask(
+ name,
+ BEHAVIOR_REJECT_TRACKER,
+ callbackTracking,
+ callbackNonTracking,
+ runInPrivateWindow,
+ iframeSandbox,
+ true,
+ extraPrefs,
+ iframeAllow
+ );
+ this._createCleanupTask(cleanupFunction);
+ }
+ }
+ },
+
+ _waitObserver(targetTopic, expectedCount) {
+ let cnt = 0;
+
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(subject, topic, data) {
+ if (topic != targetTopic) {
+ return;
+ }
+ cnt++;
+
+ if (cnt != expectedCount) {
+ return;
+ }
+
+ Services.obs.removeObserver(observer, targetTopic);
+ resolve();
+ }, targetTopic);
+ });
+ },
+
+ _waitUserInteractionPerm() {
+ return this._waitObserver(
+ "antitracking-test-user-interaction-perm-added",
+ 1
+ );
+ },
+
+ _waitStorageAccessPerm(expectedCount) {
+ return this._waitObserver(
+ "antitracking-test-storage-access-perm-added",
+ expectedCount
+ );
+ },
+
+ async interactWithTracker() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url: TEST_3RD_PARTY_PAGE },
+ async function (browser) {
+ info("Let's interact with the tracker");
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ SpecialPowers.wrap(content.document).userInteractionForTesting();
+ });
+ }
+ );
+ await BrowserTestUtils.closeWindow(win);
+ },
+
+ async _setupTest(win, cookieBehavior, runInPrivateWindow, extraPrefs) {
+ await SpecialPowers.flushPrefEnv();
+
+ await setCookieBehaviorPref(cookieBehavior, runInPrivateWindow);
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.storage_access.enabled", true],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.pbmode.enabled", false],
+ ["dom.security.https_first_pbm", false],
+ [
+ "privacy.trackingprotection.annotate_channels",
+ cookieBehavior != BEHAVIOR_ACCEPT,
+ ],
+ ["privacy.restrict3rdpartystorage.console.lazy", false],
+ [
+ "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
+ "tracking.example.com,tracking.example.org",
+ ],
+ ["privacy.antitracking.testing", true],
+ ],
+ });
+
+ if (extraPrefs && Array.isArray(extraPrefs) && extraPrefs.length) {
+ await SpecialPowers.pushPrefEnv({ set: extraPrefs });
+
+ let enableWebcompat = Services.prefs.getBoolPref(
+ "privacy.antitracking.enableWebcompat"
+ );
+
+ // If the skip list is disabled by pref, it will always return an empty
+ // list.
+ if (enableWebcompat) {
+ for (let item of extraPrefs) {
+ // When setting up exception URLs, we need to wait to ensure our prefs
+ // actually take effect. In order to do this, we set up a exception
+ // list observer and wait until it calls us back.
+ if (item[0] == "urlclassifier.trackingAnnotationSkipURLs") {
+ info("Waiting for the exception list service to initialize...");
+ let classifier = Cc[
+ "@mozilla.org/url-classifier/dbservice;1"
+ ].getService(Ci.nsIURIClassifier);
+ let feature = classifier.getFeatureByName("tracking-annotation");
+ await TestUtils.waitForCondition(() => {
+ for (let x of item[1].toLowerCase().split(",")) {
+ if (feature.exceptionHostList.split(",").includes(x)) {
+ return true;
+ }
+ }
+ return false;
+ }, "Exception list service initialized");
+ break;
+ }
+ }
+ }
+ }
+
+ await UrlClassifierTestUtils.addTestTrackers();
+ if (!gTestTrackersCleanupRegistered) {
+ registerCleanupFunction(_ => {
+ if (gTestTrackersCleanedUp) {
+ return;
+ }
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ gTestTrackersCleanedUp = true;
+ });
+ gTestTrackersCleanupRegistered = true;
+ }
+ },
+
+ _createTask(options) {
+ add_task(async function () {
+ info(
+ "Starting " +
+ (options.cookieBehavior != BEHAVIOR_ACCEPT
+ ? "blocking"
+ : "non-blocking") +
+ " cookieBehavior (" +
+ options.cookieBehavior +
+ ") with" +
+ (options.allowList ? "" : "out") +
+ " allow list test " +
+ options.name +
+ " running in a " +
+ (options.runInPrivateWindow ? "private" : "normal") +
+ " window " +
+ " with iframe sandbox set to " +
+ options.iframeSandbox +
+ " and iframe allow set to " +
+ options.iframeAllow +
+ " and access removal set to " +
+ options.accessRemoval +
+ (typeof options.thirdPartyPage == "string"
+ ? " and third party page set to " + options.thirdPartyPage
+ : "") +
+ (typeof options.topPage == "string"
+ ? " and top page set to " + options.topPage
+ : "")
+ );
+
+ is(
+ !!options.callbackAfterRemoval,
+ !!options.accessRemoval,
+ "callbackAfterRemoval must be passed when accessRemoval is non-null"
+ );
+
+ let win = window;
+ if (options.runInPrivateWindow) {
+ win = OpenBrowserWindow({ private: true });
+ await TestUtils.topicObserved("browser-delayed-startup-finished");
+ }
+
+ await AntiTracking._setupTest(
+ win,
+ options.cookieBehavior,
+ options.runInPrivateWindow,
+ options.extraPrefs
+ );
+
+ let topPage;
+ if (typeof options.topPage == "string") {
+ topPage = options.topPage;
+ } else {
+ topPage = TEST_TOP_PAGE;
+ }
+
+ let thirdPartyPage, thirdPartyDomainURI;
+ if (typeof options.thirdPartyPage == "string") {
+ thirdPartyPage = options.thirdPartyPage;
+ let url = new URL(thirdPartyPage);
+ thirdPartyDomainURI = Services.io.newURI(url.origin);
+ } else {
+ thirdPartyPage = TEST_3RD_PARTY_PAGE;
+ thirdPartyDomainURI = Services.io.newURI(TEST_3RD_PARTY_DOMAIN);
+ }
+
+ // It's possible that the third-party domain has been exceptionlisted
+ // through extraPrefs, so let's try annotating it here and adjust our
+ // blocking expectations as necessary.
+ if (
+ options.expectedBlockingNotifications ==
+ Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER
+ ) {
+ if (
+ !(await AntiTracking._isThirdPartyPageClassifiedAsTracker(
+ topPage,
+ thirdPartyDomainURI
+ ))
+ ) {
+ options.expectedBlockingNotifications = 0;
+ }
+ }
+
+ let cookieBlocked = 0;
+ let { expectedBlockingNotifications } = options;
+ if (!Array.isArray(expectedBlockingNotifications)) {
+ expectedBlockingNotifications = [expectedBlockingNotifications];
+ }
+ let listener = {
+ onContentBlockingEvent(webProgress, request, event) {
+ for (const notification of expectedBlockingNotifications) {
+ if (event & notification) {
+ ++cookieBlocked;
+ }
+ }
+ },
+ };
+ function prepareTestEnvironmentOnPage() {
+ win.gBrowser.addProgressListener(listener);
+
+ Services.console.reset();
+ }
+
+ if (!options.allowList) {
+ prepareTestEnvironmentOnPage();
+ }
+
+ let consoleWarningPromise;
+
+ if (options.expectedBlockingNotifications) {
+ consoleWarningPromise = new Promise(resolve => {
+ let consoleListener = {
+ observe(msg) {
+ if (
+ msg
+ .QueryInterface(Ci.nsIScriptError)
+ .category.startsWith("cookieBlocked")
+ ) {
+ Services.console.unregisterListener(consoleListener);
+ resolve();
+ }
+ },
+ };
+
+ Services.console.registerListener(consoleListener);
+ });
+ } else {
+ consoleWarningPromise = Promise.resolve();
+ }
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(win.gBrowser, topPage);
+ win.gBrowser.selectedTab = tab;
+
+ let browser = win.gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Check the cookieJarSettings of the browser object");
+ ok(
+ browser.cookieJarSettings,
+ "The browser object has the cookieJarSettings."
+ );
+ is(
+ browser.cookieJarSettings.cookieBehavior,
+ options.cookieBehavior,
+ "The cookieJarSettings has the correct cookieBehavior"
+ );
+
+ if (options.allowList) {
+ info("Disabling content blocking for this page");
+ win.gProtectionsHandler.disableForCurrentPage();
+
+ prepareTestEnvironmentOnPage();
+
+ // The previous function reloads the browser, so wait for it to load again!
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+
+ info("Creating a 3rd party content");
+ let doAccessRemovalChecks =
+ typeof options.accessRemoval == "string" &&
+ options.cookieBehavior == BEHAVIOR_REJECT_TRACKER &&
+ !options.allowList;
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: thirdPartyPage,
+ nextPage: TEST_4TH_PARTY_PAGE,
+ callback: options.callback.toString(),
+ callbackAfterRemoval: options.callbackAfterRemoval
+ ? options.callbackAfterRemoval.toString()
+ : null,
+ accessRemoval: options.accessRemoval,
+ iframeSandbox: options.iframeSandbox,
+ iframeAllow: options.iframeAllow,
+ allowList: options.allowList,
+ doAccessRemovalChecks,
+ },
+ ],
+ async function (obj) {
+ let id = "id" + Math.random();
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.id = id;
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ let callback = obj.allowList + "!!!" + obj.callback;
+ ifr.contentWindow.postMessage(callback, "*");
+ };
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+ if (typeof obj.iframeAllow == "string") {
+ ifr.setAttribute("allow", obj.iframeAllow);
+ }
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+
+ if (obj.doAccessRemovalChecks) {
+ info(`Running after removal checks (${obj.accessRemoval})`);
+ switch (obj.accessRemoval) {
+ case "navigate-subframe":
+ await new content.Promise(resolve => {
+ let ifr = content.document.getElementById(id);
+ let oldWindow = ifr.contentWindow;
+ ifr.onload = function () {
+ info("Sending code to the old 3rd party content");
+ oldWindow.postMessage(obj.callbackAfterRemoval, "*");
+ };
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+ if (typeof obj.iframeAllow == "string") {
+ ifr.setAttribute("allow", obj.iframeAllow);
+ }
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ ifr.src = obj.nextPage;
+ });
+ break;
+ case "navigate-topframe":
+ // pass-through
+ break;
+ default:
+ ok(
+ false,
+ "Unexpected accessRemoval code passed: " + obj.accessRemoval
+ );
+ break;
+ }
+ }
+ }
+ );
+
+ if (
+ doAccessRemovalChecks &&
+ options.accessRemoval == "navigate-topframe"
+ ) {
+ BrowserTestUtils.startLoadingURIString(browser, TEST_4TH_PARTY_PAGE);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let pageshow = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow"
+ );
+ gBrowser.goBack();
+ await pageshow;
+
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: thirdPartyPage,
+ callbackAfterRemoval: options.callbackAfterRemoval
+ ? options.callbackAfterRemoval.toString()
+ : null,
+ iframeSandbox: options.iframeSandbox,
+ iframeAllow: options.iframeAllow,
+ },
+ ],
+ async function (obj) {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info(
+ "Sending code to the 3rd party content to verify accessRemoval"
+ );
+ ifr.contentWindow.postMessage(obj.callbackAfterRemoval, "*");
+ };
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+ if (typeof obj.iframeAllow == "string") {
+ ifr.setAttribute("allow", obj.iframeAllow);
+ }
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ }
+ );
+ }
+
+ // Wait until the message appears on the console.
+ await consoleWarningPromise;
+
+ let allMessages = Services.console.getMessageArray().filter(msg => {
+ try {
+ // Select all messages that the anti-tracking backend could generate.
+ return msg
+ .QueryInterface(Ci.nsIScriptError)
+ .category.startsWith("cookieBlocked");
+ } catch (e) {
+ return false;
+ }
+ });
+ // When changing this list, please make sure to update the corresponding
+ // code in ReportBlockingToConsole().
+ let expectedCategories = [];
+ let rawExpectedCategories = options.expectedBlockingNotifications;
+ if (!Array.isArray(rawExpectedCategories)) {
+ // if given a single value to match, expect each message to match it
+ rawExpectedCategories = Array(allMessages.length).fill(
+ rawExpectedCategories
+ );
+ }
+ for (let category of rawExpectedCategories) {
+ switch (category) {
+ case Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_BY_PERMISSION:
+ expectedCategories.push("cookieBlockedPermission");
+ break;
+ case Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER:
+ expectedCategories.push("cookieBlockedTracker");
+ break;
+ case Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL:
+ expectedCategories.push("cookieBlockedAll");
+ break;
+ case Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN:
+ expectedCategories.push("cookieBlockedForeign");
+ break;
+ }
+ }
+
+ if (!expectedCategories.length) {
+ is(allMessages.length, 0, "No console messages should be generated");
+ } else {
+ ok(!!allMessages.length, "Some console message should be generated");
+ if (options.errorMessageDomains) {
+ is(
+ allMessages.length,
+ options.errorMessageDomains.length,
+ "Enough items provided in errorMessageDomains"
+ );
+ }
+ }
+ let index = 0;
+ for (let msg of allMessages) {
+ is(
+ msg.category,
+ expectedCategories[index],
+ `Message ${index} should be of expected category`
+ );
+
+ if (options.errorMessageDomains) {
+ ok(
+ msg.errorMessage.includes(options.errorMessageDomains[index]),
+ `Error message domain ${options.errorMessageDomains[index]} (${index}) found in "${msg.errorMessage}"`
+ );
+ index++;
+ }
+ }
+
+ if (options.allowList) {
+ info("Enabling content blocking for this page");
+ win.gProtectionsHandler.enableForCurrentPage();
+
+ // The previous function reloads the browser, so wait for it to load again!
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+
+ win.gBrowser.removeProgressListener(listener);
+
+ if (!!cookieBlocked != !!options.expectedBlockingNotifications) {
+ ok(false, JSON.stringify(cookieBlocked));
+ ok(false, JSON.stringify(options.expectedBlockingNotifications));
+ }
+ is(
+ !!cookieBlocked,
+ !!options.expectedBlockingNotifications,
+ "Checking cookie blocking notifications"
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ if (options.runInPrivateWindow) {
+ win.close();
+ }
+ });
+ },
+
+ _createCleanupTask(cleanupFunction) {
+ add_task(async function () {
+ info("Cleaning up.");
+ if (cleanupFunction) {
+ await cleanupFunction();
+ }
+
+ // While running these tests we typically do not have enough idle time to do
+ // GC reliably, so force it here.
+ forceGC();
+ });
+ },
+
+ _createWindowOpenTask(
+ name,
+ cookieBehavior,
+ blockingCallback,
+ nonBlockingCallback,
+ runInPrivateWindow,
+ iframeSandbox,
+ testInSubIFrame,
+ extraPrefs,
+ iframeAllow
+ ) {
+ add_task(async function () {
+ info(
+ `Starting window-open${
+ testInSubIFrame ? " sub iframe" : ""
+ } test ${name}`
+ );
+
+ let win = window;
+ if (runInPrivateWindow) {
+ win = OpenBrowserWindow({ private: true });
+ await TestUtils.topicObserved("browser-delayed-startup-finished");
+ }
+
+ await AntiTracking._setupTest(
+ win,
+ cookieBehavior,
+ runInPrivateWindow,
+ extraPrefs
+ );
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
+ win.gBrowser.selectedTab = tab;
+
+ let browser = win.gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Create a first-level iframe to test sub iframes.");
+ if (testInSubIFrame) {
+ let iframeBrowsingContext = await SpecialPowers.spawn(
+ browser,
+ [{ page: TEST_IFRAME_PAGE }],
+ async function (obj) {
+ // Add an iframe.
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ return ifr.browsingContext;
+ }
+ );
+
+ browser = iframeBrowsingContext;
+ }
+
+ let pageURL = TEST_3RD_PARTY_PAGE_WO;
+ if (gFeatures == "noopener") {
+ pageURL += "?noopener";
+ }
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: pageURL,
+ blockingCallback: blockingCallback.toString(),
+ nonBlockingCallback: nonBlockingCallback.toString(),
+ iframeSandbox,
+ iframeAllow,
+ },
+ ],
+ async function (obj) {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = function () {
+ info("Sending code to the 3rd party content");
+ ifr.contentWindow.postMessage(obj, "*");
+ };
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+ if (typeof obj.iframeAllow == "string") {
+ ifr.setAttribute("allow", obj.iframeAllow);
+ }
+
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ });
+ }
+ );
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ if (runInPrivateWindow) {
+ win.close();
+ }
+ });
+ },
+
+ _createUserInteractionTask(
+ name,
+ cookieBehavior,
+ blockingCallback,
+ nonBlockingCallback,
+ runInPrivateWindow,
+ iframeSandbox,
+ testInSubIFrame,
+ extraPrefs,
+ iframeAllow
+ ) {
+ add_task(async function () {
+ info(
+ `Starting user-interaction${
+ testInSubIFrame ? " sub iframe" : ""
+ } test ${name}`
+ );
+
+ let win = window;
+ if (runInPrivateWindow) {
+ win = OpenBrowserWindow({ private: true });
+ await TestUtils.topicObserved("browser-delayed-startup-finished");
+ }
+
+ await AntiTracking._setupTest(
+ win,
+ cookieBehavior,
+ runInPrivateWindow,
+ extraPrefs
+ );
+
+ info("Creating a new tab");
+ let tab = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
+ win.gBrowser.selectedTab = tab;
+
+ let browser = win.gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ if (testInSubIFrame) {
+ info("Create a first-level iframe to test sub iframes.");
+ let iframeBrowsingContext = await SpecialPowers.spawn(
+ browser,
+ [{ page: TEST_IFRAME_PAGE }],
+ async function (obj) {
+ // Add an iframe.
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ return ifr.browsingContext;
+ }
+ );
+
+ browser = iframeBrowsingContext;
+ }
+
+ // The following test will open an popup which interacts with the tracker
+ // page. So there will be an user-interaction permission added. We wait
+ // it explicitly.
+ let promiseUIPerm = AntiTracking._waitUserInteractionPerm();
+
+ info("Creating a 3rd party content");
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_UI,
+ popup: TEST_POPUP_PAGE,
+ blockingCallback: blockingCallback.toString(),
+ iframeSandbox,
+ iframeAllow,
+ },
+ ],
+ async function (obj) {
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+ if (typeof obj.iframeAllow == "string") {
+ ifr.setAttribute("allow", obj.iframeAllow);
+ }
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ info(
+ "The 3rd party content should not have access to first party storage."
+ );
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage(
+ { callback: obj.blockingCallback },
+ "*"
+ );
+ });
+
+ let windowClosed = new content.Promise(resolve => {
+ Services.ww.registerNotification(function notification(
+ aSubject,
+ aTopic,
+ aData
+ ) {
+ if (aTopic == "domwindowclosed") {
+ Services.ww.unregisterNotification(notification);
+ resolve();
+ }
+ });
+ });
+
+ info("Opening a window from the iframe.");
+ SpecialPowers.spawn(
+ ifr,
+ [{ popup: obj.popup }],
+ async function (obj) {
+ content.open(obj.popup);
+ }
+ );
+
+ info("Let's wait for the window to be closed");
+ await windowClosed;
+
+ info(
+ "First time, the 3rd party content should not have access to first party storage " +
+ "because the tracker did not have user interaction"
+ );
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage(
+ { callback: obj.blockingCallback },
+ "*"
+ );
+ });
+ }
+ );
+
+ // We wait until the user-interaction permission is added.
+ await promiseUIPerm;
+
+ // We also need to wait the user-interaction permission here.
+ promiseUIPerm = AntiTracking._waitUserInteractionPerm();
+ await AntiTracking.interactWithTracker();
+ await promiseUIPerm;
+
+ // Following test will also open an popup to interact with the page. We
+ // need to explicitly wait it. Without waiting it, it could be added after
+ // we clear up the test and interfere the next test.
+ promiseUIPerm = AntiTracking._waitUserInteractionPerm();
+
+ // We have to wait until the storage access permission is added. This has
+ // the same reason as above user-interaction permission. Note that there
+ // will be two storage access permission added due to the way how we
+ // trigger the heuristic. The first permission is added due to 'Opener'
+ // heuristic and the second one is due to 'Opener after user interaction'.
+ // The page we use to trigger the heuristic will trigger both heuristic,
+ // so we have to wait 2 permissions.
+ let promiseStorageAccessPerm = AntiTracking._waitStorageAccessPerm(2);
+
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ page: TEST_3RD_PARTY_PAGE_UI,
+ popup: TEST_POPUP_PAGE,
+ nonBlockingCallback: nonBlockingCallback.toString(),
+ iframeSandbox,
+ iframeAllow,
+ },
+ ],
+ async function (obj) {
+ let ifr = content.document.createElement("iframe");
+ let loading = new content.Promise(resolve => {
+ ifr.onload = resolve;
+ });
+ if (typeof obj.iframeSandbox == "string") {
+ ifr.setAttribute("sandbox", obj.iframeSandbox);
+ }
+ if (typeof obj.iframeAllow == "string") {
+ ifr.setAttribute("allow", obj.iframeAllow);
+ }
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.page;
+ await loading;
+
+ let windowClosed = new content.Promise(resolve => {
+ Services.ww.registerNotification(function notification(
+ aSubject,
+ aTopic,
+ aData
+ ) {
+ if (aTopic == "domwindowclosed") {
+ Services.ww.unregisterNotification(notification);
+ resolve();
+ }
+ });
+ });
+
+ info("Opening a window from the iframe.");
+ SpecialPowers.spawn(
+ ifr,
+ [{ popup: obj.popup }],
+ async function (obj) {
+ content.open(obj.popup);
+ }
+ );
+
+ info("Let's wait for the window to be closed");
+ await windowClosed;
+
+ info(
+ "The 3rd party content should now have access to first party storage."
+ );
+ await new content.Promise(resolve => {
+ content.addEventListener("message", function msg(event) {
+ if (event.data.type == "finish") {
+ content.removeEventListener("message", msg);
+ resolve();
+ return;
+ }
+
+ if (event.data.type == "ok") {
+ ok(event.data.what, event.data.msg);
+ return;
+ }
+
+ if (event.data.type == "info") {
+ info(event.data.msg);
+ return;
+ }
+
+ ok(false, "Unknown message");
+ });
+ ifr.contentWindow.postMessage(
+ { callback: obj.nonBlockingCallback },
+ "*"
+ );
+ });
+ }
+ );
+
+ // Explicitly wait the user-interaction and storage access permission
+ // before we do the cleanup.
+ await promiseUIPerm;
+ await promiseStorageAccessPerm;
+
+ info("Removing the tab");
+ BrowserTestUtils.removeTab(tab);
+
+ if (runInPrivateWindow) {
+ win.close();
+ }
+ });
+ },
+
+ async _isThirdPartyPageClassifiedAsTracker(topPage, thirdPartyDomainURI) {
+ let channel;
+ await new Promise((resolve, reject) => {
+ channel = NetUtil.newChannel({
+ uri: thirdPartyDomainURI,
+ loadingPrincipal: Services.scriptSecurityManager.createContentPrincipal(
+ thirdPartyDomainURI,
+ {}
+ ),
+ securityFlags:
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+
+ channel
+ .QueryInterface(Ci.nsIHttpChannelInternal)
+ .setTopWindowURIIfUnknown(Services.io.newURI(topPage));
+
+ function Listener() {}
+ Listener.prototype = {
+ onStartRequest(request) {},
+ onDataAvailable(request, stream, off, cnt) {
+ // Consume the data to prevent hitting the assertion.
+ NetUtil.readInputStreamToString(stream, cnt);
+ },
+ onStopRequest(request, st) {
+ let status = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
+ if (status == 200) {
+ resolve();
+ } else {
+ reject();
+ }
+ },
+ };
+ let listener = new Listener();
+ channel.asyncOpen(listener);
+ });
+
+ return !!(
+ channel.QueryInterface(Ci.nsIClassifiedChannel).classificationFlags &
+ Ci.nsIClassifiedChannel.CLASSIFIED_ANY_BASIC_TRACKING
+ );
+ },
+};