summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/protectionsUI
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/protectionsUI')
-rw-r--r--browser/base/content/test/protectionsUI/benignPage.html18
-rw-r--r--browser/base/content/test/protectionsUI/browser.toml97
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI.js738
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_3.js224
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_background_tabs.js74
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js279
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_cookie_banner.js489
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_cookies_subview.js533
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js306
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_email_trackers_subview.js179
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_fetch.js43
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js303
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_icon_state.js223
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_info_message.js90
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_milestones.js104
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js155
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js179
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js415
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_shield_visibility.js124
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js321
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_state.js403
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_state_reset.js129
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js400
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_suspicious_fingerprinters_subview.js427
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_telemetry.js89
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js144
-rw-r--r--browser/base/content/test/protectionsUI/canvas-fingerprinter.html22
-rw-r--r--browser/base/content/test/protectionsUI/containerPage.html6
-rw-r--r--browser/base/content/test/protectionsUI/cookiePage.html13
-rw-r--r--browser/base/content/test/protectionsUI/cookieServer.sjs24
-rw-r--r--browser/base/content/test/protectionsUI/cookieSetterPage.html6
-rw-r--r--browser/base/content/test/protectionsUI/emailTrackingPage.html12
-rw-r--r--browser/base/content/test/protectionsUI/embeddedPage.html6
-rw-r--r--browser/base/content/test/protectionsUI/file_protectionsUI_fetch.html17
-rw-r--r--browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js2
-rw-r--r--browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js^headers^1
-rw-r--r--browser/base/content/test/protectionsUI/font-fingerprinter.html102
-rw-r--r--browser/base/content/test/protectionsUI/head.js247
-rw-r--r--browser/base/content/test/protectionsUI/sandboxed.html12
-rw-r--r--browser/base/content/test/protectionsUI/sandboxed.html^headers^1
-rw-r--r--browser/base/content/test/protectionsUI/trackingAPI.js77
-rw-r--r--browser/base/content/test/protectionsUI/trackingPage.html13
42 files changed, 7047 insertions, 0 deletions
diff --git a/browser/base/content/test/protectionsUI/benignPage.html b/browser/base/content/test/protectionsUI/benignPage.html
new file mode 100644
index 0000000000..0be1cbc1c7
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/benignPage.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <!--TODO: We used to have an iframe here, to double-check that benign-->
+ <!--iframes may be included in pages. However, the cookie restrictions-->
+ <!--project introduced a change that declared blockable content to be-->
+ <!--found on any page that embeds iframes, rendering this unusable for-->
+ <!--our purposes. That's not ideal and we intend to restore this iframe.-->
+ <!--(See bug 1511303 for a more detailed technical explanation.)-->
+ <!--<iframe src="http://not-tracking.example.com/"></iframe>-->
+ </body>
+</html>
diff --git a/browser/base/content/test/protectionsUI/browser.toml b/browser/base/content/test/protectionsUI/browser.toml
new file mode 100644
index 0000000000..10611cbe8c
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser.toml
@@ -0,0 +1,97 @@
+[DEFAULT]
+tags = "trackingprotection"
+support-files = [
+ "head.js",
+ "benignPage.html",
+ "containerPage.html",
+ "cookiePage.html",
+ "cookieSetterPage.html",
+ "cookieServer.sjs",
+ "emailTrackingPage.html",
+ "embeddedPage.html",
+ "trackingAPI.js",
+ "trackingPage.html",
+]
+
+["browser_protectionsUI.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_3.js"]
+
+["browser_protectionsUI_background_tabs.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_categories.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_cookie_banner.js"]
+
+["browser_protectionsUI_cookies_subview.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_cryptominers.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_email_trackers_subview.js"]
+
+["browser_protectionsUI_fetch.js"]
+https_first_disabled = true
+support-files = [
+ "file_protectionsUI_fetch.html",
+ "file_protectionsUI_fetch.js",
+ "file_protectionsUI_fetch.js^headers^",
+]
+
+["browser_protectionsUI_fingerprinters.js"]
+https_first_disabled = true
+skip-if = ["a11y_checks"] # Bugs 1858041 and 1849179 for causing intermittent crashes
+
+["browser_protectionsUI_icon_state.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_info_message.js"]
+
+["browser_protectionsUI_milestones.js"]
+
+["browser_protectionsUI_open_preferences.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_pbmode_exceptions.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_report_breakage.js"]
+https_first_disabled = true
+skip-if = [
+ "debug", # Bug 1546797
+ "asan",
+]
+
+["browser_protectionsUI_shield_visibility.js"]
+support-files = [
+ "sandboxed.html",
+ "sandboxed.html^headers^",
+]
+
+["browser_protectionsUI_socialtracking.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_state.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_state_reset.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_subview_shim.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_suspicious_fingerprinters_subview.js"]
+support-files = [
+ "canvas-fingerprinter.html",
+ "font-fingerprinter.html",
+]
+
+["browser_protectionsUI_telemetry.js"]
+https_first_disabled = true
+
+["browser_protectionsUI_trackers_subview.js"]
+https_first_disabled = true
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI.js b/browser/base/content/test/protectionsUI/browser_protectionsUI.js
new file mode 100644
index 0000000000..98698e087e
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI.js
@@ -0,0 +1,738 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Basic UI tests for the protections panel */
+
+"use strict";
+
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+
+ChromeUtils.defineESModuleGetters(this, {
+ ContentBlockingAllowList:
+ "resource://gre/modules/ContentBlockingAllowList.sys.mjs",
+});
+
+const { CustomizableUITestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CustomizableUITestUtils.sys.mjs"
+);
+
+requestLongerTimeout(3);
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Set the auto hide timing to 100ms for blocking the test less.
+ ["browser.protections_panel.toast.timeout", 100],
+ // Hide protections cards so as not to trigger more async messaging
+ // when landing on the page.
+ ["browser.contentblocking.report.monitor.enabled", false],
+ ["browser.contentblocking.report.lockwise.enabled", false],
+ ["browser.contentblocking.report.proxy.enabled", false],
+ ["privacy.trackingprotection.enabled", true],
+ ],
+ });
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+ Services.telemetry.clearEvents();
+
+ registerCleanupFunction(() => {
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ Services.telemetry.clearEvents();
+ });
+});
+
+async function clickToggle(toggle) {
+ let changed = BrowserTestUtils.waitForEvent(toggle, "toggle");
+ await EventUtils.synthesizeMouseAtCenter(toggle.buttonEl, {});
+ await changed;
+}
+
+add_task(async function testToggleSwitch() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TRACKING_PAGE
+ );
+
+ await openProtectionsPanel();
+
+ await TestUtils.waitForCondition(() => {
+ return gProtectionsHandler._protectionsPopup.hasAttribute("blocking");
+ });
+
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS
+ ).parent;
+ let buttonEvents = events.filter(
+ e =>
+ e[1] == "security.ui.protectionspopup" &&
+ e[2] == "open" &&
+ e[3] == "protections_popup"
+ );
+ console.log(buttonEvents);
+ is(buttonEvents.length, 1, "recorded telemetry for opening the popup");
+
+ // Check the visibility of the "Site not working?" link.
+ ok(
+ BrowserTestUtils.isVisible(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageLink
+ ),
+ "The 'Site not working?' link should be visible."
+ );
+
+ // The 'Site Fixed?' link should be hidden.
+ ok(
+ BrowserTestUtils.isHidden(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink
+ ),
+ "The 'Site Fixed?' link should be hidden."
+ );
+
+ // Navigate through the 'Site Not Working?' flow and back to the main view,
+ // checking for telemetry on the way.
+ let siteNotWorkingView = document.getElementById(
+ "protections-popup-siteNotWorkingView"
+ );
+ let viewShown = BrowserTestUtils.waitForEvent(
+ siteNotWorkingView,
+ "ViewShown"
+ );
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageLink.click();
+ await viewShown;
+
+ checkClickTelemetry("sitenotworking_link");
+
+ let sendReportButton = document.getElementById(
+ "protections-popup-siteNotWorkingView-sendReport"
+ );
+ let sendReportView = document.getElementById(
+ "protections-popup-sendReportView"
+ );
+ viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown");
+ sendReportButton.click();
+ await viewShown;
+
+ checkClickTelemetry("send_report_link");
+
+ viewShown = BrowserTestUtils.waitForEvent(siteNotWorkingView, "ViewShown");
+ sendReportView.querySelector(".subviewbutton-back").click();
+ await viewShown;
+
+ let mainView = document.getElementById("protections-popup-mainView");
+
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ siteNotWorkingView.querySelector(".subviewbutton-back").click();
+ await viewShown;
+
+ ok(
+ gProtectionsHandler._protectionsPopupTPSwitch.hasAttribute("pressed"),
+ "TP Switch should be on"
+ );
+ let popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await clickToggle(gProtectionsHandler._protectionsPopupTPSwitch);
+
+ // The 'Site not working?' link should be hidden after clicking the TP switch.
+ ok(
+ BrowserTestUtils.isHidden(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageLink
+ ),
+ "The 'Site not working?' link should be hidden after TP switch turns to off."
+ );
+ // Same for the 'Site Fixed?' link
+ ok(
+ BrowserTestUtils.isHidden(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink
+ ),
+ "The 'Site Fixed?' link should be hidden."
+ );
+
+ await popuphiddenPromise;
+ checkClickTelemetry("etp_toggle_off");
+
+ // We need to wait toast's popup shown and popup hidden events. It won't fire
+ // the popup shown event if we open the protections panel while the toast is
+ // opening.
+ let toastShown = waitForProtectionsPanelToast();
+
+ await browserLoadedPromise;
+
+ // Wait until the ETP state confirmation toast is shown and hides itself.
+ await toastShown;
+
+ await openProtectionsPanel();
+ ok(
+ !gProtectionsHandler._protectionsPopupTPSwitch.hasAttribute("pressed"),
+ "TP Switch should be off"
+ );
+
+ // The 'Site not working?' link should be hidden if the TP is off.
+ ok(
+ BrowserTestUtils.isHidden(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageLink
+ ),
+ "The 'Site not working?' link should be hidden if TP is off."
+ );
+
+ // The 'Site Fixed?' link should be shown if TP is off.
+ ok(
+ BrowserTestUtils.isVisible(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink
+ ),
+ "The 'Site Fixed?' link should be visible."
+ );
+
+ // Check telemetry for 'Site Fixed?' link.
+ viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown");
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink.click();
+ await viewShown;
+
+ checkClickTelemetry("sitenotworking_link", "sitefixed");
+
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ sendReportView.querySelector(".subviewbutton-back").click();
+ await viewShown;
+
+ // Click the TP switch again and check the visibility of the 'Site not
+ // Working?'. It should be hidden after toggling the TP switch.
+ browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+
+ await clickToggle(gProtectionsHandler._protectionsPopupTPSwitch);
+
+ ok(
+ BrowserTestUtils.isHidden(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageLink
+ ),
+ `The 'Site not working?' link should be still hidden after toggling TP
+ switch to on from off.`
+ );
+ ok(
+ BrowserTestUtils.isHidden(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink
+ ),
+ "The 'Site Fixed?' link should be hidden."
+ );
+
+ // Wait for the protections panel to be hidden as the result of the ETP toggle
+ // on action.
+ await popuphiddenPromise;
+
+ toastShown = waitForProtectionsPanelToast();
+
+ await browserLoadedPromise;
+
+ // Wait until the ETP state confirmation toast is shown and hides itself.
+ await toastShown;
+
+ checkClickTelemetry("etp_toggle_on");
+
+ ContentBlockingAllowList.remove(tab.linkedBrowser);
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * A test for the protection settings button.
+ */
+add_task(async function testSettingsButton() {
+ // Open a tab and its protection panel.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+ await openProtectionsPanel();
+
+ let popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+ let newTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:preferences#privacy"
+ );
+ gProtectionsHandler._protectionsPopupSettingsButton.click();
+
+ // The protection popup should be hidden after clicking settings button.
+ await popuphiddenPromise;
+ // Wait until the about:preferences has been opened correctly.
+ let newTab = await newTabPromise;
+
+ ok(true, "about:preferences has been opened successfully");
+ checkClickTelemetry("settings");
+
+ BrowserTestUtils.removeTab(newTab);
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * A test for ensuring Tracking Protection label is shown correctly
+ */
+add_task(async function testTrackingProtectionLabel() {
+ // Open a tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+ await openProtectionsPanel();
+
+ let trackingProtectionLabel = document.getElementById(
+ "protections-popup-footer-protection-type-label"
+ );
+
+ is(
+ trackingProtectionLabel.textContent,
+ "Custom",
+ "The label is correctly set to Custom."
+ );
+ await closeProtectionsPanel();
+
+ Services.prefs.setStringPref("browser.contentblocking.category", "standard");
+ await openProtectionsPanel();
+
+ is(
+ trackingProtectionLabel.textContent,
+ "Standard",
+ "The label is correctly set to Standard."
+ );
+ await closeProtectionsPanel();
+
+ Services.prefs.setStringPref("browser.contentblocking.category", "strict");
+ await openProtectionsPanel();
+
+ is(
+ trackingProtectionLabel.textContent,
+ "Strict",
+ "The label is correctly set to Strict."
+ );
+
+ await closeProtectionsPanel();
+ Services.prefs.setStringPref("browser.contentblocking.category", "custom");
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * A test for the 'Show Full Report' button in the footer section.
+ */
+add_task(async function testShowFullReportButton() {
+ // Open a tab and its protection panel.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+ await openProtectionsPanel();
+
+ let popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+ let newTabPromise = waitForAboutProtectionsTab();
+ let showFullReportButton = document.getElementById(
+ "protections-popup-show-report-button"
+ );
+
+ showFullReportButton.click();
+
+ // The protection popup should be hidden after clicking the link.
+ await popuphiddenPromise;
+ // Wait until the 'about:protections' has been opened correctly.
+ let newTab = await newTabPromise;
+
+ ok(true, "about:protections has been opened successfully");
+
+ checkClickTelemetry("full_report");
+
+ BrowserTestUtils.removeTab(newTab);
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * A test for ensuring the mini panel is working correctly
+ */
+add_task(async function testMiniPanel() {
+ // Open a tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+
+ // Open the mini panel.
+ await openProtectionsPanel(true);
+ let popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+
+ // Check that only the header is displayed.
+ let mainView = document.getElementById("protections-popup-mainView");
+ for (let item of mainView.childNodes) {
+ if (item.id !== "protections-popup-mainView-panel-header-section") {
+ ok(
+ !BrowserTestUtils.isVisible(item),
+ `The section '${item.id}' is hidden in the toast.`
+ );
+ } else {
+ ok(
+ BrowserTestUtils.isVisible(item),
+ "The panel header is displayed as the content of the toast."
+ );
+ }
+ }
+
+ // Wait until the auto hide is happening.
+ await popuphiddenPromise;
+
+ ok(true, "The mini panel hides automatically.");
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * A test for the toggle switch flow
+ */
+add_task(async function testToggleSwitchFlow() {
+ // Open a tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+ await openProtectionsPanel();
+
+ let popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popupshown"
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Click the TP switch, from On -> Off.
+ await clickToggle(gProtectionsHandler._protectionsPopupTPSwitch);
+
+ // Check that the icon state has been changed.
+ ok(
+ gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ "The tracking protection icon state has been changed to disabled."
+ );
+
+ // The panel should be closed and the mini panel will show up after refresh.
+ await popuphiddenPromise;
+ await browserLoadedPromise;
+ await popupShownPromise;
+
+ ok(
+ gProtectionsHandler._protectionsPopup.hasAttribute("toast"),
+ "The protections popup should have the 'toast' attribute."
+ );
+
+ // Click on the mini panel and making sure the protection popup shows up.
+ popupShownPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popupshown"
+ );
+ popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+ // We intentionally turn off a11y_checks, because the following click
+ // is targeting static toast message that's not meant to be interactive and
+ // is not expected to be accessible:
+ AccessibilityUtils.setEnv({
+ mustHaveAccessibleRule: false,
+ });
+ document.getElementById("protections-popup-mainView-panel-header").click();
+ AccessibilityUtils.resetEnv();
+ await popuphiddenPromise;
+ await popupShownPromise;
+
+ ok(
+ !gProtectionsHandler._protectionsPopup.hasAttribute("toast"),
+ "The 'toast' attribute should be cleared on the protections popup."
+ );
+
+ // Click the TP switch again, from Off -> On.
+ popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+ popupShownPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popupshown"
+ );
+ browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await clickToggle(gProtectionsHandler._protectionsPopupTPSwitch);
+
+ // Check that the icon state has been changed.
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ "The tracking protection icon state has been changed to enabled."
+ );
+
+ // Protections popup hidden -> Page refresh -> Mini panel shows up.
+ await popuphiddenPromise;
+ popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+ await browserLoadedPromise;
+ await popupShownPromise;
+
+ ok(
+ gProtectionsHandler._protectionsPopup.hasAttribute("toast"),
+ "The protections popup should have the 'toast' attribute."
+ );
+
+ // Wait until the auto hide is happening.
+ await popuphiddenPromise;
+
+ // Clean up the TP state.
+ ContentBlockingAllowList.remove(tab.linkedBrowser);
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * A test for ensuring the tracking protection icon will show a correct
+ * icon according to the TP enabling state.
+ */
+add_task(async function testTrackingProtectionIcon() {
+ // Open a tab and its protection panel.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+
+ let TPIcon = document.getElementById("tracking-protection-icon");
+ // Check the icon url. It will show a shield icon if TP is enabled.
+ is(
+ gBrowser.ownerGlobal
+ .getComputedStyle(TPIcon)
+ .getPropertyValue("list-style-image"),
+ `url("chrome://browser/skin/tracking-protection.svg")`,
+ "The tracking protection icon shows a shield icon."
+ );
+
+ // Disable the tracking protection.
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ "https://example.com/"
+ );
+ gProtectionsHandler.disableForCurrentPage();
+ await browserLoadedPromise;
+
+ // Check that the tracking protection icon should show a strike-through shield
+ // icon after page is reloaded.
+ is(
+ gBrowser.ownerGlobal
+ .getComputedStyle(TPIcon)
+ .getPropertyValue("list-style-image"),
+ `url("chrome://browser/skin/tracking-protection-disabled.svg")`,
+ "The tracking protection icon shows a strike through shield icon."
+ );
+
+ // Clean up the TP state.
+ ContentBlockingAllowList.remove(tab.linkedBrowser);
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * A test for ensuring the number of blocked trackers is displayed properly.
+ */
+add_task(async function testNumberOfBlockedTrackers() {
+ // First, clear the tracking database.
+ await TrackingDBService.clearAll();
+
+ // Open a tab.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+ await openProtectionsPanel();
+
+ let trackerCounterBox = document.getElementById(
+ "protections-popup-trackers-blocked-counter-box"
+ );
+ let trackerCounterDesc = document.getElementById(
+ "protections-popup-trackers-blocked-counter-description"
+ );
+
+ // Check that whether the counter is not shown if the number of blocked
+ // trackers is zero.
+ ok(
+ BrowserTestUtils.isHidden(trackerCounterBox),
+ "The blocked tracker counter is hidden if there is no blocked tracker."
+ );
+
+ await closeProtectionsPanel();
+
+ // Add one tracker into the database and check that the tracker counter is
+ // properly shown.
+ await addTrackerDataIntoDB(1);
+
+ // A promise for waiting the `showing` attributes has been set to the counter
+ // box. This means the database access is finished.
+ let counterShownPromise = BrowserTestUtils.waitForAttribute(
+ "showing",
+ trackerCounterBox
+ );
+
+ await openProtectionsPanel();
+ await counterShownPromise;
+
+ // Check that the number of blocked trackers is shown.
+ ok(
+ BrowserTestUtils.isVisible(trackerCounterBox),
+ "The blocked tracker counter is shown if there is one blocked tracker."
+ );
+ is(
+ trackerCounterDesc.textContent,
+ "1 Blocked",
+ "The blocked tracker counter is correct."
+ );
+
+ await closeProtectionsPanel();
+ await TrackingDBService.clearAll();
+
+ // Add trackers into the database and check that the tracker counter is
+ // properly shown as well as whether the pre-fetch is triggered by the
+ // keyboard navigation.
+ await addTrackerDataIntoDB(10);
+
+ // We cannot wait for the change of "showing" attribute here since this
+ // attribute will only be set if the previous counter is zero. Instead, we
+ // wait for the change of the text content of the counter.
+ let updateCounterPromise = new Promise(resolve => {
+ let mut = new MutationObserver(mutations => {
+ resolve();
+ mut.disconnect();
+ });
+
+ mut.observe(trackerCounterDesc, {
+ childList: true,
+ });
+ });
+
+ await openProtectionsPanelWithKeyNav();
+ await updateCounterPromise;
+
+ // Check that the number of blocked trackers is shown.
+ ok(
+ BrowserTestUtils.isVisible(trackerCounterBox),
+ "The blocked tracker counter is shown if there are more than one blocked tracker."
+ );
+ is(
+ trackerCounterDesc.textContent,
+ "10 Blocked",
+ "The blocked tracker counter is correct."
+ );
+
+ await closeProtectionsPanel();
+ await TrackingDBService.clearAll();
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testSubViewTelemetry() {
+ let items = [
+ ["protections-popup-category-trackers", "trackers"],
+ ["protections-popup-category-socialblock", "social"],
+ ["protections-popup-category-cookies", "cookies"],
+ ["protections-popup-category-cryptominers", "cryptominers"],
+ ["protections-popup-category-fingerprinters", "fingerprinters"],
+ ].map(item => [document.getElementById(item[0]), item[1]]);
+
+ for (let [item, telemetryId] of items) {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await BrowserTestUtils.withNewTab("http://www.example.com", async () => {
+ await openProtectionsPanel();
+
+ item.classList.remove("notFound"); // Force visible for test
+ gProtectionsHandler._categoryItemOrderInvalidated = true;
+ gProtectionsHandler.reorderCategoryItems();
+
+ let viewShownEvent = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopupMultiView,
+ "ViewShown"
+ );
+ item.click();
+ let panelView = (await viewShownEvent).originalTarget;
+ checkClickTelemetry(telemetryId);
+ let prefsTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:preferences#privacy"
+ );
+ panelView.querySelector(".panel-subview-footer-button").click();
+ let prefsTab = await prefsTabPromise;
+ BrowserTestUtils.removeTab(prefsTab);
+ checkClickTelemetry("subview_settings", telemetryId);
+ });
+ }
+});
+
+/**
+ * A test to make sure the TP state won't apply incorrectly if we quickly switch
+ * tab after toggling the TP switch.
+ */
+add_task(async function testQuickSwitchTabAfterTogglingTPSwitch() {
+ const FIRST_TEST_SITE = "https://example.com/";
+ const SECOND_TEST_SITE = "https://example.org/";
+
+ // First, clear the tracking database.
+ await TrackingDBService.clearAll();
+
+ // Open two tabs with different origins.
+ let tabOne = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ FIRST_TEST_SITE
+ );
+ let tabTwo = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ SECOND_TEST_SITE
+ );
+
+ // Open the protection panel of the second tab.
+ await openProtectionsPanel();
+
+ // A promise to check the reload happens on the second tab.
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tabTwo.linkedBrowser,
+ false,
+ SECOND_TEST_SITE
+ );
+
+ // Toggle the TP state and switch tab without waiting it to be finished.
+ await clickToggle(gProtectionsHandler._protectionsPopupTPSwitch);
+ gBrowser.selectedTab = tabOne;
+
+ // Wait for the second tab to be reloaded.
+ await browserLoadedPromise;
+
+ // Check that the first tab is still with ETP enabled.
+ ok(
+ !ContentBlockingAllowList.includes(gBrowser.selectedBrowser),
+ "The ETP state of the first tab is still enabled."
+ );
+
+ // Check the ETP is disabled on the second origin.
+ ok(
+ ContentBlockingAllowList.includes(tabTwo.linkedBrowser),
+ "The ETP state of the second tab has been changed to disabled."
+ );
+
+ // Clean up the state of the allow list for the second tab.
+ ContentBlockingAllowList.remove(tabTwo.linkedBrowser);
+
+ BrowserTestUtils.removeTab(tabOne);
+ BrowserTestUtils.removeTab(tabTwo);
+
+ // Finally, clear the tracking database.
+ await TrackingDBService.clearAll();
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_3.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_3.js
new file mode 100644
index 0000000000..353c6a099b
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_3.js
@@ -0,0 +1,224 @@
+/*
+ * Test that the Tracking Protection is correctly enabled / disabled
+ * in both normal and private windows given all possible states of the prefs:
+ * privacy.trackingprotection.enabled
+ * privacy.trackingprotection.pbmode.enabled
+ * privacy.trackingprotection.emailtracking.enabled
+ * privacy.trackingprotection.emailtracking.pbmode.enabled
+ * See also Bug 1178985, Bug 1819662.
+ */
+
+const PREF = "privacy.trackingprotection.enabled";
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const EMAIL_PREF = "privacy.trackingprotection.emailtracking.enabled";
+const EMAIL_PB_PREF = "privacy.trackingprotection.emailtracking.pbmode.enabled";
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(PB_PREF);
+ Services.prefs.clearUserPref(EMAIL_PREF);
+ Services.prefs.clearUserPref(EMAIL_PB_PREF);
+});
+
+add_task(async function testNormalBrowsing() {
+ let { TrackingProtection } =
+ gBrowser.ownerGlobal.gProtectionsHandler.blockers;
+ ok(
+ TrackingProtection,
+ "Normal window gProtectionsHandler should have TrackingProtection blocker."
+ );
+
+ Services.prefs.setBoolPref(PREF, true);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ Services.prefs.setBoolPref(EMAIL_PREF, false);
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=false,EmailEnabled=false,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=true,EmailEnabled=false,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=true,EmailEnabled=true,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=true,EmailEnabled=true,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=true,EmailEnabled=false,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=false,EmailEnabled=false,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, true);
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=false,EmailEnabled=true,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=false,EmailEnabled=true,EmailPB=true)"
+ );
+
+ Services.prefs.setBoolPref(PREF, false);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ Services.prefs.setBoolPref(EMAIL_PREF, false);
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, false);
+ ok(
+ !TrackingProtection.enabled,
+ "TP is disabled (ENABLED=false,PB=false,EmailEnabled=false,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(
+ !TrackingProtection.enabled,
+ "TP is disabled (ENABLED=false,PB=true,EmailEnabled=false,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=true,EmailEnabled=true,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=true,EmailEnabled=true,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, false);
+ ok(
+ !TrackingProtection.enabled,
+ "TP is disabled (ENABLED=false,PB=true,EmailEnabled=false,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(
+ !TrackingProtection.enabled,
+ "TP is disabled (ENABLED=false,PB=false,EmailEnabled=false,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, true);
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=false,EmailEnabled=true,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=false,EmailEnabled=true,EmailPB=true)"
+ );
+});
+
+add_task(async function testPrivateBrowsing() {
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let { TrackingProtection } =
+ privateWin.gBrowser.ownerGlobal.gProtectionsHandler.blockers;
+ ok(
+ TrackingProtection,
+ "Private window gProtectionsHandler should have TrackingProtection blocker."
+ );
+
+ Services.prefs.setBoolPref(PREF, true);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ Services.prefs.setBoolPref(EMAIL_PREF, false);
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=false,EmailEnabled=false,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=true,EmailEnabled=false,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=true,EmailEnabled=true,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=true,EmailEnabled=true,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=true,EmailEnabled=false,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=false,EmailEnabled=false,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, true);
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=false,EmailEnabled=true,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=true,PB=false,EmailEnabled=true,EmailPB=true)"
+ );
+
+ Services.prefs.setBoolPref(PREF, false);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ Services.prefs.setBoolPref(EMAIL_PREF, false);
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, false);
+ ok(
+ !TrackingProtection.enabled,
+ "TP is disabled (ENABLED=false,PB=false,EmailEnabled=false,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=true,EmailEnabled=false,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=true,EmailEnabled=true,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=true,EmailEnabled=true,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=true,EmailEnabled=false,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=false,EmailEnabled=false,EmailPB=true)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PREF, true);
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, false);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=false,EmailEnabled=true,EmailPB=false)"
+ );
+ Services.prefs.setBoolPref(EMAIL_PB_PREF, true);
+ ok(
+ TrackingProtection.enabled,
+ "TP is enabled (ENABLED=false,PB=false,EmailEnabled=true,EmailPB=true)"
+ );
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_background_tabs.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_background_tabs.js
new file mode 100644
index 0000000000..24a83c9588
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_background_tabs.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+const BENIGN_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html";
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+
+add_task(async function testBackgroundTabs() {
+ info(
+ "Testing receiving and storing content blocking events in non-selected tabs."
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [[TP_PREF, true]],
+ });
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ registerCleanupFunction(() => {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, BENIGN_PAGE);
+
+ let backgroundTab = BrowserTestUtils.addTab(gBrowser);
+ let browser = backgroundTab.linkedBrowser;
+ let hasContentBlockingEvent = TestUtils.waitForCondition(
+ () => browser.getContentBlockingEvents() != 0
+ );
+ await promiseTabLoadEvent(backgroundTab, TRACKING_PAGE);
+ await hasContentBlockingEvent;
+
+ is(
+ browser.getContentBlockingEvents(),
+ Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT,
+ "Background tab has the correct content blocking event."
+ );
+
+ is(
+ tab.linkedBrowser.getContentBlockingEvents(),
+ 0,
+ "Foreground tab has the correct content blocking event."
+ );
+
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("active"),
+ "shield is not active"
+ );
+
+ await BrowserTestUtils.switchTab(gBrowser, backgroundTab);
+
+ is(
+ browser.getContentBlockingEvents(),
+ Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT,
+ "Background tab still has the correct content blocking event."
+ );
+
+ is(
+ tab.linkedBrowser.getContentBlockingEvents(),
+ 0,
+ "Foreground tab still has the correct content blocking event."
+ );
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active");
+
+ gBrowser.removeTab(backgroundTab);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js
new file mode 100644
index 0000000000..6fe3db3a70
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js
@@ -0,0 +1,279 @@
+const CAT_PREF = "browser.contentblocking.category";
+const TP_PREF = "privacy.trackingprotection.enabled";
+const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const TPC_PREF = "network.cookie.cookieBehavior";
+const CM_PREF = "privacy.trackingprotection.cryptomining.enabled";
+const FP_PREF = "privacy.trackingprotection.fingerprinting.enabled";
+const ST_PREF = "privacy.trackingprotection.socialtracking.enabled";
+const STC_PREF = "privacy.socialtracking.block_cookies.enabled";
+const FPP_PREF = "privacy.fingerprintingProtection";
+
+const { CustomizableUITestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CustomizableUITestUtils.sys.mjs"
+);
+
+const l10n = new Localization([
+ "browser/siteProtections.ftl",
+ "branding/brand.ftl",
+]);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(CAT_PREF);
+ Services.prefs.clearUserPref(TP_PREF);
+ Services.prefs.clearUserPref(TP_PB_PREF);
+ Services.prefs.clearUserPref(TPC_PREF);
+ Services.prefs.clearUserPref(CM_PREF);
+ Services.prefs.clearUserPref(FP_PREF);
+ Services.prefs.clearUserPref(ST_PREF);
+ Services.prefs.clearUserPref(STC_PREF);
+});
+
+add_task(async function testCookieCategoryLabel() {
+ await BrowserTestUtils.withNewTab(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.example.com",
+ async function () {
+ // Ensure the category nodes exist.
+ await openProtectionsPanel();
+ await closeProtectionsPanel();
+ let categoryItem = document.getElementById(
+ "protections-popup-category-cookies"
+ );
+ let categoryLabel = document.getElementById(
+ "protections-popup-cookies-category-label"
+ );
+
+ Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
+ await TestUtils.waitForCondition(
+ () => !categoryItem.classList.contains("blocked"),
+ "The category label has updated correctly"
+ );
+ ok(!categoryItem.classList.contains("blocked"));
+
+ Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT);
+ await TestUtils.waitForCondition(
+ () => categoryItem.classList.contains("blocked"),
+ "The category label has updated correctly"
+ );
+ ok(categoryItem.classList.contains("blocked"));
+ const blockAllMsg = await l10n.formatValue(
+ "content-blocking-cookies-blocking-all-label"
+ );
+ await TestUtils.waitForCondition(
+ () => categoryLabel.textContent == blockAllMsg,
+ "The category label has updated correctly"
+ );
+
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN
+ );
+ await TestUtils.waitForCondition(
+ () => categoryItem.classList.contains("blocked"),
+ "The category label has updated correctly"
+ );
+ ok(categoryItem.classList.contains("blocked"));
+ const block3rdMsg = await l10n.formatValue(
+ "content-blocking-cookies-blocking-third-party-label"
+ );
+ await TestUtils.waitForCondition(
+ () => categoryLabel.textContent == block3rdMsg,
+ "The category label has updated correctly"
+ );
+
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ await TestUtils.waitForCondition(
+ () => categoryItem.classList.contains("blocked"),
+ "The category label has updated correctly"
+ );
+ ok(categoryItem.classList.contains("blocked"));
+ const blockTrackersMsg = await l10n.formatValue(
+ "content-blocking-cookies-blocking-trackers-label"
+ );
+ await TestUtils.waitForCondition(
+ () => categoryLabel.textContent == blockTrackersMsg,
+ "The category label has updated correctly"
+ );
+
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
+ );
+ await TestUtils.waitForCondition(
+ () => categoryItem.classList.contains("blocked"),
+ "The category label has updated correctly"
+ );
+ ok(categoryItem.classList.contains("blocked"));
+ await TestUtils.waitForCondition(
+ () => categoryLabel.textContent == blockTrackersMsg,
+ "The category label has updated correctly"
+ );
+
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN
+ );
+ await TestUtils.waitForCondition(
+ () => !categoryItem.classList.contains("blocked"),
+ "The category label has updated correctly"
+ );
+ ok(!categoryItem.classList.contains("blocked"));
+ }
+ );
+});
+
+let categoryEnabledPrefs = [TP_PREF, STC_PREF, TPC_PREF, CM_PREF, FP_PREF];
+
+let detectedStateFlags = [
+ Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT,
+ Ci.nsIWebProgressListener.STATE_BLOCKED_SOCIALTRACKING_CONTENT,
+ Ci.nsIWebProgressListener.STATE_COOKIES_LOADED,
+ Ci.nsIWebProgressListener.STATE_BLOCKED_CRYPTOMINING_CONTENT,
+ Ci.nsIWebProgressListener.STATE_BLOCKED_FINGERPRINTING_CONTENT,
+];
+
+async function waitForClass(item, className, shouldBePresent = true) {
+ await TestUtils.waitForCondition(() => {
+ return item.classList.contains(className) == shouldBePresent;
+ }, `Target class ${className} should be ${shouldBePresent ? "present" : "not present"} on item ${item.id}`);
+
+ Assert.equal(
+ item.classList.contains(className),
+ shouldBePresent,
+ `item.classList.contains(${className}) is ${shouldBePresent} for ${item.id}`
+ );
+}
+
+add_task(async function testCategorySections() {
+ Services.prefs.setBoolPref(ST_PREF, true);
+ Services.prefs.setBoolPref(FPP_PREF, false);
+
+ for (let pref of categoryEnabledPrefs) {
+ if (pref == TPC_PREF) {
+ Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
+ } else {
+ Services.prefs.setBoolPref(pref, false);
+ }
+ }
+
+ await BrowserTestUtils.withNewTab(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.example.com",
+ async function () {
+ // Ensure the category nodes exist.
+ await openProtectionsPanel();
+ await closeProtectionsPanel();
+
+ let categoryItems = [
+ "protections-popup-category-trackers",
+ "protections-popup-category-socialblock",
+ "protections-popup-category-cookies",
+ "protections-popup-category-cryptominers",
+ "protections-popup-category-fingerprinters",
+ ].map(id => document.getElementById(id));
+
+ for (let item of categoryItems) {
+ await waitForClass(item, "notFound");
+ await waitForClass(item, "blocked", false);
+ }
+
+ // For every item, we enable the category and spoof a content blocking event,
+ // and check that .notFound goes away and .blocked is set. Then we disable the
+ // category and checks that .blocked goes away, and .notFound is still unset.
+ let contentBlockingState = 0;
+ for (let i = 0; i < categoryItems.length; i++) {
+ let itemToTest = categoryItems[i];
+ let enabledPref = categoryEnabledPrefs[i];
+ contentBlockingState |= detectedStateFlags[i];
+ if (enabledPref == TPC_PREF) {
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT
+ );
+ } else {
+ Services.prefs.setBoolPref(enabledPref, true);
+ }
+ gProtectionsHandler.onContentBlockingEvent(contentBlockingState);
+ gProtectionsHandler.updatePanelForBlockingEvent(contentBlockingState);
+ await waitForClass(itemToTest, "notFound", false);
+ await waitForClass(itemToTest, "blocked", true);
+ if (enabledPref == TPC_PREF) {
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_ACCEPT
+ );
+ } else {
+ Services.prefs.setBoolPref(enabledPref, false);
+ }
+ await waitForClass(itemToTest, "notFound", false);
+ await waitForClass(itemToTest, "blocked", false);
+ }
+ }
+ );
+});
+
+/**
+ * Check that when we open the popup in a new window, the initial state is correct
+ * wrt the pref.
+ */
+add_task(async function testCategorySectionInitial() {
+ requestLongerTimeout(3);
+
+ let categoryItems = [
+ "protections-popup-category-trackers",
+ "protections-popup-category-socialblock",
+ "protections-popup-category-cookies",
+ "protections-popup-category-cryptominers",
+ "protections-popup-category-fingerprinters",
+ ];
+ for (let i = 0; i < categoryItems.length; i++) {
+ for (let shouldBlock of [true, false]) {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ // Open non-about: page so our protections are active.
+ await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ "https://example.com/"
+ );
+ let enabledPref = categoryEnabledPrefs[i];
+ let contentBlockingState = detectedStateFlags[i];
+ if (enabledPref == TPC_PREF) {
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ shouldBlock
+ ? Ci.nsICookieService.BEHAVIOR_REJECT
+ : Ci.nsICookieService.BEHAVIOR_ACCEPT
+ );
+ } else {
+ Services.prefs.setBoolPref(enabledPref, shouldBlock);
+ }
+ win.gProtectionsHandler.onContentBlockingEvent(contentBlockingState);
+ await openProtectionsPanel(false, win);
+ let categoryItem = win.document.getElementById(categoryItems[i]);
+ let expectedFound = true;
+ // Accepting cookies outright won't mark this as found.
+ if (i == 2 && !shouldBlock) {
+ // See bug 1653019
+ expectedFound = false;
+ }
+ is(
+ categoryItem.classList.contains("notFound"),
+ !expectedFound,
+ `Should have found ${categoryItems[i]} when it was ${
+ shouldBlock ? "blocked" : "allowed"
+ }`
+ );
+ is(
+ categoryItem.classList.contains("blocked"),
+ shouldBlock,
+ `Should ${shouldBlock ? "have blocked" : "not have blocked"} ${
+ categoryItems[i]
+ }`
+ );
+ await closeProtectionsPanel(win);
+ await BrowserTestUtils.closeWindow(win);
+ }
+ }
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_cookie_banner.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_cookie_banner.js
new file mode 100644
index 0000000000..688bc9d7c8
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_cookie_banner.js
@@ -0,0 +1,489 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests the cookie banner handling section in the protections panel.
+ */
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+const { MODE_DISABLED, MODE_REJECT, MODE_REJECT_OR_ACCEPT, MODE_UNSET } =
+ Ci.nsICookieBannerService;
+
+const exampleRules = JSON.stringify([
+ {
+ id: "4b18afb0-76db-4f9e-a818-ed9a783fae6a",
+ cookies: {},
+ click: {
+ optIn: "#foo",
+ presence: "#bar",
+ },
+ domains: ["example.com"],
+ },
+]);
+
+/**
+ * Determines whether the cookie banner section in the protections panel should
+ * be visible with the given configuration.
+ * @param {*} options - Configuration to test.
+ * @param {Number} options.featureMode - nsICookieBannerService::Modes value for
+ * normal browsing.
+ * @param {Number} options.featureModePBM - nsICookieBannerService::Modes value
+ * for private browsing.
+ * @param {boolean} options.visibilityPref - State of the cookie banner UI
+ * visibility pref.
+ * @param {boolean} options.testPBM - Whether the window is in private browsing
+ * mode (true) or not (false).
+ * @returns {boolean} Whether the section should be visible for the given
+ * config.
+ */
+function cookieBannerSectionIsVisible({
+ featureMode,
+ featureModePBM,
+ detectOnly,
+ visibilityPref,
+ testPBM,
+}) {
+ if (!visibilityPref) {
+ return false;
+ }
+ if (detectOnly) {
+ return false;
+ }
+
+ return (
+ (testPBM && featureModePBM != MODE_DISABLED) ||
+ (!testPBM && featureMode != MODE_DISABLED)
+ );
+}
+
+/**
+ * Runs a visibility test of the cookie banner section in the protections panel.
+ * @param {*} options - Test options.
+ * @param {Window} options.win - Browser window to use for testing. It's
+ * browsing mode should match the testPBM variable.
+ * @param {Number} options.featureMode - nsICookieBannerService::Modes value for
+ * normal browsing.
+ * @param {Number} options.featureModePBM - nsICookieBannerService::Modes value
+ * for private browsing.
+ * @param {boolean} options.visibilityPref - State of the cookie banner UI
+ * visibility pref.
+ * @param {boolean} options.testPBM - Whether the window is in private browsing
+ * mode (true) or not (false).
+ * @returns {Promise} Resolves once the test is complete.
+ */
+async function testSectionVisibility({
+ win,
+ featureMode,
+ featureModePBM,
+ visibilityPref,
+ testPBM,
+}) {
+ info(
+ "testSectionVisibility " +
+ JSON.stringify({ featureMode, featureModePBM, visibilityPref, testPBM })
+ );
+ // initialize the pref environment
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", featureMode],
+ ["cookiebanners.service.mode.privateBrowsing", featureModePBM],
+ ["cookiebanners.ui.desktop.enabled", visibilityPref],
+ ],
+ });
+
+ // Open a tab with example.com so the protections panel can be opened.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url: "https://example.com" },
+ async () => {
+ await openProtectionsPanel(null, win);
+
+ // Get panel elements to test
+ let el = {
+ section: win.document.getElementById(
+ "protections-popup-cookie-banner-section"
+ ),
+ sectionSeparator: win.document.getElementById(
+ "protections-popup-cookie-banner-section-separator"
+ ),
+ switch: win.document.getElementById(
+ "protections-popup-cookie-banner-switch"
+ ),
+ };
+
+ let expectVisible = cookieBannerSectionIsVisible({
+ featureMode,
+ featureModePBM,
+ visibilityPref,
+ testPBM,
+ });
+ is(
+ BrowserTestUtils.isVisible(el.section),
+ expectVisible,
+ `Cookie banner section should be ${
+ expectVisible ? "visible" : "not visible"
+ }.`
+ );
+ is(
+ BrowserTestUtils.isVisible(el.sectionSeparator),
+ expectVisible,
+ `Cookie banner section separator should be ${
+ expectVisible ? "visible" : "not visible"
+ }.`
+ );
+ is(
+ BrowserTestUtils.isVisible(el.switch),
+ expectVisible,
+ `Cookie banner switch should be ${
+ expectVisible ? "visible" : "not visible"
+ }.`
+ );
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+}
+
+/**
+ * Tests cookie banner section visibility state in different configurations.
+ */
+add_task(async function test_section_visibility() {
+ // Test all combinations of cookie banner service modes and normal and
+ // private browsing.
+
+ for (let testPBM of [false, true]) {
+ let win = window;
+ // Open a new private window to test the panel in for testing PBM, otherwise
+ // reuse the existing window.
+ if (testPBM) {
+ win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ win.focus();
+ }
+
+ for (let featureMode of [
+ MODE_DISABLED,
+ MODE_REJECT,
+ MODE_REJECT_OR_ACCEPT,
+ ]) {
+ for (let featureModePBM of [
+ MODE_DISABLED,
+ MODE_REJECT,
+ MODE_REJECT_OR_ACCEPT,
+ ]) {
+ for (let detectOnly of [false, true]) {
+ // Testing detect only mode for normal browsing is sufficient.
+ if (detectOnly && featureModePBM != MODE_DISABLED) {
+ continue;
+ }
+ await testSectionVisibility({
+ win,
+ featureMode,
+ featureModePBM,
+ detectOnly,
+ testPBM,
+ visibilityPref: true,
+ });
+ }
+ }
+ }
+
+ if (testPBM) {
+ await BrowserTestUtils.closeWindow(win);
+ }
+ }
+});
+
+/**
+ * Tests that the cookie banner section is only visible if enabled by UI pref.
+ */
+add_task(async function test_section_visibility_pref() {
+ for (let visibilityPref of [false, true]) {
+ await testSectionVisibility({
+ win: window,
+ featureMode: MODE_REJECT,
+ featureModePBM: MODE_DISABLED,
+ testPBM: false,
+ visibilityPref,
+ });
+ }
+});
+
+/**
+ * Test the state of the per-site exception switch in the cookie banner section
+ * and whether a matching per-site exception is set.
+ * @param {*} options
+ * @param {Window} options.win - Chrome window to test exception for (selected
+ * tab).
+ * @param {boolean} options.isPBM - Whether the given window is in private
+ * browsing mode.
+ * @param {string} options.expectedSwitchState - Whether the switch is expected to be
+ * "on" (CBH enabled), "off" (user added exception), or "unsupported" (no rules for site).
+ */
+function assertSwitchAndPrefState({ win, isPBM, expectedSwitchState }) {
+ let el = {
+ section: win.document.getElementById(
+ "protections-popup-cookie-banner-section"
+ ),
+ switch: win.document.getElementById(
+ "protections-popup-cookie-banner-switch"
+ ),
+ labelON: win.document.querySelector(
+ "#protections-popup-cookie-banner-detected"
+ ),
+ labelOFF: win.document.querySelector(
+ "#protections-popup-cookie-banner-site-disabled"
+ ),
+ labelUNDETECTED: win.document.querySelector(
+ "#protections-popup-cookie-banner-undetected"
+ ),
+ };
+
+ let currentURI = win.gBrowser.currentURI;
+ let pref = Services.cookieBanners.getDomainPref(currentURI, isPBM);
+ if (expectedSwitchState == "on") {
+ Assert.equal(
+ el.section.dataset.state,
+ "detected",
+ "CBH switch is set to ON"
+ );
+
+ ok(BrowserTestUtils.isVisible(el.labelON), "ON label should be visible");
+ ok(
+ !BrowserTestUtils.isVisible(el.labelOFF),
+ "OFF label should not be visible"
+ );
+ ok(
+ !BrowserTestUtils.isVisible(el.labelUNDETECTED),
+ "UNDETECTED label should not be visible"
+ );
+
+ is(
+ pref,
+ MODE_UNSET,
+ `There should be no per-site exception for ${currentURI.spec}.`
+ );
+ } else if (expectedSwitchState === "off") {
+ Assert.equal(
+ el.section.dataset.state,
+ "site-disabled",
+ "CBH switch is set to OFF"
+ );
+
+ ok(
+ !BrowserTestUtils.isVisible(el.labelON),
+ "ON label should not be visible"
+ );
+ ok(BrowserTestUtils.isVisible(el.labelOFF), "OFF label should be visible");
+ ok(
+ !BrowserTestUtils.isVisible(el.labelUNDETECTED),
+ "UNDETECTED label should not be visible"
+ );
+
+ is(
+ pref,
+ MODE_DISABLED,
+ `There should be a per-site exception for ${currentURI.spec}.`
+ );
+ } else {
+ Assert.equal(
+ el.section.dataset.state,
+ "undetected",
+ "CBH not supported for site"
+ );
+
+ ok(
+ !BrowserTestUtils.isVisible(el.labelON),
+ "ON label should not be visible"
+ );
+ ok(
+ !BrowserTestUtils.isVisible(el.labelOFF),
+ "OFF label should not be visible"
+ );
+ ok(
+ BrowserTestUtils.isVisible(el.labelUNDETECTED),
+ "UNDETECTED label should be visible"
+ );
+ }
+}
+
+/**
+ * Test the telemetry associated with the cookie banner toggle. To be called
+ * after interacting with the toggle.
+ * @param {*} options
+ * @param {boolean|null} - Expected telemetry state matching the button state.
+ * button on = true = cookieb_toggle_on event. Pass null to expect no event
+ * recorded.
+ */
+function assertTelemetryState({ expectEnabled = null } = {}) {
+ info("Test telemetry state.");
+
+ let events = [];
+ const CATEGORY = "security.ui.protectionspopup";
+ const METHOD = "click";
+
+ if (expectEnabled != null) {
+ events.push({
+ category: CATEGORY,
+ method: METHOD,
+ object: expectEnabled ? "cookieb_toggle_on" : "cookieb_toggle_off",
+ });
+ }
+
+ // Assert event state and clear event list.
+ TelemetryTestUtils.assertEvents(events, {
+ category: CATEGORY,
+ method: METHOD,
+ });
+}
+
+/**
+ * Test the cookie banner enable / disable by clicking the switch, then
+ * clicking the on/off button in the cookie banner subview. Assumes the
+ * protections panel is already open.
+ *
+ * @param {boolean} enable - Whether we want to enable or disable.
+ * @param {Window} win - Current chrome window under test.
+ */
+async function toggleCookieBannerHandling(enable, win) {
+ let switchEl = win.document.getElementById(
+ "protections-popup-cookie-banner-switch"
+ );
+ let enableButton = win.document.getElementById(
+ "protections-popup-cookieBannerView-enable-button"
+ );
+ let disableButton = win.document.getElementById(
+ "protections-popup-cookieBannerView-disable-button"
+ );
+ let subView = win.document.getElementById(
+ "protections-popup-cookieBannerView"
+ );
+
+ let subViewShownPromise = BrowserTestUtils.waitForEvent(subView, "ViewShown");
+ switchEl.click();
+ await subViewShownPromise;
+
+ if (enable) {
+ ok(BrowserTestUtils.isVisible(enableButton), "Enable button is visible");
+ enableButton.click();
+ } else {
+ ok(BrowserTestUtils.isVisible(disableButton), "Disable button is visible");
+ disableButton.click();
+ }
+}
+
+function waitForProtectionsPopupHide(win = window) {
+ return BrowserTestUtils.waitForEvent(
+ win.document.getElementById("protections-popup"),
+ "popuphidden"
+ );
+}
+
+/**
+ * Tests the cookie banner section per-site preference toggle.
+ */
+add_task(async function test_section_toggle() {
+ requestLongerTimeout(3);
+
+ // initialize the pref environment
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["cookiebanners.service.mode", MODE_REJECT_OR_ACCEPT],
+ ["cookiebanners.service.mode.privateBrowsing", MODE_REJECT_OR_ACCEPT],
+ ["cookiebanners.ui.desktop.enabled", true],
+ ["cookiebanners.listService.testRules", exampleRules],
+ ["cookiebanners.listService.testSkipRemoteSettings", true],
+ ],
+ });
+
+ Services.cookieBanners.resetRules(false);
+ await BrowserTestUtils.waitForCondition(
+ () => !!Services.cookieBanners.rules.length,
+ "waiting for Services.cookieBanners.rules.length to be greater than 0"
+ );
+
+ // Test both normal and private browsing windows. For normal windows we reuse
+ // the existing one, for private windows we need to open a new window.
+ for (let testPBM of [false, true]) {
+ let win = window;
+ if (testPBM) {
+ win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ }
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url: "https://example.com" },
+ async () => {
+ let clearSiteDataSpy = sinon.spy(window.SiteDataManager, "remove");
+
+ await openProtectionsPanel(null, win);
+ let switchEl = win.document.getElementById(
+ "protections-popup-cookie-banner-switch"
+ );
+ info("Testing initial switch ON state.");
+ assertSwitchAndPrefState({
+ win,
+ isPBM: testPBM,
+ switchEl,
+ expectedSwitchState: "on",
+ });
+ assertTelemetryState();
+
+ info("Testing switch state after toggle OFF");
+ let closePromise = waitForProtectionsPopupHide(win);
+ await toggleCookieBannerHandling(false, win);
+ await closePromise;
+ if (testPBM) {
+ Assert.ok(
+ clearSiteDataSpy.notCalled,
+ "clearSiteData should not be called in private browsing mode"
+ );
+ } else {
+ Assert.ok(
+ clearSiteDataSpy.calledOnce,
+ "clearSiteData should be called in regular browsing mode"
+ );
+ }
+ clearSiteDataSpy.restore();
+
+ await openProtectionsPanel(null, win);
+ assertSwitchAndPrefState({
+ win,
+ isPBM: testPBM,
+ switchEl,
+ expectedSwitchState: "off",
+ });
+ assertTelemetryState({ expectEnabled: false });
+
+ info("Testing switch state after toggle ON.");
+ closePromise = waitForProtectionsPopupHide(win);
+ await toggleCookieBannerHandling(true, win);
+ await closePromise;
+
+ await openProtectionsPanel(null, win);
+ assertSwitchAndPrefState({
+ win,
+ isPBM: testPBM,
+ switchEl,
+ expectedSwitchState: "on",
+ });
+ assertTelemetryState({ expectEnabled: true });
+ }
+ );
+
+ if (testPBM) {
+ await BrowserTestUtils.closeWindow(win);
+ }
+ }
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_cookies_subview.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_cookies_subview.js
new file mode 100644
index 0000000000..00281ac415
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_cookies_subview.js
@@ -0,0 +1,533 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+"use strict";
+
+const COOKIE_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html";
+const CONTAINER_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/containerPage.html";
+
+const TPC_PREF = "network.cookie.cookieBehavior";
+
+add_setup(async function () {
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ registerCleanupFunction(() => {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+});
+
+/*
+ * Accepts an array containing 6 elements that identify the testcase:
+ * [0] - boolean indicating whether trackers are blocked.
+ * [1] - boolean indicating whether third party cookies are blocked.
+ * [2] - boolean indicating whether first party cookies are blocked.
+ * [3] - integer indicating number of expected content blocking events.
+ * [4] - integer indicating number of expected subview list headers.
+ * [5] - integer indicating number of expected cookie list items.
+ * [6] - integer indicating number of expected cookie list items
+ * after loading a cookie-setting third party URL in an iframe
+ * [7] - integer indicating number of expected cookie list items
+ * after loading a cookie-setting first party URL in an iframe
+ */
+async function assertSitesListed(testCase) {
+ let sitesListedTestCases = [
+ [true, false, false, 4, 1, 1, 1, 1],
+ [true, true, false, 5, 1, 1, 2, 2],
+ [true, true, true, 6, 2, 2, 3, 3],
+ [false, false, false, 3, 1, 1, 1, 1],
+ ];
+ let [
+ trackersBlocked,
+ thirdPartyBlocked,
+ firstPartyBlocked,
+ contentBlockingEventCount,
+ listHeaderCount,
+ cookieItemsCount1,
+ cookieItemsCount2,
+ cookieItemsCount3,
+ ] = sitesListedTestCases[testCase];
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: COOKIE_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([
+ promise,
+ waitForContentBlockingEvent(contentBlockingEventCount),
+ ]);
+ let browser = tab.linkedBrowser;
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-cookies"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
+ let cookiesView = document.getElementById("protections-popup-cookiesView");
+ let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Cookies view was shown");
+
+ let listHeaders = cookiesView.querySelectorAll(
+ ".protections-popup-cookiesView-list-header"
+ );
+ is(
+ listHeaders.length,
+ listHeaderCount,
+ `We have ${listHeaderCount} list headers.`
+ );
+ if (listHeaderCount == 1) {
+ ok(
+ !BrowserTestUtils.isVisible(listHeaders[0]),
+ "Only one header, should be hidden"
+ );
+ } else {
+ for (let header of listHeaders) {
+ ok(
+ BrowserTestUtils.isVisible(header),
+ "Multiple list headers - all should be visible."
+ );
+ }
+ }
+
+ let emptyLabels = cookiesView.querySelectorAll(
+ ".protections-popup-empty-label"
+ );
+ is(emptyLabels.length, 0, `We have no empty labels`);
+
+ let listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
+ is(
+ listItems.length,
+ cookieItemsCount1,
+ `We have ${cookieItemsCount1} cookies in the list`
+ );
+
+ if (trackersBlocked) {
+ let trackerTestItem;
+ for (let item of listItems) {
+ let label = item.querySelector(".protections-popup-list-host-label");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ if (label.value == "http://trackertest.org") {
+ trackerTestItem = item;
+ break;
+ }
+ }
+ ok(trackerTestItem, "Has an item for trackertest.org");
+ ok(BrowserTestUtils.isVisible(trackerTestItem), "List item is visible");
+ }
+
+ if (firstPartyBlocked) {
+ let notTrackingExampleItem;
+ for (let item of listItems) {
+ let label = item.querySelector(".protections-popup-list-host-label");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ if (label.value == "http://not-tracking.example.com") {
+ notTrackingExampleItem = item;
+ break;
+ }
+ }
+ ok(notTrackingExampleItem, "Has an item for not-tracking.example.com");
+ ok(
+ BrowserTestUtils.isVisible(notTrackingExampleItem),
+ "List item is visible"
+ );
+ }
+ let mainView = document.getElementById("protections-popup-mainView");
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ let backButton = cookiesView.querySelector(".subviewbutton-back");
+ backButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+
+ let change = waitForContentBlockingEvent();
+ let timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000));
+
+ await SpecialPowers.spawn(browser, [], function () {
+ content.postMessage("third-party-cookie", "*");
+ });
+
+ let result = await Promise.race([change, timeoutPromise]);
+ is(result, undefined, "No contentBlockingEvent events should be received");
+
+ viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Cookies view was shown");
+
+ emptyLabels = cookiesView.querySelectorAll(".protections-popup-empty-label");
+ is(emptyLabels.length, 0, `We have no empty labels`);
+
+ listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
+ is(
+ listItems.length,
+ cookieItemsCount2,
+ `We have ${cookieItemsCount2} cookies in the list`
+ );
+
+ if (thirdPartyBlocked) {
+ let test1ExampleItem;
+ for (let item of listItems) {
+ let label = item.querySelector(".protections-popup-list-host-label");
+ if (label.value == "https://test1.example.org") {
+ test1ExampleItem = item;
+ break;
+ }
+ }
+ ok(test1ExampleItem, "Has an item for test1.example.org");
+ ok(BrowserTestUtils.isVisible(test1ExampleItem), "List item is visible");
+ }
+
+ if (trackersBlocked || thirdPartyBlocked || firstPartyBlocked) {
+ let trackerTestItem;
+ for (let item of listItems) {
+ let label = item.querySelector(".protections-popup-list-host-label");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ if (label.value == "http://trackertest.org") {
+ trackerTestItem = item;
+ break;
+ }
+ }
+ ok(trackerTestItem, "List item should exist for http://trackertest.org");
+ ok(BrowserTestUtils.isVisible(trackerTestItem), "List item is visible");
+ }
+
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ backButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+
+ change = waitForSecurityChange();
+ timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000));
+
+ await SpecialPowers.spawn(browser, [], function () {
+ content.postMessage("first-party-cookie", "*");
+ });
+
+ result = await Promise.race([change, timeoutPromise]);
+ is(result, undefined, "No securityChange events should be received");
+
+ viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Cookies view was shown");
+
+ emptyLabels = cookiesView.querySelectorAll(".protections-popup-empty-label");
+ is(emptyLabels.length, 0, "We have no empty labels");
+
+ listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
+ is(
+ listItems.length,
+ cookieItemsCount3,
+ `We have ${cookieItemsCount3} cookies in the list`
+ );
+
+ if (firstPartyBlocked) {
+ let notTrackingExampleItem;
+ for (let item of listItems) {
+ let label = item.querySelector(".protections-popup-list-host-label");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ if (label.value == "http://not-tracking.example.com") {
+ notTrackingExampleItem = item;
+ break;
+ }
+ }
+ ok(notTrackingExampleItem, "Has an item for not-tracking.example.com");
+ ok(
+ BrowserTestUtils.isVisible(notTrackingExampleItem),
+ "List item is visible"
+ );
+ }
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function testCookiesSubView() {
+ info("Testing cookies subview with reject tracking cookies.");
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ let testCaseIndex = 0;
+ await assertSitesListed(testCaseIndex++);
+ info("Testing cookies subview with reject third party cookies.");
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN
+ );
+ await assertSitesListed(testCaseIndex++);
+ info("Testing cookies subview with reject all cookies.");
+ Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT);
+ await assertSitesListed(testCaseIndex++);
+ info("Testing cookies subview with accept all cookies.");
+ Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
+ await assertSitesListed(testCaseIndex++);
+
+ Services.prefs.clearUserPref(TPC_PREF);
+});
+
+add_task(async function testCookiesSubViewAllowed() {
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://trackertest.org/"
+ );
+ Services.perms.addFromPrincipal(
+ principal,
+ "cookie",
+ Services.perms.ALLOW_ACTION
+ );
+
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: COOKIE_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent(3)]);
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-cookies"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
+ let cookiesView = document.getElementById("protections-popup-cookiesView");
+ let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Cookies view was shown");
+
+ let listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
+ is(listItems.length, 1, "We have 1 cookie in the list");
+
+ let listItem = listItems[0];
+ let label = listItem.querySelector(".protections-popup-list-host-label");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ is(label.value, "http://trackertest.org", "has an item for trackertest.org");
+ ok(BrowserTestUtils.isVisible(listItem), "list item is visible");
+ ok(
+ listItem.classList.contains("allowed"),
+ "indicates whether the cookie was blocked or allowed"
+ );
+
+ let stateLabel = listItem.querySelector(
+ ".protections-popup-list-state-label"
+ );
+ ok(stateLabel, "List item has a state label");
+ ok(BrowserTestUtils.isVisible(stateLabel), "State label is visible");
+ is(
+ stateLabel.getAttribute("data-l10n-id"),
+ "content-blocking-cookies-view-allowed-label",
+ "State label has correct text"
+ );
+
+ let button = listItem.querySelector(
+ ".permission-popup-permission-remove-button"
+ );
+ ok(BrowserTestUtils.isVisible(button), "Permission remove button is visible");
+ button.click();
+ is(
+ Services.perms.testExactPermissionFromPrincipal(principal, "cookie"),
+ Services.perms.UNKNOWN_ACTION,
+ "Button click should remove cookie pref."
+ );
+ ok(!listItem.classList.contains("allowed"), "Has removed the allowed class");
+
+ BrowserTestUtils.removeTab(tab);
+
+ Services.prefs.clearUserPref(TPC_PREF);
+});
+
+add_task(async function testCookiesSubViewAllowedHeuristic() {
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://not-tracking.example.com/"
+ );
+
+ // Pretend that the tracker has already been interacted with
+ let trackerPrincipal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://trackertest.org/"
+ );
+ Services.perms.addFromPrincipal(
+ trackerPrincipal,
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION
+ );
+
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: COOKIE_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent(5)]);
+ let browser = tab.linkedBrowser;
+
+ let popup;
+ let windowCreated = TestUtils.topicObserved(
+ "chrome-document-global-created",
+ (subject, data) => {
+ popup = subject;
+ return true;
+ }
+ );
+ let permChanged = TestUtils.topicObserved("perm-changed", (subject, data) => {
+ return (
+ subject &&
+ subject.QueryInterface(Ci.nsIPermission).type ==
+ "3rdPartyStorage^http://trackertest.org" &&
+ subject.principal.origin == principal.origin &&
+ data == "added"
+ );
+ });
+
+ await SpecialPowers.spawn(browser, [], function () {
+ content.postMessage("window-open", "*");
+ });
+ await Promise.all([windowCreated, permChanged]);
+
+ await new Promise(resolve => waitForFocus(resolve, popup));
+ await new Promise(resolve => waitForFocus(resolve, window));
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-cookies"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
+ let cookiesView = document.getElementById("protections-popup-cookiesView");
+ let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Cookies view was shown");
+
+ let listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
+ is(listItems.length, 1, "We have 1 cookie in the list");
+
+ let listItem = listItems[0];
+ let label = listItem.querySelector(".protections-popup-list-host-label");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ is(label.value, "http://trackertest.org", "has an item for trackertest.org");
+ ok(BrowserTestUtils.isVisible(listItem), "list item is visible");
+ ok(
+ listItem.classList.contains("allowed"),
+ "indicates whether the cookie was blocked or allowed"
+ );
+
+ let button = listItem.querySelector(
+ ".permission-popup-permission-remove-button"
+ );
+ ok(BrowserTestUtils.isVisible(button), "Permission remove button is visible");
+ button.click();
+ is(
+ Services.perms.testExactPermissionFromPrincipal(
+ principal,
+ "3rdPartyStorage^http://trackertest.org"
+ ),
+ Services.perms.UNKNOWN_ACTION,
+ "Button click should remove the storage pref."
+ );
+ ok(!listItem.classList.contains("allowed"), "Has removed the allowed class");
+
+ await SpecialPowers.spawn(browser, [], function () {
+ content.postMessage("window-close", "*");
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ Services.prefs.clearUserPref(TPC_PREF);
+});
+
+add_task(async function testCookiesSubViewBlockedDoublyNested() {
+ requestLongerTimeout(2);
+
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: CONTAINER_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent(3)]);
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-cookies"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
+ let cookiesView = document.getElementById("protections-popup-cookiesView");
+ let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Cookies view was shown");
+
+ let listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
+ is(listItems.length, 1, "We have 1 cookie in the list");
+
+ let listItem = listItems[0];
+ let label = listItem.querySelector(".protections-popup-list-host-label");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ is(label.value, "http://trackertest.org", "has an item for trackertest.org");
+ ok(BrowserTestUtils.isVisible(listItem), "list item is visible");
+ ok(
+ !listItem.classList.contains("allowed"),
+ "indicates whether the cookie was blocked or allowed"
+ );
+
+ let button = listItem.querySelector(
+ ".permission-popup-permission-remove-button"
+ );
+ ok(!button, "Permission remove button doesn't exist");
+
+ BrowserTestUtils.removeTab(tab);
+
+ Services.prefs.clearUserPref(TPC_PREF);
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js
new file mode 100644
index 0000000000..30a836d352
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js
@@ -0,0 +1,306 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+const CM_PROTECTION_PREF = "privacy.trackingprotection.cryptomining.enabled";
+let cmHistogram;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "urlclassifier.features.cryptomining.blacklistHosts",
+ "cryptomining.example.com",
+ ],
+ [
+ "urlclassifier.features.cryptomining.annotate.blacklistHosts",
+ "cryptomining.example.com",
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", false],
+ ["privacy.trackingprotection.fingerprinting.enabled", false],
+ ["urlclassifier.features.fingerprinting.annotate.blacklistHosts", ""],
+ ],
+ });
+ cmHistogram = Services.telemetry.getHistogramById(
+ "CRYPTOMINERS_BLOCKED_COUNT"
+ );
+ registerCleanupFunction(() => {
+ cmHistogram.clear();
+ });
+});
+
+async function testIdentityState(hasException) {
+ cmHistogram.clear();
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.disableForCurrentPage();
+ await loaded;
+ }
+
+ await openProtectionsPanel();
+
+ ok(
+ !gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "cryptominers are not detected"
+ );
+
+ ok(
+ BrowserTestUtils.isVisible(gProtectionsHandler.iconBox),
+ "icon box is visible regardless the exception"
+ );
+
+ promise = waitForContentBlockingEvent();
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("cryptomining", "*");
+ });
+
+ await promise;
+
+ ok(
+ gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "trackers are detected"
+ );
+ ok(
+ BrowserTestUtils.isVisible(gProtectionsHandler.iconBox),
+ "icon box is visible"
+ );
+ is(
+ gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ hasException,
+ "Shows an exception when appropriate"
+ );
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.enableForCurrentPage();
+ await loaded;
+ }
+
+ let loads = hasException ? 3 : 1;
+ testTelemetry(loads, 1, hasException);
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testSubview(hasException) {
+ cmHistogram.clear();
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.disableForCurrentPage();
+ await loaded;
+ }
+
+ promise = waitForContentBlockingEvent();
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("cryptomining", "*");
+ });
+ await promise;
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-cryptominers"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
+
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ // We have to wait until the ContentBlockingLog gets updated in the content.
+ // Unfortunately, we need to use the setTimeout here since we don't have an
+ // easy to know whether the log is updated in the content. This should be
+ // removed after the log been removed in the content (Bug 1599046).
+ await new Promise(resolve => {
+ setTimeout(resolve, 500);
+ });
+ /* eslint-enable mozilla/no-arbitrary-setTimeout */
+
+ let subview = document.getElementById("protections-popup-cryptominersView");
+ let viewShown = BrowserTestUtils.waitForEvent(subview, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ let trackersViewShimHint = document.getElementById(
+ "protections-popup-cryptominersView-shim-allow-hint"
+ );
+ ok(trackersViewShimHint.hidden, "Shim hint is hidden");
+
+ let listItems = subview.querySelectorAll(".protections-popup-list-item");
+ is(listItems.length, 1, "We have 1 item in the list");
+ let listItem = listItems[0];
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+ is(
+ listItem.querySelector("label").value,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://cryptomining.example.com",
+ "Has the correct host"
+ );
+ is(
+ listItem.classList.contains("allowed"),
+ hasException,
+ "Indicates the miner was blocked or allowed"
+ );
+
+ let mainView = document.getElementById("protections-popup-mainView");
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ let backButton = subview.querySelector(".subviewbutton-back");
+ backButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.enableForCurrentPage();
+ await loaded;
+ }
+
+ let loads = hasException ? 3 : 1;
+ testTelemetry(loads, 1, hasException);
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testCategoryItem() {
+ Services.prefs.setBoolPref(CM_PROTECTION_PREF, false);
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-cryptominers"
+ );
+
+ ok(
+ !categoryItem.classList.contains("blocked"),
+ "Category not marked as blocked"
+ );
+ ok(
+ categoryItem.classList.contains("notFound"),
+ "Category marked as not found"
+ );
+ Services.prefs.setBoolPref(CM_PROTECTION_PREF, true);
+ ok(categoryItem.classList.contains("blocked"), "Category marked as blocked");
+ ok(
+ categoryItem.classList.contains("notFound"),
+ "Category marked as not found"
+ );
+ Services.prefs.setBoolPref(CM_PROTECTION_PREF, false);
+ ok(
+ !categoryItem.classList.contains("blocked"),
+ "Category not marked as blocked"
+ );
+ ok(
+ categoryItem.classList.contains("notFound"),
+ "Category marked as not found"
+ );
+ await closeProtectionsPanel();
+
+ promise = waitForContentBlockingEvent();
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("cryptomining", "*");
+ });
+
+ await promise;
+
+ await openProtectionsPanel();
+ ok(
+ !categoryItem.classList.contains("blocked"),
+ "Category not marked as blocked"
+ );
+ ok(
+ !categoryItem.classList.contains("notFound"),
+ "Category not marked as not found"
+ );
+ Services.prefs.setBoolPref(CM_PROTECTION_PREF, true);
+ ok(categoryItem.classList.contains("blocked"), "Category marked as blocked");
+ ok(
+ !categoryItem.classList.contains("notFound"),
+ "Category not marked as not found"
+ );
+ Services.prefs.setBoolPref(CM_PROTECTION_PREF, false);
+ ok(
+ !categoryItem.classList.contains("blocked"),
+ "Category not marked as blocked"
+ );
+ ok(
+ !categoryItem.classList.contains("notFound"),
+ "Category not marked as not found"
+ );
+ await closeProtectionsPanel();
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+function testTelemetry(pagesVisited, pagesWithBlockableContent, hasException) {
+ let results = cmHistogram.snapshot();
+ Assert.equal(
+ results.values[0],
+ pagesVisited,
+ "The correct number of page loads have been recorded"
+ );
+ let expectedValue = hasException ? 2 : 1;
+ Assert.equal(
+ results.values[expectedValue],
+ pagesWithBlockableContent,
+ "The correct number of cryptominers have been recorded as blocked or allowed."
+ );
+}
+
+add_task(async function test() {
+ Services.prefs.setBoolPref(CM_PROTECTION_PREF, true);
+
+ await testIdentityState(false);
+ await testIdentityState(true);
+
+ await testSubview(false);
+ await testSubview(true);
+
+ await testCategoryItem();
+
+ Services.prefs.clearUserPref(CM_PROTECTION_PREF);
+ Services.prefs.setStringPref("browser.contentblocking.category", "standard");
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_email_trackers_subview.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_email_trackers_subview.js
new file mode 100644
index 0000000000..1beda6c6e2
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_email_trackers_subview.js
@@ -0,0 +1,179 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Bug 1819662 - Testing the tracking category of the protection panel shows the
+ * email tracker domain if the email tracking protection is
+ * enabled
+ */
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+const TEST_PAGE =
+ "https://www.example.com/browser/browser/base/content/test/protectionsUI/emailTrackingPage.html";
+const TEST_TRACKER_PAGE = "https://itisatracker.org/";
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+const EMAIL_TP_PREF = "privacy.trackingprotection.emailtracking.enabled";
+
+/**
+ * A helper function to check whether or not an element has "notFound" class.
+ *
+ * @param {String} id The id of the testing element.
+ * @returns {Boolean} true when the element has "notFound" class.
+ */
+function notFound(id) {
+ return document.getElementById(id).classList.contains("notFound");
+}
+
+/**
+ * A helper function to test the protection UI tracker category.
+ *
+ * @param {Boolean} blocked - true if the email tracking protection is enabled.
+ */
+async function assertSitesListed(blocked) {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ url: TEST_PAGE,
+ gBrowser,
+ });
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-trackers"
+ );
+
+ if (!blocked) {
+ // The tracker category should have the 'notFound' class to indicate that
+ // no tracker was blocked in the page.
+ ok(
+ notFound("protections-popup-category-trackers"),
+ "Tracker category is not found"
+ );
+
+ ok(
+ !BrowserTestUtils.isVisible(categoryItem),
+ "TP category item is not visible"
+ );
+ BrowserTestUtils.removeTab(tab);
+
+ return;
+ }
+
+ // Testing if the tracker category is visible.
+
+ // Explicitly waiting for the category item becoming visible.
+ await BrowserTestUtils.waitForMutationCondition(categoryItem, {}, () =>
+ BrowserTestUtils.isVisible(categoryItem)
+ );
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
+
+ // Click the tracker category and wait until the tracker view is shown.
+ let trackersView = document.getElementById("protections-popup-trackersView");
+ let viewShown = BrowserTestUtils.waitForEvent(trackersView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Trackers view was shown");
+
+ // Ensure the email tracker is listed on the tracker list.
+ let listItems = Array.from(
+ trackersView.querySelectorAll(".protections-popup-list-item")
+ );
+ is(listItems.length, 1, "We have 1 trackers in the list");
+
+ let listItem = listItems.find(
+ item =>
+ item.querySelector("label").value == "https://email-tracking.example.org"
+ );
+ ok(listItem, "Has an item for email-tracking.example.org");
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+
+ // Back to the popup main view.
+ let mainView = document.getElementById("protections-popup-mainView");
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ let backButton = trackersView.querySelector(".subviewbutton-back");
+ backButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+
+ // Add an iframe to a tracker domain and wait until the content event files.
+ let contentBlockingEventPromise = waitForContentBlockingEvent(1);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [TEST_TRACKER_PAGE],
+ test_url => {
+ let ifr = content.document.createElement("iframe");
+
+ content.document.body.appendChild(ifr);
+ ifr.src = test_url;
+ }
+ );
+ await contentBlockingEventPromise;
+
+ // Click the tracker category again.
+ viewShown = BrowserTestUtils.waitForEvent(trackersView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ // Ensure both the email tracker and the tracker are listed on the tracker
+ // list.
+ listItems = Array.from(
+ trackersView.querySelectorAll(".protections-popup-list-item")
+ );
+ is(listItems.length, 2, "We have 2 trackers in the list");
+
+ listItem = listItems.find(
+ item =>
+ item.querySelector("label").value == "https://email-tracking.example.org"
+ );
+ ok(listItem, "Has an item for email-tracking.example.org");
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+
+ listItem = listItems.find(
+ item => item.querySelector("label").value == "https://itisatracker.org"
+ );
+ ok(listItem, "Has an item for itisatracker.org");
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_setup(async function () {
+ Services.prefs.setBoolPref(TP_PREF, true);
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(TP_PREF);
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+});
+
+add_task(async function testTrackersSubView() {
+ info("Testing trackers subview with TP disabled.");
+ Services.prefs.setBoolPref(EMAIL_TP_PREF, false);
+ await assertSitesListed(false);
+ info("Testing trackers subview with TP enabled.");
+ Services.prefs.setBoolPref(EMAIL_TP_PREF, true);
+ await assertSitesListed(true);
+ info("Testing trackers subview with TP enabled and a CB exception.");
+ let uri = Services.io.newURI("https://www.example.com");
+ PermissionTestUtils.add(
+ uri,
+ "trackingprotection",
+ Services.perms.ALLOW_ACTION
+ );
+ await assertSitesListed(false);
+ info("Testing trackers subview with TP enabled and a CB exception removed.");
+ PermissionTestUtils.remove(uri, "trackingprotection");
+ await assertSitesListed(true);
+
+ Services.prefs.clearUserPref(EMAIL_TP_PREF);
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_fetch.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_fetch.js
new file mode 100644
index 0000000000..26b131d4eb
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_fetch.js
@@ -0,0 +1,43 @@
+const URL =
+ "http://mochi.test:8888/browser/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.html";
+
+add_task(async function test_fetch() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.trackingprotection.enabled", true]],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (newTabBrowser) {
+ const win = newTabBrowser.ownerGlobal;
+ await openProtectionsPanel(false, win);
+ let contentBlockingEvent = waitForContentBlockingEvent();
+ await SpecialPowers.spawn(newTabBrowser, [], async function () {
+ await content.wrappedJSObject
+ .test_fetch()
+ .then(response => Assert.ok(false, "should have denied the request"))
+ .catch(e => Assert.ok(true, `Caught exception: ${e}`));
+ });
+ await contentBlockingEvent;
+
+ const gProtectionsHandler = win.gProtectionsHandler;
+ ok(gProtectionsHandler, "got CB object");
+
+ ok(
+ gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "has detected content blocking"
+ );
+ ok(
+ gProtectionsHandler.iconBox.hasAttribute("active"),
+ "icon box is active"
+ );
+ is(
+ gProtectionsHandler._trackingProtectionIconTooltipLabel.getAttribute(
+ "data-l10n-id"
+ ),
+ "tracking-protection-icon-active",
+ "correct tooltip"
+ );
+ }
+ );
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js
new file mode 100644
index 0000000000..9d2a2f1da6
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js
@@ -0,0 +1,303 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+const FP_PROTECTION_PREF = "privacy.trackingprotection.fingerprinting.enabled";
+let fpHistogram;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "urlclassifier.features.fingerprinting.blacklistHosts",
+ "fingerprinting.example.com",
+ ],
+ [
+ "urlclassifier.features.fingerprinting.annotate.blacklistHosts",
+ "fingerprinting.example.com",
+ ],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", false],
+ ["privacy.trackingprotection.cryptomining.enabled", false],
+ ["urlclassifier.features.cryptomining.annotate.blacklistHosts", ""],
+ ["urlclassifier.features.cryptomining.annotate.blacklistTables", ""],
+ ],
+ });
+ fpHistogram = Services.telemetry.getHistogramById(
+ "FINGERPRINTERS_BLOCKED_COUNT"
+ );
+ registerCleanupFunction(() => {
+ fpHistogram.clear();
+ });
+});
+
+async function testIdentityState(hasException) {
+ fpHistogram.clear();
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.disableForCurrentPage();
+ await loaded;
+ }
+
+ await openProtectionsPanel();
+
+ ok(
+ !gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "fingerprinters are not detected"
+ );
+ ok(
+ !BrowserTestUtils.isHidden(gProtectionsHandler.iconBox),
+ "icon box is visible regardless the exception"
+ );
+
+ promise = waitForContentBlockingEvent();
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("fingerprinting", "*");
+ });
+
+ await promise;
+
+ ok(
+ gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "trackers are detected"
+ );
+ ok(
+ BrowserTestUtils.isVisible(gProtectionsHandler.iconBox),
+ "icon box is visible"
+ );
+ is(
+ gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ hasException,
+ "Shows an exception when appropriate"
+ );
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.enableForCurrentPage();
+ await loaded;
+ }
+
+ let loads = hasException ? 3 : 1;
+ testTelemetry(loads, 1, hasException);
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testCategoryItem() {
+ Services.prefs.setBoolPref(FP_PROTECTION_PREF, false);
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ await openProtectionsPanel();
+ let categoryItem = document.getElementById(
+ "protections-popup-category-fingerprinters"
+ );
+
+ ok(
+ !categoryItem.classList.contains("blocked"),
+ "Category not marked as blocked"
+ );
+ ok(
+ categoryItem.classList.contains("notFound"),
+ "Category marked as not found"
+ );
+ Services.prefs.setBoolPref(FP_PROTECTION_PREF, true);
+ ok(categoryItem.classList.contains("blocked"), "Category marked as blocked");
+ ok(
+ categoryItem.classList.contains("notFound"),
+ "Category marked as not found"
+ );
+ Services.prefs.setBoolPref(FP_PROTECTION_PREF, false);
+ ok(
+ !categoryItem.classList.contains("blocked"),
+ "Category not marked as blocked"
+ );
+ ok(
+ categoryItem.classList.contains("notFound"),
+ "Category marked as not found"
+ );
+ await closeProtectionsPanel();
+
+ promise = waitForContentBlockingEvent();
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("fingerprinting", "*");
+ });
+
+ await promise;
+
+ await openProtectionsPanel();
+ ok(
+ !categoryItem.classList.contains("blocked"),
+ "Category not marked as blocked"
+ );
+ ok(
+ !categoryItem.classList.contains("notFound"),
+ "Category not marked as not found"
+ );
+ Services.prefs.setBoolPref(FP_PROTECTION_PREF, true);
+ ok(categoryItem.classList.contains("blocked"), "Category marked as blocked");
+ ok(
+ !categoryItem.classList.contains("notFound"),
+ "Category not marked as not found"
+ );
+ Services.prefs.setBoolPref(FP_PROTECTION_PREF, false);
+ ok(
+ !categoryItem.classList.contains("blocked"),
+ "Category not marked as blocked"
+ );
+ ok(
+ !categoryItem.classList.contains("notFound"),
+ "Category not marked as not found"
+ );
+ await closeProtectionsPanel();
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testSubview(hasException) {
+ fpHistogram.clear();
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.disableForCurrentPage();
+ await loaded;
+ }
+
+ promise = waitForContentBlockingEvent();
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("fingerprinting", "*");
+ });
+ await promise;
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-fingerprinters"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
+
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ // We have to wait until the ContentBlockingLog gets updated in the content.
+ // Unfortunately, we need to use the setTimeout here since we don't have an
+ // easy to know whether the log is updated in the content. This should be
+ // removed after the log been removed in the content (Bug 1599046).
+ await new Promise(resolve => {
+ setTimeout(resolve, 500);
+ });
+ /* eslint-enable mozilla/no-arbitrary-setTimeout */
+
+ let subview = document.getElementById("protections-popup-fingerprintersView");
+ let viewShown = BrowserTestUtils.waitForEvent(subview, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ let trackersViewShimHint = document.getElementById(
+ "protections-popup-fingerprintersView-shim-allow-hint"
+ );
+ ok(trackersViewShimHint.hidden, "Shim hint is hidden");
+
+ let listItems = subview.querySelectorAll(".protections-popup-list-item");
+ is(listItems.length, 1, "We have 1 item in the list");
+ let listItem = listItems[0];
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+ is(
+ listItem.querySelector("label").value,
+ "https://fingerprinting.example.com",
+ "Has the correct host"
+ );
+ is(
+ listItem.classList.contains("allowed"),
+ hasException,
+ "Indicates the fingerprinter was blocked or allowed"
+ );
+
+ let mainView = document.getElementById("protections-popup-mainView");
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ let backButton = subview.querySelector(".subviewbutton-back");
+ backButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.enableForCurrentPage();
+ await loaded;
+ }
+
+ let loads = hasException ? 3 : 1;
+ testTelemetry(loads, 1, hasException);
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+function testTelemetry(pagesVisited, pagesWithBlockableContent, hasException) {
+ let results = fpHistogram.snapshot();
+ Assert.equal(
+ results.values[0],
+ pagesVisited,
+ "The correct number of page loads have been recorded"
+ );
+ let expectedValue = hasException ? 2 : 1;
+ Assert.equal(
+ results.values[expectedValue],
+ pagesWithBlockableContent,
+ "The correct number of fingerprinters have been recorded as blocked or allowed."
+ );
+}
+
+add_task(async function test() {
+ Services.prefs.setBoolPref(FP_PROTECTION_PREF, true);
+
+ await testIdentityState(false);
+ await testIdentityState(true);
+
+ await testSubview(false);
+ await testSubview(true);
+
+ await testCategoryItem();
+
+ Services.prefs.clearUserPref(FP_PROTECTION_PREF);
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_icon_state.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_icon_state.js
new file mode 100644
index 0000000000..187a777850
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_icon_state.js
@@ -0,0 +1,223 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+/*
+ * Test that the Content Blocking icon state is properly updated in the identity
+ * block when loading tabs and switching between tabs.
+ * See also Bug 1175858.
+ */
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const APS_PREF =
+ "privacy.partition.always_partition_third_party_non_cookie_storage";
+const NCB_PREF = "network.cookie.cookieBehavior";
+const BENIGN_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html";
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+const COOKIE_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/cookiePage.html";
+const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
+
+registerCleanupFunction(function () {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref(TP_PREF);
+ Services.prefs.clearUserPref(TP_PB_PREF);
+ Services.prefs.clearUserPref(NCB_PREF);
+ Services.prefs.clearUserPref(DTSCBN_PREF);
+});
+
+async function testTrackingProtectionIconState(tabbrowser) {
+ Services.prefs.setBoolPref(DTSCBN_PREF, true);
+
+ info("Load a test page not containing tracking elements");
+ let benignTab = await BrowserTestUtils.openNewForegroundTab(
+ tabbrowser,
+ BENIGN_PAGE
+ );
+ let gProtectionsHandler = tabbrowser.ownerGlobal.gProtectionsHandler;
+
+ ok(!gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox not active");
+
+ info("Load a test page containing tracking elements");
+ let trackingTab = await BrowserTestUtils.openNewForegroundTab(
+ tabbrowser,
+ TRACKING_PAGE
+ );
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active");
+
+ info("Load a test page containing tracking cookies");
+ let trackingCookiesTab = await BrowserTestUtils.openNewForegroundTab(
+ tabbrowser,
+ COOKIE_PAGE
+ );
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active");
+
+ info("Switch from tracking cookie -> benign tab");
+ let securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
+ tabbrowser.selectedTab = benignTab;
+ await securityChanged;
+
+ ok(!gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox not active");
+
+ info("Switch from benign -> tracking tab");
+ securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
+ tabbrowser.selectedTab = trackingTab;
+ await securityChanged;
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active");
+
+ info("Switch from tracking -> tracking cookies tab");
+ securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
+ tabbrowser.selectedTab = trackingCookiesTab;
+ await securityChanged;
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active");
+
+ info("Reload tracking cookies tab");
+ securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
+ let contentBlockingEvent = waitForContentBlockingEvent(
+ 2,
+ tabbrowser.ownerGlobal
+ );
+ tabbrowser.reload();
+ await Promise.all([securityChanged, contentBlockingEvent]);
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active");
+
+ info("Reload tracking tab");
+ securityChanged = waitForSecurityChange(2, tabbrowser.ownerGlobal);
+ contentBlockingEvent = waitForContentBlockingEvent(3, tabbrowser.ownerGlobal);
+ tabbrowser.selectedTab = trackingTab;
+ tabbrowser.reload();
+ await Promise.all([securityChanged, contentBlockingEvent]);
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active");
+
+ info("Inject tracking cookie inside tracking tab");
+ securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
+ let timeoutPromise = new Promise(resolve => setTimeout(resolve, 500));
+ await SpecialPowers.spawn(tabbrowser.selectedBrowser, [], function () {
+ content.postMessage("cookie", "*");
+ });
+ let result = await Promise.race([securityChanged, timeoutPromise]);
+ is(result, undefined, "No securityChange events should be received");
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active");
+
+ info("Inject tracking element inside tracking tab");
+ securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
+ timeoutPromise = new Promise(resolve => setTimeout(resolve, 500));
+ await SpecialPowers.spawn(tabbrowser.selectedBrowser, [], function () {
+ content.postMessage("tracking", "*");
+ });
+ result = await Promise.race([securityChanged, timeoutPromise]);
+ is(result, undefined, "No securityChange events should be received");
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active");
+
+ tabbrowser.selectedTab = trackingCookiesTab;
+
+ info("Inject tracking cookie inside tracking cookies tab");
+ securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
+ timeoutPromise = new Promise(resolve => setTimeout(resolve, 500));
+ await SpecialPowers.spawn(tabbrowser.selectedBrowser, [], function () {
+ content.postMessage("cookie", "*");
+ });
+ result = await Promise.race([securityChanged, timeoutPromise]);
+ is(result, undefined, "No securityChange events should be received");
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active");
+
+ info("Inject tracking element inside tracking cookies tab");
+ securityChanged = waitForSecurityChange(1, tabbrowser.ownerGlobal);
+ timeoutPromise = new Promise(resolve => setTimeout(resolve, 500));
+ await SpecialPowers.spawn(tabbrowser.selectedBrowser, [], function () {
+ content.postMessage("tracking", "*");
+ });
+ result = await Promise.race([securityChanged, timeoutPromise]);
+ is(result, undefined, "No securityChange events should be received");
+
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "iconBox active");
+
+ while (tabbrowser.tabs.length > 1) {
+ tabbrowser.removeCurrentTab();
+ }
+}
+
+add_task(async function testNormalBrowsing() {
+ await SpecialPowers.pushPrefEnv({ set: [[APS_PREF, false]] });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ let gProtectionsHandler = gBrowser.ownerGlobal.gProtectionsHandler;
+ ok(
+ gProtectionsHandler,
+ "gProtectionsHandler is attached to the browser window"
+ );
+
+ let { TrackingProtection } =
+ gBrowser.ownerGlobal.gProtectionsHandler.blockers;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ let { ThirdPartyCookies } = gBrowser.ownerGlobal.gProtectionsHandler.blockers;
+ ok(ThirdPartyCookies, "TPC is attached to the browser window");
+
+ Services.prefs.setBoolPref(TP_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+ Services.prefs.setIntPref(
+ NCB_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ ok(
+ ThirdPartyCookies.enabled,
+ "ThirdPartyCookies is enabled after setting the pref"
+ );
+
+ await testTrackingProtectionIconState(gBrowser);
+});
+
+add_task(async function testPrivateBrowsing() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [APS_PREF, false],
+ ["dom.security.https_first_pbm", false],
+ ],
+ });
+
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let tabbrowser = privateWin.gBrowser;
+
+ let gProtectionsHandler = tabbrowser.ownerGlobal.gProtectionsHandler;
+ ok(
+ gProtectionsHandler,
+ "gProtectionsHandler is attached to the private window"
+ );
+ let { TrackingProtection } =
+ tabbrowser.ownerGlobal.gProtectionsHandler.blockers;
+ ok(TrackingProtection, "TP is attached to the private window");
+ let { ThirdPartyCookies } =
+ tabbrowser.ownerGlobal.gProtectionsHandler.blockers;
+ ok(ThirdPartyCookies, "TPC is attached to the browser window");
+
+ Services.prefs.setBoolPref(TP_PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+ Services.prefs.setIntPref(
+ NCB_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ ok(
+ ThirdPartyCookies.enabled,
+ "ThirdPartyCookies is enabled after setting the pref"
+ );
+
+ await testTrackingProtectionIconState(tabbrowser);
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_info_message.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_info_message.js
new file mode 100644
index 0000000000..fadfaaab98
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_info_message.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests the info message that apears in the protections panel
+ * on first render, and afterward by clicking the "info" icon */
+
+"use strict";
+
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Set the auto hide timing to 100ms for blocking the test less.
+ ["browser.protections_panel.toast.timeout", 100],
+ // Hide protections cards so as not to trigger more async messaging
+ // when landing on the page.
+ ["browser.contentblocking.report.monitor.enabled", false],
+ ["browser.contentblocking.report.lockwise.enabled", false],
+ ["browser.contentblocking.report.proxy.enabled", false],
+ ["privacy.trackingprotection.enabled", true],
+ // Set the infomessage pref to ensure the message is displayed
+ // every time
+ ["browser.protections_panel.infoMessage.seen", false],
+ ],
+ });
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+ Services.telemetry.clearEvents();
+
+ registerCleanupFunction(() => {
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ Services.telemetry.clearEvents();
+ });
+});
+
+add_task(async function testPanelInfoMessage() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TRACKING_PAGE
+ );
+
+ await openProtectionsPanel();
+
+ await TestUtils.waitForCondition(() => {
+ return gProtectionsHandler._protectionsPopup.hasAttribute(
+ "infoMessageShowing"
+ );
+ });
+
+ // Test that the info message is displayed when the panel opens
+ let container = document.getElementById("messaging-system-message-container");
+ let message = document.getElementById("protections-popup-message");
+ let learnMoreLink = document.querySelector(
+ "#messaging-system-message-container .text-link"
+ );
+
+ // Check the visibility of the info message.
+ ok(
+ BrowserTestUtils.isVisible(container),
+ "The message container should exist."
+ );
+
+ ok(BrowserTestUtils.isVisible(message), "The message should be visible.");
+
+ ok(BrowserTestUtils.isVisible(learnMoreLink), "The link should be visible.");
+
+ // Check telemetry for the info message
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ false
+ ).parent;
+ let messageEvents = events.filter(
+ e =>
+ e[1] == "security.ui.protectionspopup" &&
+ e[2] == "open" &&
+ e[3] == "protectionspopup_cfr" &&
+ e[4] == "impression"
+ );
+ is(
+ messageEvents.length,
+ 1,
+ "recorded telemetry for showing the info message"
+ );
+ //Clear telemetry from this test so that the next one doesn't fall over
+ Services.telemetry.clearEvents();
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_milestones.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_milestones.js
new file mode 100644
index 0000000000..713d13c30c
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_milestones.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Hide protections cards so as not to trigger more async messaging
+ // when landing on the page.
+ ["browser.contentblocking.report.monitor.enabled", false],
+ ["browser.contentblocking.report.lockwise.enabled", false],
+ ["browser.contentblocking.report.proxy.enabled", false],
+ ["browser.contentblocking.cfr-milestone.update-interval", 0],
+ ],
+ });
+});
+
+add_task(async function doTest() {
+ requestLongerTimeout(3);
+
+ // The protections panel needs to be openend at least once,
+ // or the milestone-achieved pref observer is not triggered.
+ await BrowserTestUtils.withNewTab("https://example.com", async () => {
+ await openProtectionsPanel();
+ await closeProtectionsPanel();
+ });
+
+ // This also ensures that the DB tables have been initialized.
+ await TrackingDBService.clearAll();
+
+ let milestones = JSON.parse(
+ Services.prefs.getStringPref(
+ "browser.contentblocking.cfr-milestone.milestones"
+ )
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+
+ for (let milestone of milestones) {
+ Services.telemetry.clearEvents();
+ // Trigger the milestone feature.
+ Services.prefs.setIntPref(
+ "browser.contentblocking.cfr-milestone.milestone-achieved",
+ milestone
+ );
+ await TestUtils.waitForCondition(
+ () => gProtectionsHandler._milestoneTextSet
+ );
+ // We set the shown-time pref to pretend that the CFR has been
+ // shown, so that we can test the panel.
+ // TODO: Full integration test for robustness.
+ Services.prefs.setStringPref(
+ "browser.contentblocking.cfr-milestone.milestone-shown-time",
+ Date.now().toString()
+ );
+ await openProtectionsPanel();
+
+ ok(
+ BrowserTestUtils.isVisible(
+ gProtectionsHandler._protectionsPopupMilestonesText
+ ),
+ "Milestones section should be visible in the panel."
+ );
+
+ await closeProtectionsPanel();
+ await openProtectionsPanel();
+
+ ok(
+ BrowserTestUtils.isVisible(
+ gProtectionsHandler._protectionsPopupMilestonesText
+ ),
+ "Milestones section should still be visible in the panel."
+ );
+
+ let newTabPromise = waitForAboutProtectionsTab();
+ await EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("protections-popup-milestones-content"),
+ {}
+ );
+ let protectionsTab = await newTabPromise;
+
+ ok(true, "about:protections has been opened as expected.");
+
+ BrowserTestUtils.removeTab(protectionsTab);
+
+ await openProtectionsPanel();
+
+ ok(
+ !BrowserTestUtils.isVisible(
+ gProtectionsHandler._protectionsPopupMilestonesText
+ ),
+ "Milestones section should no longer be visible in the panel."
+ );
+
+ checkClickTelemetry("milestone_message");
+
+ await closeProtectionsPanel();
+ }
+
+ BrowserTestUtils.removeTab(tab);
+ await TrackingDBService.clearAll();
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js
new file mode 100644
index 0000000000..5790ebe1e0
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js
@@ -0,0 +1,155 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+const TPC_PREF = "network.cookie.cookieBehavior";
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+const COOKIE_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html";
+
+async function waitAndAssertPreferencesShown(_spotlight) {
+ await BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+ await TestUtils.waitForCondition(
+ () => gBrowser.currentURI.spec == "about:preferences#privacy",
+ "Should open about:preferences."
+ );
+
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [_spotlight],
+ async spotlight => {
+ let doc = content.document;
+ let section = await ContentTaskUtils.waitForCondition(
+ () => doc.querySelector(".spotlight"),
+ "The spotlight should appear."
+ );
+ Assert.equal(
+ section.getAttribute("data-subcategory"),
+ spotlight,
+ "The correct section is spotlighted."
+ );
+ }
+ );
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
+
+add_setup(async function () {
+ await UrlClassifierTestUtils.addTestTrackers();
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+
+ registerCleanupFunction(() => {
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+});
+
+// Tests that pressing the preferences button in the trackers subview
+// links to about:preferences
+add_task(async function testOpenPreferencesFromTrackersSubview() {
+ Services.prefs.setBoolPref(TP_PREF, true);
+
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+
+ // Wait for 2 content blocking events - one for the load and one for the tracker.
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent(2)]);
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-trackers"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
+ let trackersView = document.getElementById("protections-popup-trackersView");
+ let viewShown = BrowserTestUtils.waitForEvent(trackersView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Trackers view was shown");
+
+ let preferencesButton = document.getElementById(
+ "protections-popup-trackersView-settings-button"
+ );
+
+ ok(
+ BrowserTestUtils.isVisible(preferencesButton),
+ "The preferences button is shown."
+ );
+
+ let shown = waitAndAssertPreferencesShown("trackingprotection");
+ preferencesButton.click();
+ await shown;
+ BrowserTestUtils.removeTab(tab);
+
+ Services.prefs.clearUserPref(TP_PREF);
+});
+
+// Tests that pressing the preferences button in the cookies subview
+// links to about:preferences
+add_task(async function testOpenPreferencesFromCookiesSubview() {
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: COOKIE_PAGE,
+ gBrowser,
+ });
+
+ // Wait for 2 content blocking events - one for the load and one for the tracker.
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent(2)]);
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-cookies"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
+ let cookiesView = document.getElementById("protections-popup-cookiesView");
+ let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Cookies view was shown");
+
+ let preferencesButton = document.getElementById(
+ "protections-popup-cookiesView-settings-button"
+ );
+
+ ok(
+ BrowserTestUtils.isVisible(preferencesButton),
+ "The preferences button is shown."
+ );
+
+ let shown = waitAndAssertPreferencesShown("trackingprotection");
+ preferencesButton.click();
+ await shown;
+ BrowserTestUtils.removeTab(tab);
+
+ Services.prefs.clearUserPref(TPC_PREF);
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js
new file mode 100644
index 0000000000..ee393801bc
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js
@@ -0,0 +1,179 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that sites added to the Tracking Protection whitelist in private
+// browsing mode don't persist once the private browsing window closes.
+
+const TP_PB_PREF = "privacy.trackingprotection.enabled";
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
+var TrackingProtection = null;
+var gProtectionsHandler = null;
+var browser = null;
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(TP_PB_PREF);
+ Services.prefs.clearUserPref(DTSCBN_PREF);
+ gProtectionsHandler = TrackingProtection = browser = null;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+function hidden(sel) {
+ let win = browser.ownerGlobal;
+ let el = win.document.querySelector(sel);
+ let display = win.getComputedStyle(el).getPropertyValue("display", null);
+ return display === "none";
+}
+
+function protectionsPopupState() {
+ let win = browser.ownerGlobal;
+ return win.document.getElementById("protections-popup")?.state || "closed";
+}
+
+function clickButton(sel) {
+ let win = browser.ownerGlobal;
+ let el = win.document.querySelector(sel);
+ el.doCommand();
+}
+
+function testTrackingPage() {
+ info("Tracking content must be blocked");
+ ok(gProtectionsHandler.anyDetected, "trackers are detected");
+ ok(!gProtectionsHandler.hasException, "content shows no exception");
+
+ ok(
+ BrowserTestUtils.isVisible(gProtectionsHandler.iconBox),
+ "icon box is visible"
+ );
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active");
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ "icon box shows no exception"
+ );
+ is(
+ gProtectionsHandler._trackingProtectionIconTooltipLabel.getAttribute(
+ "data-l10n-id"
+ ),
+ "tracking-protection-icon-active",
+ "correct tooltip"
+ );
+}
+
+function testTrackingPageUnblocked() {
+ info("Tracking content must be allowlisted and not blocked");
+ ok(gProtectionsHandler.anyDetected, "trackers are detected");
+ ok(gProtectionsHandler.hasException, "content shows exception");
+
+ ok(!gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active");
+ ok(
+ gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ "shield shows exception"
+ );
+ is(
+ gProtectionsHandler._trackingProtectionIconTooltipLabel.getAttribute(
+ "data-l10n-id"
+ ),
+ "tracking-protection-icon-disabled",
+ "correct tooltip"
+ );
+
+ ok(
+ BrowserTestUtils.isVisible(gProtectionsHandler.iconBox),
+ "icon box is visible"
+ );
+}
+
+add_task(async function testExceptionAddition() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first_pbm", false]],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+ Services.prefs.setBoolPref(DTSCBN_PREF, true);
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ browser = privateWin.gBrowser;
+ let tab = await BrowserTestUtils.openNewForegroundTab(browser);
+
+ gProtectionsHandler = browser.ownerGlobal.gProtectionsHandler;
+ ok(gProtectionsHandler, "CB is attached to the private window");
+
+ TrackingProtection =
+ browser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the private window");
+
+ Services.prefs.setBoolPref(TP_PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ info("Load a test page containing tracking elements");
+ await Promise.all([
+ promiseTabLoadEvent(tab, TRACKING_PAGE),
+ waitForContentBlockingEvent(2, tab.ownerGlobal),
+ ]);
+
+ testTrackingPage(tab.ownerGlobal);
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ gProtectionsHandler.disableForCurrentPage();
+ is(protectionsPopupState(), "closed", "protections popup is closed");
+
+ await tabReloadPromise;
+ testTrackingPageUnblocked();
+
+ info(
+ "Test that the exception is remembered across tabs in the same private window"
+ );
+ tab = browser.selectedTab = BrowserTestUtils.addTab(browser);
+
+ info("Load a test page containing tracking elements");
+ await promiseTabLoadEvent(tab, TRACKING_PAGE);
+ testTrackingPageUnblocked();
+
+ await BrowserTestUtils.closeWindow(privateWin);
+});
+
+add_task(async function testExceptionPersistence() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first_pbm", false]],
+ });
+
+ info("Open another private browsing window");
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ browser = privateWin.gBrowser;
+ let tab = await BrowserTestUtils.openNewForegroundTab(browser);
+
+ gProtectionsHandler = browser.ownerGlobal.gProtectionsHandler;
+ ok(gProtectionsHandler, "CB is attached to the private window");
+ TrackingProtection =
+ browser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the private window");
+
+ ok(TrackingProtection.enabled, "TP is still enabled");
+
+ info("Load a test page containing tracking elements");
+ await Promise.all([
+ promiseTabLoadEvent(tab, TRACKING_PAGE),
+ waitForContentBlockingEvent(2, tab.ownerGlobal),
+ ]);
+
+ testTrackingPage(tab.ownerGlobal);
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ gProtectionsHandler.disableForCurrentPage();
+ is(protectionsPopupState(), "closed", "protections popup is closed");
+
+ await Promise.all([
+ tabReloadPromise,
+ waitForContentBlockingEvent(2, tab.ownerGlobal),
+ ]);
+ testTrackingPageUnblocked();
+
+ await BrowserTestUtils.closeWindow(privateWin);
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js
new file mode 100644
index 0000000000..2ae0d5c9d9
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js
@@ -0,0 +1,415 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+const BENIGN_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html";
+const COOKIE_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html";
+
+const CM_PREF = "privacy.trackingprotection.cryptomining.enabled";
+const FP_PREF = "privacy.trackingprotection.fingerprinting.enabled";
+const TP_PREF = "privacy.trackingprotection.enabled";
+const CB_PREF = "network.cookie.cookieBehavior";
+const GPC_PREF = "privacy.globalprivacycontrol.enabled";
+
+const PREF_REPORT_BREAKAGE_URL = "browser.contentblocking.reportBreakage.url";
+
+let { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+let { CommonUtils } = ChromeUtils.importESModule(
+ "resource://services-common/utils.sys.mjs"
+);
+let { Preferences } = ChromeUtils.importESModule(
+ "resource://gre/modules/Preferences.sys.mjs"
+);
+
+add_setup(async function () {
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ // Disable Report Broken Site, as it hides "Site not working?" when enabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [["ui.new-webcompat-reporter.enabled", false]],
+ });
+
+ registerCleanupFunction(() => {
+ // Clear prefs that are touched in this test again for sanity.
+ Services.prefs.clearUserPref(TP_PREF);
+ Services.prefs.clearUserPref(CB_PREF);
+ Services.prefs.clearUserPref(FP_PREF);
+ Services.prefs.clearUserPref(CM_PREF);
+ Services.prefs.clearUserPref(GPC_PREF);
+ Services.prefs.clearUserPref(PREF_REPORT_BREAKAGE_URL);
+
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "urlclassifier.features.fingerprinting.blacklistHosts",
+ "fingerprinting.example.com",
+ ],
+ [
+ "urlclassifier.features.fingerprinting.annotate.blacklistHosts",
+ "fingerprinting.example.com",
+ ],
+ ["privacy.trackingprotection.cryptomining.enabled", true],
+ [
+ "urlclassifier.features.cryptomining.blacklistHosts",
+ "cryptomining.example.com",
+ ],
+ [
+ "urlclassifier.features.cryptomining.annotate.blacklistHosts",
+ "cryptomining.example.com",
+ ],
+ ["privacy.globalprivacycontrol.enabled", true],
+ ],
+ });
+});
+
+add_task(async function testReportBreakageCancel() {
+ Services.prefs.setBoolPref(TP_PREF, true);
+
+ await BrowserTestUtils.withNewTab(TRACKING_PAGE, async function () {
+ await openProtectionsPanel();
+ await TestUtils.waitForCondition(() =>
+ gProtectionsHandler._protectionsPopup.hasAttribute("blocking")
+ );
+
+ let siteNotWorkingButton = document.getElementById(
+ "protections-popup-tp-switch-breakage-link"
+ );
+ ok(
+ BrowserTestUtils.isVisible(siteNotWorkingButton),
+ "site not working button is visible"
+ );
+ let siteNotWorkingView = document.getElementById(
+ "protections-popup-siteNotWorkingView"
+ );
+ let viewShown = BrowserTestUtils.waitForEvent(
+ siteNotWorkingView,
+ "ViewShown"
+ );
+ siteNotWorkingButton.click();
+ await viewShown;
+
+ let sendReportButton = document.getElementById(
+ "protections-popup-siteNotWorkingView-sendReport"
+ );
+ let sendReportView = document.getElementById(
+ "protections-popup-sendReportView"
+ );
+ viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown");
+ sendReportButton.click();
+ await viewShown;
+
+ ok(true, "Report breakage view was shown");
+
+ viewShown = BrowserTestUtils.waitForEvent(siteNotWorkingView, "ViewShown");
+ let cancelButton = document.getElementById(
+ "protections-popup-sendReportView-cancel"
+ );
+ cancelButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+ });
+
+ Services.prefs.clearUserPref(TP_PREF);
+});
+
+add_task(async function testReportBreakageSiteException() {
+ Services.prefs.setBoolPref(TP_PREF, true);
+
+ let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊";
+
+ await BrowserTestUtils.withNewTab(url, async browser => {
+ let loaded = BrowserTestUtils.browserLoaded(browser, false);
+ gProtectionsHandler.disableForCurrentPage();
+ await loaded;
+
+ await openProtectionsPanel();
+
+ let siteFixedButton = document.getElementById(
+ "protections-popup-tp-switch-breakage-fixed-link"
+ );
+ ok(
+ BrowserTestUtils.isVisible(siteFixedButton),
+ "site fixed button is visible"
+ );
+ let sendReportView = document.getElementById(
+ "protections-popup-sendReportView"
+ );
+ let viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown");
+ siteFixedButton.click();
+ await viewShown;
+
+ ok(true, "Report breakage view was shown");
+
+ await testReportBreakageSubmit(
+ TRACKING_PAGE,
+ "trackingprotection",
+ false,
+ true
+ );
+
+ // Pass false for shouldReload - there's no need since the tab is going away.
+ gProtectionsHandler.enableForCurrentPage(false);
+ });
+
+ Services.prefs.clearUserPref(TP_PREF);
+});
+
+add_task(async function testNoTracking() {
+ await BrowserTestUtils.withNewTab(BENIGN_PAGE, async function () {
+ await openProtectionsPanel();
+
+ let siteNotWorkingButton = document.getElementById(
+ "protections-popup-tp-switch-breakage-link"
+ );
+ ok(
+ BrowserTestUtils.isHidden(siteNotWorkingButton),
+ "site not working button is not visible"
+ );
+ });
+});
+
+add_task(async function testReportBreakageError() {
+ Services.prefs.setBoolPref(TP_PREF, true);
+ // Make sure that we correctly strip the query.
+ let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊";
+ await BrowserTestUtils.withNewTab(url, async function () {
+ await openAndTestReportBreakage(TRACKING_PAGE, "trackingprotection", true);
+ });
+
+ Services.prefs.clearUserPref(TP_PREF);
+});
+
+add_task(async function testTP() {
+ Services.prefs.setBoolPref(TP_PREF, true);
+ // Make sure that we correctly strip the query.
+ let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊";
+ await BrowserTestUtils.withNewTab(url, async function () {
+ await openAndTestReportBreakage(TRACKING_PAGE, "trackingprotection");
+ });
+
+ Services.prefs.clearUserPref(TP_PREF);
+});
+
+add_task(async function testCR() {
+ Services.prefs.setIntPref(
+ CB_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ // Make sure that we correctly strip the query.
+ let url = COOKIE_PAGE + "?a=b&1=abc&unicode=🦊";
+ await BrowserTestUtils.withNewTab(url, async function () {
+ await openAndTestReportBreakage(COOKIE_PAGE, "cookierestrictions");
+ });
+
+ Services.prefs.clearUserPref(CB_PREF);
+});
+
+add_task(async function testFP() {
+ Services.prefs.setIntPref(CB_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
+ Services.prefs.setBoolPref(FP_PREF, true);
+ // Make sure that we correctly strip the query.
+ let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊";
+ await BrowserTestUtils.withNewTab(url, async function (browser) {
+ let promise = waitForContentBlockingEvent();
+ await SpecialPowers.spawn(browser, [], function () {
+ content.postMessage("fingerprinting", "*");
+ });
+ await promise;
+
+ await openAndTestReportBreakage(TRACKING_PAGE, "fingerprinting", true);
+ });
+
+ Services.prefs.clearUserPref(FP_PREF);
+ Services.prefs.clearUserPref(CB_PREF);
+});
+
+add_task(async function testCM() {
+ Services.prefs.setIntPref(CB_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
+ Services.prefs.setBoolPref(CM_PREF, true);
+ // Make sure that we correctly strip the query.
+ let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊";
+ await BrowserTestUtils.withNewTab(url, async function (browser) {
+ let promise = waitForContentBlockingEvent();
+ await SpecialPowers.spawn(browser, [], function () {
+ content.postMessage("cryptomining", "*");
+ });
+ await promise;
+
+ await openAndTestReportBreakage(TRACKING_PAGE, "cryptomining", true);
+ });
+
+ Services.prefs.clearUserPref(CM_PREF);
+ Services.prefs.clearUserPref(CB_PREF);
+});
+
+async function openAndTestReportBreakage(url, tags, error = false) {
+ await openProtectionsPanel();
+
+ let siteNotWorkingButton = document.getElementById(
+ "protections-popup-tp-switch-breakage-link"
+ );
+ ok(
+ BrowserTestUtils.isVisible(siteNotWorkingButton),
+ "site not working button is visible"
+ );
+ let siteNotWorkingView = document.getElementById(
+ "protections-popup-siteNotWorkingView"
+ );
+ let viewShown = BrowserTestUtils.waitForEvent(
+ siteNotWorkingView,
+ "ViewShown"
+ );
+ siteNotWorkingButton.click();
+ await viewShown;
+
+ let sendReportButton = document.getElementById(
+ "protections-popup-siteNotWorkingView-sendReport"
+ );
+ let sendReportView = document.getElementById(
+ "protections-popup-sendReportView"
+ );
+ viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown");
+ sendReportButton.click();
+ await viewShown;
+
+ ok(true, "Report breakage view was shown");
+
+ await testReportBreakageSubmit(url, tags, error, false);
+}
+
+// This function assumes that the breakage report view is ready.
+async function testReportBreakageSubmit(url, tags, error, hasException) {
+ // Setup a mock server for receiving breakage reports.
+ let server = new HttpServer();
+ server.start(-1);
+ let i = server.identity;
+ let path =
+ i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort + "/";
+
+ Services.prefs.setStringPref(PREF_REPORT_BREAKAGE_URL, path);
+
+ let comments = document.getElementById(
+ "protections-popup-sendReportView-collection-comments"
+ );
+ is(comments.value, "", "Comments textarea should initially be empty");
+
+ let submitButton = document.getElementById(
+ "protections-popup-sendReportView-submit"
+ );
+ let reportURL = document.getElementById(
+ "protections-popup-sendReportView-collection-url"
+ ).value;
+
+ is(reportURL, url, "Shows the correct URL in the report UI.");
+
+ // Make sure that sending the report closes the identity popup.
+ let popuphidden = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+
+ // Check that we're receiving a good report.
+ await new Promise(resolve => {
+ server.registerPathHandler("/", async (request, response) => {
+ is(request.method, "POST", "request was a post");
+
+ // Extract and "parse" the form data in the request body.
+ let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
+ let boundary = request
+ .getHeader("Content-Type")
+ .match(/boundary=-+([^-]*)/i)[1];
+ let regex = new RegExp("-+" + boundary + "-*\\s+");
+ let sections = body.split(regex);
+
+ let prefs = [
+ "privacy.trackingprotection.enabled",
+ "privacy.trackingprotection.pbmode.enabled",
+ "urlclassifier.trackingTable",
+ "network.http.referer.defaultPolicy",
+ "network.http.referer.defaultPolicy.pbmode",
+ "network.cookie.cookieBehavior",
+ "privacy.annotate_channels.strict_list.enabled",
+ "privacy.restrict3rdpartystorage.expiration",
+ "privacy.trackingprotection.fingerprinting.enabled",
+ "privacy.trackingprotection.cryptomining.enabled",
+ "privacy.globalprivacycontrol.enabled",
+ ];
+ let prefsBody = "";
+
+ for (let pref of prefs) {
+ prefsBody += `${pref}: ${Preferences.get(pref)}\r\n`;
+ }
+
+ Assert.deepEqual(
+ sections,
+ [
+ "",
+ `Content-Disposition: form-data; name=\"title\"\r\n\r\n${
+ Services.io.newURI(reportURL).host
+ }\r\n`,
+ 'Content-Disposition: form-data; name="body"\r\n\r\n' +
+ `Full URL: ${reportURL + "?"}\r\n` +
+ `userAgent: ${navigator.userAgent}\r\n\r\n` +
+ "**Preferences**\r\n" +
+ `${prefsBody}\r\n` +
+ `hasException: ${hasException}\r\n\r\n` +
+ "**Comments**\r\n" +
+ "This is a comment\r\n",
+ 'Content-Disposition: form-data; name="labels"\r\n\r\n' +
+ `${hasException ? "" : tags}\r\n`,
+ "",
+ ],
+ "Should send the correct form data"
+ );
+
+ if (error) {
+ response.setStatusLine(request.httpVersion, 500, "Request failed");
+ } else {
+ response.setStatusLine(request.httpVersion, 201, "Entry created");
+ }
+
+ resolve();
+ });
+
+ comments.value = "This is a comment";
+ submitButton.click();
+ });
+
+ let errorMessage = document.getElementById(
+ "protections-popup-sendReportView-report-error"
+ );
+ if (error) {
+ await TestUtils.waitForCondition(() =>
+ BrowserTestUtils.isVisible(errorMessage)
+ );
+ is(
+ comments.value,
+ "This is a comment",
+ "Comment not cleared in case of an error"
+ );
+ gProtectionsHandler._protectionsPopup.hidePopup();
+ } else {
+ ok(BrowserTestUtils.isHidden(errorMessage), "Error message not shown");
+ }
+
+ await popuphidden;
+
+ // Stop the server.
+ await new Promise(r => server.stop(r));
+
+ Services.prefs.clearUserPref(PREF_REPORT_BREAKAGE_URL);
+}
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_shield_visibility.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_shield_visibility.js
new file mode 100644
index 0000000000..d4addd380d
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_shield_visibility.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This test checks pages of different URL variants (mostly differing in scheme)
+ * and verifies that the shield is only shown when content blocking can deal
+ * with the specific variant. */
+
+const TEST_CASES = [
+ {
+ type: "http",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ testURL: "http://example.com",
+ hidden: false,
+ },
+ {
+ type: "https",
+ testURL: "https://example.com",
+ hidden: false,
+ },
+ {
+ type: "chrome page",
+ testURL: "chrome://global/skin/in-content/info-pages.css",
+ hidden: true,
+ },
+ {
+ type: "content-privileged about page",
+ testURL: "about:robots",
+ hidden: true,
+ },
+ {
+ type: "non-chrome about page",
+ testURL: "about:about",
+ hidden: true,
+ },
+ {
+ type: "chrome about page",
+ testURL: "about:preferences",
+ hidden: true,
+ },
+ {
+ type: "file",
+ testURL: "benignPage.html",
+ hidden: true,
+ },
+ {
+ type: "certificateError",
+ testURL: "https://self-signed.example.com",
+ hidden: true,
+ },
+ {
+ type: "localhost",
+ testURL: "http://127.0.0.1",
+ hidden: false,
+ },
+ {
+ type: "data URI",
+ testURL: "data:text/html,<div>",
+ hidden: true,
+ },
+ {
+ type: "view-source HTTP",
+ testURL: "view-source:http://example.com/",
+ hidden: true,
+ },
+ {
+ type: "view-source HTTPS",
+ testURL: "view-source:https://example.com/",
+ hidden: true,
+ },
+ {
+ type: "top level sandbox",
+ testURL:
+ "https://example.com/browser/browser/base/content/test/protectionsUI/sandboxed.html",
+ hidden: false,
+ },
+];
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // By default, proxies don't apply to 127.0.0.1. We need them to for this test, though:
+ ["network.proxy.allow_hijacking_localhost", true],
+ ],
+ });
+
+ for (let testData of TEST_CASES) {
+ info(`Testing for ${testData.type}`);
+ let testURL = testData.testURL;
+
+ // Overwrite the url if it is testing the file url.
+ if (testData.type === "file") {
+ let dir = getChromeDir(getResolvedURI(gTestPath));
+ dir.append(testURL);
+ dir.normalize();
+ testURL = Services.io.newFileURI(dir).spec;
+ }
+
+ let pageLoaded;
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, testURL);
+ let browser = gBrowser.selectedBrowser;
+ if (testData.type === "certificateError") {
+ pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ } else {
+ pageLoaded = BrowserTestUtils.browserLoaded(browser, true);
+ }
+ },
+ false
+ );
+ await pageLoaded;
+
+ is(
+ BrowserTestUtils.isHidden(
+ gProtectionsHandler._trackingProtectionIconContainer
+ ),
+ testData.hidden,
+ "tracking protection icon container is correctly displayed"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js
new file mode 100644
index 0000000000..3f579b0280
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js
@@ -0,0 +1,321 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+
+const ST_PROTECTION_PREF = "privacy.trackingprotection.socialtracking.enabled";
+const ST_BLOCK_COOKIES_PREF = "privacy.socialtracking.block_cookies.enabled";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [ST_BLOCK_COOKIES_PREF, true],
+ [
+ "urlclassifier.features.socialtracking.blacklistHosts",
+ "social-tracking.example.org",
+ ],
+ [
+ "urlclassifier.features.socialtracking.annotate.blacklistHosts",
+ "social-tracking.example.org",
+ ],
+ // Whitelist trackertest.org loaded by default in trackingPage.html
+ ["urlclassifier.trackingSkipURLs", "trackertest.org"],
+ ["urlclassifier.trackingAnnotationSkipURLs", "trackertest.org"],
+ ["privacy.trackingprotection.enabled", false],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ],
+ });
+});
+
+async function testIdentityState(hasException) {
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.disableForCurrentPage();
+ await loaded;
+ }
+
+ await openProtectionsPanel();
+ let categoryItem = document.getElementById(
+ "protections-popup-category-socialblock"
+ );
+
+ ok(
+ categoryItem.classList.contains("notFound"),
+ "socialtrackings are not detected"
+ );
+
+ ok(
+ BrowserTestUtils.isVisible(gProtectionsHandler.iconBox),
+ "icon box is visible regardless the exception"
+ );
+ await closeProtectionsPanel();
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("socialtracking", "*");
+ });
+ await openProtectionsPanel();
+
+ await TestUtils.waitForCondition(() => {
+ return !categoryItem.classList.contains("notFound");
+ });
+
+ ok(
+ gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "trackers are detected"
+ );
+ ok(
+ !categoryItem.classList.contains("notFound"),
+ "social trackers are detected"
+ );
+ ok(
+ BrowserTestUtils.isVisible(gProtectionsHandler.iconBox),
+ "icon box is visible"
+ );
+ is(
+ gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ hasException,
+ "Shows an exception when appropriate"
+ );
+ await closeProtectionsPanel();
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.enableForCurrentPage();
+ await loaded;
+ }
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testSubview(hasException) {
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.disableForCurrentPage();
+ await loaded;
+ }
+
+ promise = waitForContentBlockingEvent();
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("socialtracking", "*");
+ });
+ await promise;
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-socialblock"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "STP category item is visible");
+ ok(
+ categoryItem.classList.contains("blocked"),
+ "STP category item is blocked"
+ );
+
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ // We have to wait until the ContentBlockingLog gets updated in the content.
+ // Unfortunately, we need to use the setTimeout here since we don't have an
+ // easy to know whether the log is updated in the content. This should be
+ // removed after the log been removed in the content (Bug 1599046).
+ await new Promise(resolve => {
+ setTimeout(resolve, 500);
+ });
+ /* eslint-enable mozilla/no-arbitrary-setTimeout */
+
+ let subview = document.getElementById("protections-popup-socialblockView");
+ let viewShown = BrowserTestUtils.waitForEvent(subview, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ let trackersViewShimHint = document.getElementById(
+ "protections-popup-socialblockView-shim-allow-hint"
+ );
+ ok(trackersViewShimHint.hidden, "Shim hint is hidden");
+
+ let listItems = subview.querySelectorAll(".protections-popup-list-item");
+ is(listItems.length, 1, "We have 1 item in the list");
+ let listItem = listItems[0];
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+ is(
+ listItem.querySelector("label").value,
+ "https://social-tracking.example.org",
+ "Has the correct host"
+ );
+
+ let mainView = document.getElementById("protections-popup-mainView");
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ let backButton = subview.querySelector(".subviewbutton-back");
+ backButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+
+ if (hasException) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ TRACKING_PAGE
+ );
+ gProtectionsHandler.enableForCurrentPage();
+ await loaded;
+ }
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testCategoryItem(blockLoads) {
+ if (blockLoads) {
+ Services.prefs.setBoolPref(ST_PROTECTION_PREF, true);
+ }
+
+ Services.prefs.setBoolPref(ST_BLOCK_COOKIES_PREF, false);
+
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-socialblock"
+ );
+
+ let noTrackersDetectedDesc = document.getElementById(
+ "protections-popup-no-trackers-found-description"
+ );
+
+ ok(categoryItem.hasAttribute("uidisabled"), "Category should be uidisabled");
+
+ ok(
+ !categoryItem.classList.contains("blocked"),
+ "Category not marked as blocked"
+ );
+ ok(!BrowserTestUtils.isVisible(categoryItem), "Item should be hidden");
+ ok(
+ !gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "trackers are not detected"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("socialtracking", "*");
+ });
+
+ ok(
+ !categoryItem.classList.contains("blocked"),
+ "Category not marked as blocked"
+ );
+ ok(!BrowserTestUtils.isVisible(categoryItem), "Item should be hidden");
+ ok(
+ !gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "trackers are not detected"
+ );
+ ok(
+ BrowserTestUtils.isVisible(noTrackersDetectedDesc),
+ "No Trackers detected should be shown"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+
+ Services.prefs.setBoolPref(ST_BLOCK_COOKIES_PREF, true);
+
+ promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ await openProtectionsPanel();
+
+ ok(!categoryItem.hasAttribute("uidisabled"), "Item shouldn't be uidisabled");
+
+ ok(categoryItem.classList.contains("blocked"), "Category marked as blocked");
+ ok(
+ categoryItem.classList.contains("notFound"),
+ "Category marked as not found"
+ );
+ // At this point we should still be showing "No Trackers Detected"
+ ok(!BrowserTestUtils.isVisible(categoryItem), "Item should not be visible");
+ ok(
+ BrowserTestUtils.isVisible(noTrackersDetectedDesc),
+ "No Trackers detected should be shown"
+ );
+ ok(
+ !gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "trackers are not detected"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("socialtracking", "*");
+ });
+
+ await TestUtils.waitForCondition(() => {
+ return !categoryItem.classList.contains("notFound");
+ });
+
+ ok(categoryItem.classList.contains("blocked"), "Category marked as blocked");
+ ok(
+ !categoryItem.classList.contains("notFound"),
+ "Category not marked as not found"
+ );
+ ok(BrowserTestUtils.isVisible(categoryItem), "Item should be visible");
+ ok(
+ !BrowserTestUtils.isVisible(noTrackersDetectedDesc),
+ "No Trackers detected should be hidden"
+ );
+ ok(
+ gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "trackers are not detected"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+
+ Services.prefs.clearUserPref(ST_PROTECTION_PREF);
+}
+
+add_task(async function testIdentityUI() {
+ requestLongerTimeout(2);
+
+ await testIdentityState(false);
+ await testIdentityState(true);
+
+ await testSubview(false);
+ await testSubview(true);
+
+ await testCategoryItem(false);
+ await testCategoryItem(true);
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js
new file mode 100644
index 0000000000..126328914e
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js
@@ -0,0 +1,403 @@
+/*
+ * Test that the Tracking Protection section is visible in the Control Center
+ * and has the correct state for the cases when:
+ *
+ * In a normal window as well as a private window,
+ * With TP enabled
+ * 1) A page with no tracking elements is loaded.
+ * 2) A page with tracking elements is loaded and they are blocked.
+ * 3) A page with tracking elements is loaded and they are not blocked.
+ * With TP disabled
+ * 1) A page with no tracking elements is loaded.
+ * 2) A page with tracking elements is loaded.
+ *
+ * See also Bugs 1175327, 1043801, 1178985
+ */
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const APS_PREF =
+ "privacy.partition.always_partition_third_party_non_cookie_storage";
+const TPC_PREF = "network.cookie.cookieBehavior";
+const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
+const BENIGN_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html";
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+const COOKIE_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html";
+var gProtectionsHandler = null;
+var TrackingProtection = null;
+var ThirdPartyCookies = null;
+var tabbrowser = null;
+var gTrackingPageURL = TRACKING_PAGE;
+
+registerCleanupFunction(function () {
+ TrackingProtection =
+ gProtectionsHandler =
+ ThirdPartyCookies =
+ tabbrowser =
+ null;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref(TP_PREF);
+ Services.prefs.clearUserPref(TP_PB_PREF);
+ Services.prefs.clearUserPref(TPC_PREF);
+ Services.prefs.clearUserPref(DTSCBN_PREF);
+});
+
+function notFound(id) {
+ let doc = tabbrowser.ownerGlobal.document;
+ return doc.getElementById(id).classList.contains("notFound");
+}
+
+async function testBenignPage() {
+ info("Non-tracking content must not be blocked");
+ ok(!gProtectionsHandler.anyDetected, "no trackers are detected");
+ ok(!gProtectionsHandler.hasException, "content shows no exception");
+
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("active"),
+ "shield is not active"
+ );
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ "icon box shows no exception"
+ );
+ is(
+ gProtectionsHandler._trackingProtectionIconTooltipLabel.getAttribute(
+ "data-l10n-id"
+ ),
+ "tracking-protection-icon-no-trackers-detected",
+ "correct tooltip"
+ );
+ ok(
+ BrowserTestUtils.isVisible(gProtectionsHandler.iconBox),
+ "icon box is visible"
+ );
+
+ let win = tabbrowser.ownerGlobal;
+ await openProtectionsPanel(false, win);
+ ok(
+ notFound("protections-popup-category-cookies"),
+ "Cookie restrictions category is not found"
+ );
+ ok(
+ notFound("protections-popup-category-trackers"),
+ "Trackers category is not found"
+ );
+ await closeProtectionsPanel(win);
+}
+
+async function testBenignPageWithException() {
+ info("Non-tracking content must not be blocked");
+ ok(!gProtectionsHandler.anyDetected, "no trackers are detected");
+ ok(gProtectionsHandler.hasException, "content shows exception");
+
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("active"),
+ "shield is not active"
+ );
+ ok(
+ gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ "shield shows exception"
+ );
+ is(
+ gProtectionsHandler._trackingProtectionIconTooltipLabel.getAttribute(
+ "data-l10n-id"
+ ),
+ "tracking-protection-icon-disabled",
+ "correct tooltip"
+ );
+
+ ok(
+ !BrowserTestUtils.isHidden(gProtectionsHandler.iconBox),
+ "icon box is not hidden"
+ );
+
+ let win = tabbrowser.ownerGlobal;
+ await openProtectionsPanel(false, win);
+ ok(
+ notFound("protections-popup-category-cookies"),
+ "Cookie restrictions category is not found"
+ );
+ ok(
+ notFound("protections-popup-category-trackers"),
+ "Trackers category is not found"
+ );
+ await closeProtectionsPanel(win);
+}
+
+function areTrackersBlocked(isPrivateBrowsing) {
+ let blockedByTP = Services.prefs.getBoolPref(
+ isPrivateBrowsing ? TP_PB_PREF : TP_PREF
+ );
+ let blockedByTPC = [
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ].includes(Services.prefs.getIntPref(TPC_PREF));
+ return blockedByTP || blockedByTPC;
+}
+
+async function testTrackingPage(window) {
+ info("Tracking content must be blocked");
+ ok(gProtectionsHandler.anyDetected, "trackers are detected");
+ ok(!gProtectionsHandler.hasException, "content shows no exception");
+
+ let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+ let blockedByTP = areTrackersBlocked(isWindowPrivate);
+ ok(
+ BrowserTestUtils.isVisible(gProtectionsHandler.iconBox),
+ "icon box is always visible"
+ );
+ is(
+ gProtectionsHandler.iconBox.hasAttribute("active"),
+ blockedByTP,
+ "shield is" + (blockedByTP ? "" : " not") + " active"
+ );
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ "icon box shows no exception"
+ );
+ is(
+ gProtectionsHandler._trackingProtectionIconTooltipLabel.getAttribute(
+ "data-l10n-id"
+ ),
+ blockedByTP
+ ? "tracking-protection-icon-active"
+ : "tracking-protection-icon-no-trackers-detected",
+ "correct tooltip"
+ );
+
+ await openProtectionsPanel(false, window);
+ ok(
+ !notFound("protections-popup-category-trackers"),
+ "Trackers category is detected"
+ );
+ if (gTrackingPageURL == COOKIE_PAGE) {
+ ok(
+ !notFound("protections-popup-category-cookies"),
+ "Cookie restrictions category is detected"
+ );
+ } else {
+ ok(
+ notFound("protections-popup-category-cookies"),
+ "Cookie restrictions category is not found"
+ );
+ }
+ await closeProtectionsPanel(window);
+}
+
+async function testTrackingPageUnblocked(blockedByTP, window) {
+ info("Tracking content must be in the exception list and not blocked");
+ ok(gProtectionsHandler.anyDetected, "trackers are detected");
+ ok(gProtectionsHandler.hasException, "content shows exception");
+
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("active"),
+ "shield is not active"
+ );
+ ok(
+ gProtectionsHandler.iconBox.hasAttribute("hasException"),
+ "shield shows exception"
+ );
+ is(
+ gProtectionsHandler._trackingProtectionIconTooltipLabel.getAttribute(
+ "data-l10n-id"
+ ),
+ "tracking-protection-icon-disabled",
+ "correct tooltip"
+ );
+
+ ok(
+ BrowserTestUtils.isVisible(gProtectionsHandler.iconBox),
+ "icon box is visible"
+ );
+
+ await openProtectionsPanel(false, window);
+ ok(
+ !notFound("protections-popup-category-trackers"),
+ "Trackers category is detected"
+ );
+ if (gTrackingPageURL == COOKIE_PAGE) {
+ ok(
+ !notFound("protections-popup-category-cookies"),
+ "Cookie restrictions category is detected"
+ );
+ } else {
+ ok(
+ notFound("protections-popup-category-cookies"),
+ "Cookie restrictions category is not found"
+ );
+ }
+ await closeProtectionsPanel(window);
+}
+
+async function testContentBlocking(tab) {
+ info("Testing with Tracking Protection ENABLED.");
+
+ info("Load a test page not containing tracking elements");
+ await promiseTabLoadEvent(tab, BENIGN_PAGE);
+ await testBenignPage();
+
+ info(
+ "Load a test page not containing tracking elements which has an exception."
+ );
+
+ await promiseTabLoadEvent(tab, "https://example.org/?round=1");
+
+ ContentBlockingAllowList.add(tab.linkedBrowser);
+ // Load another page from the same origin to ensure there is an onlocationchange
+ // notification which would trigger an oncontentblocking notification for us.
+ await promiseTabLoadEvent(tab, "https://example.org/?round=2");
+
+ await testBenignPageWithException();
+
+ ContentBlockingAllowList.remove(tab.linkedBrowser);
+
+ info("Load a test page containing tracking elements");
+ await promiseTabLoadEvent(tab, gTrackingPageURL);
+ await testTrackingPage(tab.ownerGlobal);
+
+ info("Disable CB for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ tab.ownerGlobal.gProtectionsHandler.disableForCurrentPage();
+ await tabReloadPromise;
+ let isPrivateBrowsing = PrivateBrowsingUtils.isWindowPrivate(tab.ownerGlobal);
+ let blockedByTP = areTrackersBlocked(isPrivateBrowsing);
+ await testTrackingPageUnblocked(blockedByTP, tab.ownerGlobal);
+
+ info("Re-enable TP for the page (which reloads the page)");
+ tabReloadPromise = promiseTabLoadEvent(tab);
+ tab.ownerGlobal.gProtectionsHandler.enableForCurrentPage();
+ await tabReloadPromise;
+ await testTrackingPage(tab.ownerGlobal);
+}
+
+add_task(async function testNormalBrowsing() {
+ await SpecialPowers.pushPrefEnv({ set: [[APS_PREF, false]] });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ Services.prefs.setBoolPref(DTSCBN_PREF, true);
+
+ tabbrowser = gBrowser;
+ let tab = (tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser));
+
+ gProtectionsHandler = gBrowser.ownerGlobal.gProtectionsHandler;
+ ok(
+ gProtectionsHandler,
+ "gProtectionsHandler is attached to the browser window"
+ );
+
+ TrackingProtection =
+ gBrowser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+ is(
+ TrackingProtection.enabled,
+ Services.prefs.getBoolPref(TP_PREF),
+ "TP.enabled is based on the original pref value"
+ );
+
+ Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
+
+ await testContentBlocking(tab);
+
+ Services.prefs.setBoolPref(TP_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ await testContentBlocking(tab);
+
+ gBrowser.removeCurrentTab();
+
+ Services.prefs.clearUserPref(TPC_PREF);
+});
+
+add_task(async function testPrivateBrowsing() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_first_pbm", false],
+ [APS_PREF, false],
+ ],
+ });
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ tabbrowser = privateWin.gBrowser;
+ let tab = (tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser));
+
+ // Set the normal mode pref to false to check the pbmode pref.
+ Services.prefs.setBoolPref(TP_PREF, false);
+
+ Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
+
+ gProtectionsHandler = tabbrowser.ownerGlobal.gProtectionsHandler;
+ ok(
+ gProtectionsHandler,
+ "gProtectionsHandler is attached to the private window"
+ );
+
+ TrackingProtection =
+ tabbrowser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the private window");
+ is(
+ TrackingProtection.enabled,
+ Services.prefs.getBoolPref(TP_PB_PREF),
+ "TP.enabled is based on the pb pref value"
+ );
+
+ await testContentBlocking(tab);
+
+ Services.prefs.setBoolPref(TP_PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ await testContentBlocking(tab);
+
+ privateWin.close();
+
+ Services.prefs.clearUserPref(TPC_PREF);
+});
+
+add_task(async function testThirdPartyCookies() {
+ requestLongerTimeout(3);
+
+ await SpecialPowers.pushPrefEnv({ set: [[APS_PREF, false]] });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+ gTrackingPageURL = COOKIE_PAGE;
+
+ tabbrowser = gBrowser;
+ let tab = (tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser));
+
+ gProtectionsHandler = gBrowser.ownerGlobal.gProtectionsHandler;
+ ok(
+ gProtectionsHandler,
+ "gProtectionsHandler is attached to the browser window"
+ );
+ ThirdPartyCookies =
+ gBrowser.ownerGlobal.gProtectionsHandler.blockers.ThirdPartyCookies;
+ ok(ThirdPartyCookies, "TP is attached to the browser window");
+ is(
+ ThirdPartyCookies.enabled,
+ [
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
+ ].includes(Services.prefs.getIntPref(TPC_PREF)),
+ "TPC.enabled is based on the original pref value"
+ );
+
+ await testContentBlocking(tab);
+
+ Services.prefs.setIntPref(
+ TPC_PREF,
+ Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
+ );
+ ok(ThirdPartyCookies.enabled, "TPC is enabled after setting the pref");
+
+ await testContentBlocking(tab);
+
+ Services.prefs.clearUserPref(TPC_PREF);
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_state_reset.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_state_reset.js
new file mode 100644
index 0000000000..020733cc72
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_state_reset.js
@@ -0,0 +1,129 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+const BENIGN_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html";
+const ABOUT_PAGE = "about:preferences";
+
+/* This asserts that the content blocking event state is correctly reset
+ * when navigating to a new location, and that the user is correctly
+ * reset when switching between tabs. */
+
+add_task(async function testResetOnLocationChange() {
+ Services.prefs.setBoolPref(TP_PREF, true);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, BENIGN_PAGE);
+ let browser = tab.linkedBrowser;
+
+ is(
+ browser.getContentBlockingEvents(),
+ 0,
+ "Benign page has no content blocking event"
+ );
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("active"),
+ "shield is not active"
+ );
+
+ await Promise.all([
+ promiseTabLoadEvent(tab, TRACKING_PAGE),
+ waitForContentBlockingEvent(2),
+ ]);
+
+ is(
+ browser.getContentBlockingEvents(),
+ Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT,
+ "Tracking page has a content blocking event"
+ );
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active");
+
+ await promiseTabLoadEvent(tab, BENIGN_PAGE);
+
+ is(
+ browser.getContentBlockingEvents(),
+ 0,
+ "Benign page has no content blocking event"
+ );
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("active"),
+ "shield is not active"
+ );
+
+ let contentBlockingEvent = waitForContentBlockingEvent(3);
+ let trackingTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TRACKING_PAGE
+ );
+ await contentBlockingEvent;
+
+ is(
+ trackingTab.linkedBrowser.getContentBlockingEvents(),
+ Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT,
+ "Tracking page has a content blocking event"
+ );
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active");
+
+ gBrowser.selectedTab = tab;
+ is(
+ browser.getContentBlockingEvents(),
+ 0,
+ "Benign page has no content blocking event"
+ );
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("active"),
+ "shield is not active"
+ );
+
+ gBrowser.removeTab(trackingTab);
+ gBrowser.removeTab(tab);
+
+ Services.prefs.clearUserPref(TP_PREF);
+});
+
+/* Test that the content blocking icon is correctly reset
+ * when changing tabs or navigating to an about: page */
+add_task(async function testResetOnTabChange() {
+ Services.prefs.setBoolPref(TP_PREF, true);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, ABOUT_PAGE);
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("active"),
+ "shield is not active"
+ );
+
+ await Promise.all([
+ promiseTabLoadEvent(tab, TRACKING_PAGE),
+ waitForContentBlockingEvent(3),
+ ]);
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active");
+
+ await promiseTabLoadEvent(tab, ABOUT_PAGE);
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("active"),
+ "shield is not active"
+ );
+
+ let contentBlockingEvent = waitForContentBlockingEvent(3);
+ let trackingTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TRACKING_PAGE
+ );
+ await contentBlockingEvent;
+ ok(gProtectionsHandler.iconBox.hasAttribute("active"), "shield is active");
+
+ gBrowser.selectedTab = tab;
+ ok(
+ !gProtectionsHandler.iconBox.hasAttribute("active"),
+ "shield is not active"
+ );
+
+ gBrowser.removeTab(trackingTab);
+ gBrowser.removeTab(tab);
+
+ Services.prefs.clearUserPref(TP_PREF);
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js
new file mode 100644
index 0000000000..186c3b36ce
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js
@@ -0,0 +1,400 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests the warning and list indicators that are shown in the protections panel
+ * subview when a tracking channel is allowed via the
+ * "urlclassifier-before-block-channel" event.
+ */
+
+// Choose origin so that all tracking origins used are third-parties.
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.net/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.trackingprotection.enabled", true],
+ ["privacy.trackingprotection.annotate_channels", true],
+ ["privacy.trackingprotection.cryptomining.enabled", true],
+ ["privacy.trackingprotection.socialtracking.enabled", true],
+ ["privacy.trackingprotection.fingerprinting.enabled", true],
+ ["privacy.socialtracking.block_cookies.enabled", true],
+ // Allowlist trackertest.org loaded by default in trackingPage.html
+ ["urlclassifier.trackingSkipURLs", "trackertest.org"],
+ ["urlclassifier.trackingAnnotationSkipURLs", "trackertest.org"],
+ // Additional denylisted hosts.
+ [
+ "urlclassifier.trackingAnnotationTable.testEntries",
+ "tracking.example.com",
+ ],
+ [
+ "urlclassifier.features.cryptomining.blacklistHosts",
+ "cryptomining.example.com",
+ ],
+ [
+ "urlclassifier.features.cryptomining.annotate.blacklistHosts",
+ "cryptomining.example.com",
+ ],
+ [
+ "urlclassifier.features.fingerprinting.blacklistHosts",
+ "fingerprinting.example.com",
+ ],
+ [
+ "urlclassifier.features.fingerprinting.annotate.blacklistHosts",
+ "fingerprinting.example.com",
+ ],
+ ],
+ });
+
+ await UrlClassifierTestUtils.addTestTrackers();
+ registerCleanupFunction(() => {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+});
+
+async function assertSubViewState(category, expectedState) {
+ await openProtectionsPanel();
+
+ // Sort the expected state by origin and transform it into an array.
+ let expectedStateSorted = Object.keys(expectedState)
+ .sort()
+ .reduce((stateArr, key) => {
+ let obj = expectedState[key];
+ obj.origin = key;
+ stateArr.push(obj);
+ return stateArr;
+ }, []);
+
+ if (!expectedStateSorted.length) {
+ ok(
+ BrowserTestUtils.isVisible(
+ document.getElementById(
+ "protections-popup-no-trackers-found-description"
+ )
+ ),
+ "No Trackers detected should be shown"
+ );
+ return;
+ }
+
+ let categoryItem = document.getElementById(
+ `protections-popup-category-${category}`
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(
+ BrowserTestUtils.isVisible(categoryItem),
+ `${category} category item is visible`
+ );
+
+ ok(!categoryItem.disabled, `${category} category item is enabled`);
+
+ let subView = document.getElementById(`protections-popup-${category}View`);
+ let viewShown = BrowserTestUtils.waitForEvent(subView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, `${category} subView was shown`);
+
+ info("Testing tracker list");
+
+ // Get the listed trackers in the UI and sort them by origin.
+ let items = Array.from(
+ subView.querySelectorAll(
+ `#protections-popup-${category}View-list .protections-popup-list-item`
+ )
+ ).sort((a, b) => {
+ let originA = a.querySelector("label").value;
+ let originB = b.querySelector("label").value;
+ return originA.localeCompare(originB);
+ });
+
+ is(
+ items.length,
+ expectedStateSorted.length,
+ "List has expected amount of entries"
+ );
+
+ for (let i = 0; i < expectedStateSorted.length; i += 1) {
+ let expected = expectedStateSorted[i];
+ let item = items[i];
+
+ let label = item.querySelector(".protections-popup-list-host-label");
+ ok(label, "Item has label.");
+ is(label.tooltipText, expected.origin, "Label has correct tooltip.");
+ is(label.value, expected.origin, "Label has correct text.");
+
+ is(
+ item.classList.contains("allowed"),
+ !expected.block,
+ "Item has allowed class if tracker is not blocked"
+ );
+
+ let shimAllowIndicator = item.querySelector(
+ ".protections-popup-list-host-shim-allow-indicator"
+ );
+
+ if (expected.shimAllow) {
+ is(item.childNodes.length, 2, "Item has two childNodes.");
+ ok(shimAllowIndicator, "Item has shim allow indicator icon.");
+ ok(
+ shimAllowIndicator.tooltipText,
+ "Shim allow indicator icon has tooltip text"
+ );
+ } else {
+ is(item.childNodes.length, 1, "Item has one childNode.");
+ ok(!shimAllowIndicator, "Item does not have shim allow indicator icon.");
+ }
+ }
+
+ let shimAllowSection = document.getElementById(
+ `protections-popup-${category}View-shim-allow-hint`
+ );
+ ok(shimAllowSection, `Category ${category} has shim-allow hint.`);
+
+ if (Object.values(expectedState).some(entry => entry.shimAllow)) {
+ BrowserTestUtils.isVisible(shimAllowSection, "Shim allow hint is visible.");
+ } else {
+ BrowserTestUtils.isHidden(shimAllowSection, "Shim allow hint is hidden.");
+ }
+
+ await closeProtectionsPanel();
+}
+
+async function runTestForCategoryAndState(category, action) {
+ // Maps the protection categories to the test tracking origins defined in
+ // ./trackingAPI.js and the UI class identifiers to look for in the
+ // protections UI.
+ let categoryToTestData = {
+ tracking: {
+ apiMessage: "more-tracking",
+ origin: "https://itisatracker.org",
+ elementId: "trackers",
+ },
+ socialtracking: {
+ origin: "https://social-tracking.example.org",
+ elementId: "socialblock",
+ },
+ cryptomining: {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ origin: "http://cryptomining.example.com",
+ elementId: "cryptominers",
+ },
+ fingerprinting: {
+ origin: "https://fingerprinting.example.com",
+ elementId: "fingerprinters",
+ },
+ };
+
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+ // Wait for the tab to load and the initial blocking events from the
+ // classifier.
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ let {
+ origin: trackingOrigin,
+ elementId: categoryElementId,
+ apiMessage,
+ } = categoryToTestData[category];
+ if (!apiMessage) {
+ apiMessage = category;
+ }
+
+ // For allow or replace actions we need to hook into before-block-channel.
+ // If we don't hook into the event, the tracking channel will be blocked.
+ let beforeBlockChannelPromise;
+ if (action != "block") {
+ beforeBlockChannelPromise = UrlClassifierTestUtils.handleBeforeBlockChannel(
+ {
+ filterOrigin: trackingOrigin,
+ action,
+ }
+ );
+ }
+ // Load the test tracker matching the category.
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ apiMessage }],
+ function (args) {
+ content.postMessage(args.apiMessage, "*");
+ }
+ );
+ await beforeBlockChannelPromise;
+
+ // Next, test if the UI state is correct for the given category and action.
+ let expectedState = {};
+ expectedState[trackingOrigin] = {
+ block: action == "block",
+ shimAllow: action == "allow",
+ };
+
+ await assertSubViewState(categoryElementId, expectedState);
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+/**
+ * Test mixed allow/block/replace states for the tracking protection category.
+ * @param {Object} options - States to test.
+ * @param {boolean} options.block - Test tracker block state.
+ * @param {boolean} options.allow - Test tracker allow state.
+ * @param {boolean} options.replace - Test tracker replace state.
+ */
+async function runTestMixed({ block, allow, replace }) {
+ const ORIGIN_BLOCK = "https://trackertest.org";
+ const ORIGIN_ALLOW = "https://itisatracker.org";
+ const ORIGIN_REPLACE = "https://tracking.example.com";
+
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]);
+
+ if (block) {
+ // Temporarily remove trackertest.org from the allowlist.
+ await SpecialPowers.pushPrefEnv({
+ clear: [
+ ["urlclassifier.trackingSkipURLs"],
+ ["urlclassifier.trackingAnnotationSkipURLs"],
+ ],
+ });
+ let blockEventPromise = waitForContentBlockingEvent();
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("tracking", "*");
+ });
+ await blockEventPromise;
+ await SpecialPowers.popPrefEnv();
+ }
+
+ if (allow) {
+ let promiseEvent = waitForContentBlockingEvent();
+ let promiseAllow = UrlClassifierTestUtils.handleBeforeBlockChannel({
+ filterOrigin: ORIGIN_ALLOW,
+ action: "allow",
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("more-tracking", "*");
+ });
+
+ await promiseAllow;
+ await promiseEvent;
+ }
+
+ if (replace) {
+ let promiseReplace = UrlClassifierTestUtils.handleBeforeBlockChannel({
+ filterOrigin: ORIGIN_REPLACE,
+ action: "replace",
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("more-tracking-2", "*");
+ });
+
+ await promiseReplace;
+ }
+
+ let expectedState = {};
+
+ if (block) {
+ expectedState[ORIGIN_BLOCK] = {
+ shimAllow: false,
+ block: true,
+ };
+ }
+
+ if (replace) {
+ expectedState[ORIGIN_REPLACE] = {
+ shimAllow: false,
+ block: false,
+ };
+ }
+
+ if (allow) {
+ expectedState[ORIGIN_ALLOW] = {
+ shimAllow: true,
+ block: false,
+ };
+ }
+
+ // Check the protection categories subview with the block list.
+ await assertSubViewState("trackers", expectedState);
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function testNoShim() {
+ await runTestMixed({
+ allow: false,
+ replace: false,
+ block: false,
+ });
+ await runTestMixed({
+ allow: false,
+ replace: false,
+ block: true,
+ });
+});
+
+add_task(async function testShimAllow() {
+ await runTestMixed({
+ allow: true,
+ replace: false,
+ block: false,
+ });
+ await runTestMixed({
+ allow: true,
+ replace: false,
+ block: true,
+ });
+});
+
+add_task(async function testShimReplace() {
+ await runTestMixed({
+ allow: false,
+ replace: true,
+ block: false,
+ });
+ await runTestMixed({
+ allow: false,
+ replace: true,
+ block: true,
+ });
+});
+
+add_task(async function testShimMixed() {
+ await runTestMixed({
+ allow: true,
+ replace: true,
+ block: true,
+ });
+});
+
+add_task(async function testShimCategorySubviews() {
+ let categories = [
+ "tracking",
+ "socialtracking",
+ "cryptomining",
+ "fingerprinting",
+ ];
+ for (let category of categories) {
+ for (let action of ["block", "allow", "replace"]) {
+ info(`Test category subview. category: ${category}, action: ${action}`);
+ await runTestForCategoryAndState(category, action);
+ }
+ }
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_suspicious_fingerprinters_subview.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_suspicious_fingerprinters_subview.js
new file mode 100644
index 0000000000..220f985223
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_suspicious_fingerprinters_subview.js
@@ -0,0 +1,427 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+/**
+ * Bug 1863280 - Testing the fingerprinting category of the protection panel
+ * shows the suspicious fingerpinter domain if the fingerprinting
+ * protection is enabled
+ */
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ ""
+);
+
+const TEST_DOMAIN = "https://www.example.com";
+const TEST_3RD_DOMAIN = "https://example.org";
+
+const TEST_PAGE = TEST_DOMAIN + TEST_PATH + "benignPage.html";
+const TEST_3RD_CANVAS_FP_PAGE =
+ TEST_3RD_DOMAIN + TEST_PATH + "canvas-fingerprinter.html";
+const TEST_3RD_FONT_FP_PAGE =
+ TEST_3RD_DOMAIN + TEST_PATH + "font-fingerprinter.html";
+
+const FINGERPRINT_BLOCKING_PREF =
+ "privacy.trackingprotection.fingerprinting.enabled";
+const FINGERPRINT_PROTECTION_PREF = "privacy.fingerprintingProtection";
+const FINGERPRINT_PROTECTION_PBM_PREF =
+ "privacy.fingerprintingProtection.pbmode";
+
+/**
+ * A helper function to check whether or not an element has "notFound" class.
+ *
+ * @param {String} id The id of the testing element.
+ * @returns {Boolean} true when the element has "notFound" class.
+ */
+function notFound(id) {
+ return document.getElementById(id).classList.contains("notFound");
+}
+
+/**
+ * A helper function to wait until the suspicious fingerprinting content
+ * blocking event is dispatched.
+ *
+ * @param {Window} win The window to listen content blocking events.
+ * @returns {Promise} A promise that resolves when the event files.
+ */
+async function waitForSuspiciousFingerprintingEvent(win) {
+ return new Promise(resolve => {
+ let listener = {
+ onContentBlockingEvent(webProgress, request, event) {
+ info(`Received onContentBlockingEvent event: ${event}`);
+ if (
+ event &
+ Ci.nsIWebProgressListener.STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING
+ ) {
+ win.gBrowser.removeProgressListener(listener);
+ resolve();
+ }
+ },
+ };
+ win.gBrowser.addProgressListener(listener);
+ });
+}
+
+/**
+ * A helper function that opens a tests page that embeds iframes with given
+ * urls.
+ *
+ * @param {string[]} urls An array of urls that will be embedded in the test page.
+ * @param {boolean} usePrivateWin true to indicate testing in a private window.
+ * @param {Function} testFn The test function that will be called after the
+ * iframes have been loaded.
+ */
+async function openTestPage(urls, usePrivateWin, testFn) {
+ let win = window;
+
+ if (usePrivateWin) {
+ win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ }
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url: TEST_PAGE },
+ async browser => {
+ info("Loading all iframes");
+
+ for (const url of urls) {
+ await SpecialPowers.spawn(browser, [url], async testUrl => {
+ await new content.Promise(resolve => {
+ let ifr = content.document.createElement("iframe");
+ ifr.onload = resolve;
+
+ content.document.body.appendChild(ifr);
+ ifr.src = testUrl;
+ });
+ });
+ }
+
+ info("Running the test");
+ await testFn(win, browser);
+ }
+ );
+
+ if (usePrivateWin) {
+ await BrowserTestUtils.closeWindow(win);
+ }
+}
+
+/**
+ * A testing function verifies that fingerprinting category is not shown.
+ *
+ * @param {*} win The window to run the test.
+ */
+async function testCategoryNotShown(win) {
+ await openProtectionsPanel(false, win);
+
+ let categoryItem = win.document.getElementById(
+ "protections-popup-category-fingerprinters"
+ );
+
+ // The fingerprinting category should have the 'notFound' class to indicate
+ // that no fingerprinter was found in the page.
+ ok(
+ notFound("protections-popup-category-fingerprinters"),
+ "Fingerprinting category is not found"
+ );
+
+ ok(
+ !BrowserTestUtils.isVisible(categoryItem),
+ "Fingerprinting category item is not visible"
+ );
+
+ await closeProtectionsPanel(win);
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [[FINGERPRINT_BLOCKING_PREF, true]],
+ });
+});
+
+// Verify that fingerprinting category is not shown if fingerprinting protection
+// is disabled.
+add_task(async function testFPPDisabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[FINGERPRINT_PROTECTION_PREF, false]],
+ });
+
+ await openTestPage(
+ [TEST_3RD_CANVAS_FP_PAGE, TEST_3RD_FONT_FP_PAGE],
+ false,
+ testCategoryNotShown
+ );
+});
+
+// Verify that fingerprinting category is not shown if no fingerprinting
+// activity is detected.
+add_task(async function testFPPEnabledWithoutFingerprintingActivity() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[FINGERPRINT_PROTECTION_PREF, true]],
+ });
+
+ // Test the case where the page doesn't load any fingerprinter.
+ await openTestPage([], false, testCategoryNotShown);
+
+ // Test the case where the page loads only one fingerprinter. We don't treat
+ // this case as suspicious fingerprinting.
+ await openTestPage([TEST_3RD_FONT_FP_PAGE], false, testCategoryNotShown);
+
+ // Test the case where the page loads the same fingerprinter multiple times.
+ // We don't treat this case as suspicious fingerprinting.
+ await openTestPage(
+ [TEST_3RD_FONT_FP_PAGE, TEST_3RD_FONT_FP_PAGE],
+ false,
+ testCategoryNotShown
+ );
+});
+
+// Verify that fingerprinting category is not shown if no fingerprinting
+// activity is detected.
+add_task(
+ async function testFPPEnabledWithoutSuspiciousFingerprintingActivity() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[FINGERPRINT_PROTECTION_PREF, true]],
+ });
+
+ // Test the case where the page doesn't load any fingerprinter.
+ await openTestPage([], false, testCategoryNotShown);
+
+ // Test the case where the page loads only one fingerprinter. We don't treat
+ // this case as suspicious fingerprinting.
+ await openTestPage([TEST_3RD_FONT_FP_PAGE], false, testCategoryNotShown);
+
+ // Test the case where the page loads the same fingerprinter multiple times.
+ // We don't treat this case as suspicious fingerprinting.
+ await openTestPage(
+ [TEST_3RD_FONT_FP_PAGE, TEST_3RD_FONT_FP_PAGE],
+ false,
+ testCategoryNotShown
+ );
+ }
+);
+
+// Verify that fingerprinting category is properly shown and the fingerprinting
+// subview displays the origin of the suspicious fingerprinter.
+add_task(async function testFingerprintingSubview() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[FINGERPRINT_PROTECTION_PREF, true]],
+ });
+
+ await openTestPage(
+ [TEST_3RD_CANVAS_FP_PAGE, TEST_3RD_FONT_FP_PAGE],
+ false,
+ async (win, _) => {
+ await openProtectionsPanel(false, win);
+
+ let categoryItem = win.document.getElementById(
+ "protections-popup-category-fingerprinters"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await BrowserTestUtils.waitForMutationCondition(categoryItem, {}, () =>
+ BrowserTestUtils.isVisible(categoryItem)
+ );
+
+ ok(
+ BrowserTestUtils.isVisible(categoryItem),
+ "Fingerprinting category item is visible"
+ );
+
+ // Click the fingerprinting category and wait until the fingerprinter view is
+ // shown.
+ let fingerprintersView = win.document.getElementById(
+ "protections-popup-fingerprintersView"
+ );
+ let viewShown = BrowserTestUtils.waitForEvent(
+ fingerprintersView,
+ "ViewShown"
+ );
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Fingerprinter view was shown");
+
+ // Ensure the fingerprinter is listed on the tracker list.
+ let listItems = Array.from(
+ fingerprintersView.querySelectorAll(".protections-popup-list-item")
+ );
+ is(listItems.length, 1, "We have 1 fingerprinter in the list");
+
+ let listItem = listItems.find(
+ item => item.querySelector("label").value == "https://example.org"
+ );
+ ok(listItem, "Has an item for example.org");
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+
+ // Back to the popup main view.
+ let mainView = win.document.getElementById("protections-popup-mainView");
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ let backButton = fingerprintersView.querySelector(".subviewbutton-back");
+ backButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+
+ await closeProtectionsPanel(win);
+ }
+ );
+});
+
+// Verify the case where the fingerprinting protection is only enabled in PBM.
+add_task(async function testFingerprintingSubviewInPBM() {
+ // Only enabled fingerprinting protection in PBM.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FINGERPRINT_PROTECTION_PREF, false],
+ [FINGERPRINT_PROTECTION_PBM_PREF, true],
+ ],
+ });
+
+ // Verify that fingerprinting category isn't shown on a normal window.
+ await openTestPage(
+ [TEST_3RD_CANVAS_FP_PAGE, TEST_3RD_FONT_FP_PAGE],
+ false,
+ testCategoryNotShown
+ );
+
+ // Verify that fingerprinting category is shown on a private window.
+ await openTestPage(
+ [TEST_3RD_CANVAS_FP_PAGE, TEST_3RD_FONT_FP_PAGE],
+ true,
+ async (win, _) => {
+ await openProtectionsPanel(false, win);
+
+ let categoryItem = win.document.getElementById(
+ "protections-popup-category-fingerprinters"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await BrowserTestUtils.waitForMutationCondition(categoryItem, {}, () =>
+ BrowserTestUtils.isVisible(categoryItem)
+ );
+
+ ok(
+ BrowserTestUtils.isVisible(categoryItem),
+ "Fingerprinting category item is visible"
+ );
+
+ // Click the fingerprinting category and wait until the fingerprinter view is
+ // shown.
+ let fingerprintersView = win.document.getElementById(
+ "protections-popup-fingerprintersView"
+ );
+ let viewShown = BrowserTestUtils.waitForEvent(
+ fingerprintersView,
+ "ViewShown"
+ );
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Fingerprinter view was shown");
+
+ // Ensure the fingerprinter is listed on the tracker list.
+ let listItems = Array.from(
+ fingerprintersView.querySelectorAll(".protections-popup-list-item")
+ );
+ is(listItems.length, 1, "We have 1 fingerprinter in the list");
+
+ let listItem = listItems.find(
+ item => item.querySelector("label").value == "https://example.org"
+ );
+ ok(listItem, "Has an item for example.org");
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+
+ // Back to the popup main view.
+ let mainView = win.document.getElementById("protections-popup-mainView");
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ let backButton = fingerprintersView.querySelector(".subviewbutton-back");
+ backButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+
+ await closeProtectionsPanel(win);
+ }
+ );
+});
+
+// Verify that fingerprinting category will be shown after loading
+// fingerprinters.
+add_task(async function testDynamicallyLoadFingerprinter() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[FINGERPRINT_PROTECTION_PREF, true]],
+ });
+
+ await openTestPage([TEST_3RD_FONT_FP_PAGE], false, async (win, browser) => {
+ await openProtectionsPanel(false, win);
+
+ let categoryItem = win.document.getElementById(
+ "protections-popup-category-fingerprinters"
+ );
+
+ // The fingerprinting category should have the 'notFound' class to indicate
+ // that no suspicious fingerprinter was found in the page.
+ ok(
+ notFound("protections-popup-category-fingerprinters"),
+ "Fingerprinting category is not found"
+ );
+
+ ok(
+ !BrowserTestUtils.isVisible(categoryItem),
+ "Fingerprinting category item is not visible"
+ );
+
+ // Add an iframe that triggers suspicious fingerprinting and wait until the
+ // content event files.
+
+ let contentBlockingEventPromise = waitForSuspiciousFingerprintingEvent(win);
+ await SpecialPowers.spawn(browser, [TEST_3RD_CANVAS_FP_PAGE], test_url => {
+ let ifr = content.document.createElement("iframe");
+
+ content.document.body.appendChild(ifr);
+ ifr.src = test_url;
+ });
+ await contentBlockingEventPromise;
+
+ // Click the fingerprinting category and wait until the fingerprinter view
+ // is shown.
+ let fingerprintersView = win.document.getElementById(
+ "protections-popup-fingerprintersView"
+ );
+ let viewShown = BrowserTestUtils.waitForEvent(
+ fingerprintersView,
+ "ViewShown"
+ );
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Fingerprinter view was shown");
+
+ // Ensure the fingerprinter is listed on the tracker list.
+ let listItems = Array.from(
+ fingerprintersView.querySelectorAll(".protections-popup-list-item")
+ );
+ is(listItems.length, 1, "We have 1 fingerprinter in the list");
+
+ let listItem = listItems.find(
+ item => item.querySelector("label").value == "https://example.org"
+ );
+ ok(listItem, "Has an item for example.org");
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+
+ // Back to the popup main view.
+ let mainView = win.document.getElementById("protections-popup-mainView");
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ let backButton = fingerprintersView.querySelector(".subviewbutton-back");
+ backButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+
+ await closeProtectionsPanel(win);
+ });
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_telemetry.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_telemetry.js
new file mode 100644
index 0000000000..6bac0ce9b6
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_telemetry.js
@@ -0,0 +1,89 @@
+/*
+ * Test telemetry for Tracking Protection
+ */
+
+const PREF = "privacy.trackingprotection.enabled";
+const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
+const BENIGN_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html";
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+
+/**
+ * Enable local telemetry recording for the duration of the tests.
+ */
+var oldCanRecord = Services.telemetry.canRecordExtended;
+Services.telemetry.canRecordExtended = true;
+registerCleanupFunction(function () {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(DTSCBN_PREF);
+});
+
+function getShieldHistogram() {
+ return Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD");
+}
+
+function getShieldCounts() {
+ return getShieldHistogram().snapshot().values;
+}
+
+add_setup(async function () {
+ await UrlClassifierTestUtils.addTestTrackers();
+ Services.prefs.setBoolPref(DTSCBN_PREF, true);
+
+ let TrackingProtection =
+ gBrowser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+ ok(!TrackingProtection.enabled, "TP is not enabled");
+
+ let enabledCounts = Services.telemetry
+ .getHistogramById("TRACKING_PROTECTION_ENABLED")
+ .snapshot().values;
+ is(enabledCounts[0], 1, "TP was not enabled on start up");
+});
+
+add_task(async function testShieldHistogram() {
+ Services.prefs.setBoolPref(PREF, true);
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // Reset these to make counting easier
+ getShieldHistogram().clear();
+
+ await promiseTabLoadEvent(tab, BENIGN_PAGE);
+ is(getShieldCounts()[0], 1, "Page loads without tracking");
+
+ await promiseTabLoadEvent(tab, TRACKING_PAGE);
+ is(getShieldCounts()[0], 2, "Adds one more page load");
+ is(getShieldCounts()[2], 1, "Counts one instance of the shield being shown");
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ gProtectionsHandler.disableForCurrentPage();
+ await tabReloadPromise;
+ is(getShieldCounts()[0], 3, "Adds one more page load");
+ is(
+ getShieldCounts()[1],
+ 1,
+ "Counts one instance of the shield being crossed out"
+ );
+
+ info("Re-enable TP for the page (which reloads the page)");
+ tabReloadPromise = promiseTabLoadEvent(tab);
+ gProtectionsHandler.enableForCurrentPage();
+ await tabReloadPromise;
+ is(getShieldCounts()[0], 4, "Adds one more page load");
+ is(
+ getShieldCounts()[2],
+ 2,
+ "Adds one more instance of the shield being shown"
+ );
+
+ gBrowser.removeCurrentTab();
+
+ // Reset these to make counting easier for the next test
+ getShieldHistogram().clear();
+});
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js
new file mode 100644
index 0000000000..22decca636
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js
@@ -0,0 +1,144 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+const l10n = new Localization(["browser/siteProtections.ftl"]);
+
+const TRACKING_PAGE =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+
+const TP_PREF = "privacy.trackingprotection.enabled";
+
+add_setup(async function () {
+ await UrlClassifierTestUtils.addTestTrackers();
+
+ registerCleanupFunction(() => {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ });
+});
+
+async function assertSitesListed(blocked) {
+ let promise = BrowserTestUtils.openNewForegroundTab({
+ url: TRACKING_PAGE,
+ gBrowser,
+ });
+
+ // Wait for 2 content blocking events - one for the load and one for the tracker.
+ let [tab] = await Promise.all([promise, waitForContentBlockingEvent(2)]);
+
+ await openProtectionsPanel();
+
+ let categoryItem = document.getElementById(
+ "protections-popup-category-trackers"
+ );
+
+ // Explicitly waiting for the category item becoming visible.
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.isVisible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
+ let trackersView = document.getElementById("protections-popup-trackersView");
+ let viewShown = BrowserTestUtils.waitForEvent(trackersView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Trackers view was shown");
+
+ let trackersViewShimHint = document.getElementById(
+ "protections-popup-trackersView-shim-allow-hint"
+ );
+ ok(trackersViewShimHint.hidden, "Shim hint is hidden");
+ let listItems = trackersView.querySelectorAll(".protections-popup-list-item");
+ is(listItems.length, 1, "We have 1 tracker in the list");
+
+ let mainView = document.getElementById("protections-popup-mainView");
+ viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+ let backButton = trackersView.querySelector(".subviewbutton-back");
+ backButton.click();
+ await viewShown;
+
+ ok(true, "Main view was shown");
+
+ let change = waitForSecurityChange(1);
+ let timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000));
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.postMessage("more-tracking", "*");
+ });
+
+ let result = await Promise.race([change, timeoutPromise]);
+ is(result, undefined, "No securityChange events should be received");
+
+ viewShown = BrowserTestUtils.waitForEvent(trackersView, "ViewShown");
+ categoryItem.click();
+ await viewShown;
+
+ ok(true, "Trackers view was shown");
+
+ const header = trackersView.querySelector(".panel-header > h1 > span");
+ const headerL10nId = blocked
+ ? "protections-blocking-tracking-content"
+ : "protections-not-blocking-tracking-content";
+ const [headerMsg] = await l10n.formatMessages([headerL10nId]);
+ const expHeader = headerMsg.attributes.find(a => a.name === "title").value;
+ is(header.textContent, expHeader, "Trackers view header is correct");
+
+ listItems = Array.from(
+ trackersView.querySelectorAll(".protections-popup-list-item")
+ );
+ is(listItems.length, 2, "We have 2 trackers in the list");
+
+ let listItem = listItems.find(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ item => item.querySelector("label").value == "http://trackertest.org"
+ );
+ ok(listItem, "Has an item for trackertest.org");
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+ is(
+ listItem.classList.contains("allowed"),
+ !blocked,
+ "Indicates whether the tracker was blocked or allowed"
+ );
+
+ listItem = listItems.find(
+ item => item.querySelector("label").value == "https://itisatracker.org"
+ );
+ ok(listItem, "Has an item for itisatracker.org");
+ ok(BrowserTestUtils.isVisible(listItem), "List item is visible");
+ is(
+ listItem.classList.contains("allowed"),
+ !blocked,
+ "Indicates whether the tracker was blocked or allowed"
+ );
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function testTrackersSubView() {
+ info("Testing trackers subview with TP disabled.");
+ Services.prefs.setBoolPref(TP_PREF, false);
+ await assertSitesListed(false);
+ info("Testing trackers subview with TP enabled.");
+ Services.prefs.setBoolPref(TP_PREF, true);
+ await assertSitesListed(true);
+ info("Testing trackers subview with TP enabled and a CB exception.");
+ let uri = Services.io.newURI("https://tracking.example.org");
+ PermissionTestUtils.add(
+ uri,
+ "trackingprotection",
+ Services.perms.ALLOW_ACTION
+ );
+ await assertSitesListed(false);
+ info("Testing trackers subview with TP enabled and a CB exception removed.");
+ PermissionTestUtils.remove(uri, "trackingprotection");
+ await assertSitesListed(true);
+
+ Services.prefs.clearUserPref(TP_PREF);
+});
diff --git a/browser/base/content/test/protectionsUI/canvas-fingerprinter.html b/browser/base/content/test/protectionsUI/canvas-fingerprinter.html
new file mode 100644
index 0000000000..d48d289529
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/canvas-fingerprinter.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html>
+<head>
+ <title>A page containing a canvas fingerprinter</title>
+</head>
+<body>
+ <canvas width=200 height=200>
+ </canvas>
+ <script>
+ var canvas = document.querySelector("canvas");
+ var context = canvas.getContext("2d");
+
+ context.fillStyle = "rgb(100, 210, 0)";
+ context.fillRect(100, 10, 50, 50);
+ context.fillStyle = "#f65";
+ context.font = "16pt Arial";
+ context.fillText("<@nv45. F1n63r,Pr1n71n6!", 20, 40);
+
+ var data = canvas.toDataURL();
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/protectionsUI/containerPage.html b/browser/base/content/test/protectionsUI/containerPage.html
new file mode 100644
index 0000000000..f68f7325c1
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/containerPage.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <iframe src="http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/embeddedPage.html"></iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/protectionsUI/cookiePage.html b/browser/base/content/test/protectionsUI/cookiePage.html
new file mode 100644
index 0000000000..e7ef2aafa1
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/cookiePage.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ <script src="trackingAPI.js" type="text/javascript"></script>
+ </head>
+ <body>
+ <iframe src="http://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/protectionsUI/cookieServer.sjs b/browser/base/content/test/protectionsUI/cookieServer.sjs
new file mode 100644
index 0000000000..44341b9a71
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/cookieServer.sjs
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// A 1x1 PNG image.
+// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+const IMAGE = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
+);
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200);
+ if (
+ request.queryString &&
+ request.queryString.includes("type=image-no-cookie")
+ ) {
+ response.setHeader("Content-Type", "image/png", false);
+ response.write(IMAGE);
+ } else {
+ response.setHeader("Set-Cookie", "foopy=1");
+ response.write("cookie served");
+ }
+}
diff --git a/browser/base/content/test/protectionsUI/cookieSetterPage.html b/browser/base/content/test/protectionsUI/cookieSetterPage.html
new file mode 100644
index 0000000000..aab18e0aff
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/cookieSetterPage.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <script> document.cookie = "foo=bar"; </script>
+</body>
+</html>
diff --git a/browser/base/content/test/protectionsUI/emailTrackingPage.html b/browser/base/content/test/protectionsUI/emailTrackingPage.html
new file mode 100644
index 0000000000..85b48cbbcb
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/emailTrackingPage.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <iframe src="https://email-tracking.example.org/"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/protectionsUI/embeddedPage.html b/browser/base/content/test/protectionsUI/embeddedPage.html
new file mode 100644
index 0000000000..6003d49300
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/embeddedPage.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <iframe src="http://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieSetterPage.html"></iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.html b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.html
new file mode 100644
index 0000000000..4a7f1a1682
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Testing the shield from fetch and XHR</title>
+</head>
+<body>
+ <p>Hello there!</p>
+ <script type="application/javascript">
+ function test_fetch() {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let url = "http://trackertest.org/browser/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js";
+ return fetch(url);
+ }
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js
new file mode 100644
index 0000000000..f7ac687cfc
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js
@@ -0,0 +1,2 @@
+/* Some code goes here! */
+void 0;
diff --git a/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js^headers^ b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js^headers^
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/file_protectionsUI_fetch.js^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/browser/base/content/test/protectionsUI/font-fingerprinter.html b/browser/base/content/test/protectionsUI/font-fingerprinter.html
new file mode 100644
index 0000000000..efc71aa999
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/font-fingerprinter.html
@@ -0,0 +1,102 @@
+<!doctype html>
+<html>
+<head>
+ <title>A page containing a font fingerprinter</title>
+</head>
+<body>
+<script>
+/*
+* Based on https://github.com/Valve/fingerprintjs2/blob/master/fingerprint2.js
+* (Archived: https://web.archive.org/web/20150706050408/https://github.com/Valve/fingerprintjs2/blob/master/fingerprint2.js#L233)
+*
+* Fingerprintjs2 0.1.4 - Modern & flexible browser fingerprint library v2
+* https://github.com/Valve/fingerprintjs2
+* Copyright (c) 2015 Valentin Vasilyev (valentin.vasilyev@outlook.com)
+* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+*/
+// kudos to http://www.lalit.org/lab/javascript-css-font-detect/
+function getFonts() {
+ // a font will be compared against all the three default fonts.
+ // and if it doesn't match all 3 then that font is not available.
+ var baseFonts = ["monospace", "sans-serif", "serif"];
+
+ //we use m or w because these two characters take up the maximum width.
+ // And we use a LLi so that the same matching fonts can get separated
+ var testString = "mmmmmmmmmmlli";
+
+ //we test using 72px font size, we may use any size. I guess larger the better.
+ var testSize = "72px";
+
+ var h = document.getElementsByTagName("body")[0];
+
+ // create a SPAN in the document to get the width of the text we use to test
+ var s = document.createElement("span");
+ s.style.fontSize = testSize;
+ s.innerHTML = testString;
+ var defaultWidth = {};
+ var defaultHeight = {};
+ for (var index in baseFonts) {
+ //get the default width for the three base fonts
+ s.style.fontFamily = baseFonts[index];
+ h.appendChild(s);
+ defaultWidth[baseFonts[index]] = s.offsetWidth; //width for the default font
+ defaultHeight[baseFonts[index]] = s.offsetHeight; //height for the defualt font
+ h.removeChild(s);
+ }
+ var detect = function (font) {
+ var detected = false;
+ for (var idx in baseFonts) {
+ s.style.fontFamily = font + "," + baseFonts[idx]; // name of the font along with the base font for fallback.
+ h.appendChild(s);
+ var matched = (s.offsetWidth !== defaultWidth[baseFonts[idx]] || s.offsetHeight !== defaultHeight[baseFonts[idx]]);
+ h.removeChild(s);
+ detected = detected || matched;
+ }
+ return detected;
+ };
+ var fontList = [
+ "Abadi MT Condensed Light", "Academy Engraved LET", "ADOBE CASLON PRO", "Adobe Garamond", "ADOBE GARAMOND PRO", "Agency FB", "Aharoni", "Albertus Extra Bold", "Albertus Medium", "Algerian", "Amazone BT", "American Typewriter",
+ "American Typewriter Condensed", "AmerType Md BT", "Andale Mono", "Andalus", "Angsana New", "AngsanaUPC", "Antique Olive", "Aparajita", "Apple Chancery", "Apple Color Emoji", "Apple SD Gothic Neo", "Arabic Typesetting", "ARCHER", "Arial", "Arial Black", "Arial Hebrew",
+ "Arial MT", "Arial Narrow", "Arial Rounded MT Bold", "Arial Unicode MS", "ARNO PRO", "Arrus BT", "Aurora Cn BT", "AvantGarde Bk BT", "AvantGarde Md BT", "AVENIR", "Ayuthaya", "Bandy", "Bangla Sangam MN", "Bank Gothic", "BankGothic Md BT", "Baskerville",
+ "Baskerville Old Face", "Batang", "BatangChe", "Bauer Bodoni", "Bauhaus 93", "Bazooka", "Bell MT", "Bembo", "Benguiat Bk BT", "Berlin Sans FB", "Berlin Sans FB Demi", "Bernard MT Condensed", "BernhardFashion BT", "BernhardMod BT", "Big Caslon", "BinnerD",
+ "Bitstream Vera Sans Mono", "Blackadder ITC", "BlairMdITC TT", "Bodoni 72", "Bodoni 72 Oldstyle", "Bodoni 72 Smallcaps", "Bodoni MT", "Bodoni MT Black", "Bodoni MT Condensed", "Bodoni MT Poster Compressed", "Book Antiqua", "Bookman Old Style",
+ "Bookshelf Symbol 7", "Boulder", "Bradley Hand", "Bradley Hand ITC", "Bremen Bd BT", "Britannic Bold", "Broadway", "Browallia New", "BrowalliaUPC", "Brush Script MT", "Calibri", "Californian FB", "Calisto MT", "Calligrapher", "Cambria", "Cambria Math", "Candara",
+ "CaslonOpnface BT", "Castellar", "Centaur", "Century", "Century Gothic", "Century Schoolbook", "Cezanne", "CG Omega", "CG Times", "Chalkboard", "Chalkboard SE", "Chalkduster", "Charlesworth", "Charter Bd BT", "Charter BT", "Chaucer",
+ "ChelthmITC Bk BT", "Chiller", "Clarendon", "Clarendon Condensed", "CloisterBlack BT", "Cochin", "Colonna MT", "Comic Sans", "Comic Sans MS", "Consolas", "Constantia", "Cooper Black", "Copperplate", "Copperplate Gothic", "Copperplate Gothic Bold",
+ "Copperplate Gothic Light", "CopperplGoth Bd BT", "Corbel", "Cordia New", "CordiaUPC", "Cornerstone", "Coronet", "Courier", "Courier New", "Cuckoo", "Curlz MT", "DaunPenh", "Dauphin", "David", "DB LCD Temp", "DELICIOUS", "Denmark", "Devanagari Sangam MN",
+ "DFKai-SB", "Didot", "DilleniaUPC", "DIN", "DokChampa", "Dotum", "DotumChe", "Ebrima", "Edwardian Script ITC", "Elephant", "English 111 Vivace BT", "Engravers MT", "EngraversGothic BT", "Eras Bold ITC", "Eras Demi ITC", "Eras Light ITC", "Eras Medium ITC",
+ "Estrangelo Edessa", "EucrosiaUPC", "Euphemia", "Euphemia UCAS", "EUROSTILE", "Exotc350 Bd BT", "FangSong", "Felix Titling", "Fixedsys", "FONTIN", "Footlight MT Light", "Forte", "Franklin Gothic", "Franklin Gothic Book", "Franklin Gothic Demi",
+ "Franklin Gothic Demi Cond", "Franklin Gothic Heavy", "Franklin Gothic Medium", "Franklin Gothic Medium Cond", "FrankRuehl", "Fransiscan", "Freefrm721 Blk BT", "FreesiaUPC", "Freestyle Script", "French Script MT", "FrnkGothITC Bk BT", "Fruitger", "FRUTIGER",
+ "Futura", "Futura Bk BT", "Futura Lt BT", "Futura Md BT", "Futura ZBlk BT", "FuturaBlack BT", "Gabriola", "Galliard BT", "Garamond", "Gautami", "Geeza Pro", "Geneva", "Geometr231 BT", "Geometr231 Hv BT", "Geometr231 Lt BT", "Georgia", "GeoSlab 703 Lt BT",
+ "GeoSlab 703 XBd BT", "Gigi", "Gill Sans", "Gill Sans MT", "Gill Sans MT Condensed", "Gill Sans MT Ext Condensed Bold", "Gill Sans Ultra Bold", "Gill Sans Ultra Bold Condensed", "Gisha", "Gloucester MT Extra Condensed", "GOTHAM", "GOTHAM BOLD",
+ "Goudy Old Style", "Goudy Stout", "GoudyHandtooled BT", "GoudyOLSt BT", "Gujarati Sangam MN", "Gulim", "GulimChe", "Gungsuh", "GungsuhChe", "Gurmukhi MN", "Haettenschweiler", "Harlow Solid Italic", "Harrington", "Heather", "Heiti SC", "Heiti TC", "HELV", "Helvetica",
+ "Helvetica Neue", "Herald", "High Tower Text", "Hiragino Kaku Gothic ProN", "Hiragino Mincho ProN", "Hoefler Text", "Humanst 521 Cn BT", "Humanst521 BT", "Humanst521 Lt BT", "Impact", "Imprint MT Shadow", "Incised901 Bd BT", "Incised901 BT",
+ "Incised901 Lt BT", "INCONSOLATA", "Informal Roman", "Informal011 BT", "INTERSTATE", "IrisUPC", "Iskoola Pota", "JasmineUPC", "Jazz LET", "Jenson", "Jester", "Jokerman", "Juice ITC", "Kabel Bk BT", "Kabel Ult BT", "Kailasa", "KaiTi", "Kalinga", "Kannada Sangam MN",
+ "Kartika", "Kaufmann Bd BT", "Kaufmann BT", "Khmer UI", "KodchiangUPC", "Kokila", "Korinna BT", "Kristen ITC", "Krungthep", "Kunstler Script", "Lao UI", "Latha", "Leelawadee", "Letter Gothic", "Levenim MT", "LilyUPC", "Lithograph", "Lithograph Light", "Long Island",
+ "Lucida Bright", "Lucida Calligraphy", "Lucida Console", "Lucida Fax", "LUCIDA GRANDE", "Lucida Handwriting", "Lucida Sans", "Lucida Sans Typewriter", "Lucida Sans Unicode", "Lydian BT", "Magneto", "Maiandra GD", "Malayalam Sangam MN", "Malgun Gothic",
+ "Mangal", "Marigold", "Marion", "Marker Felt", "Market", "Marlett", "Matisse ITC", "Matura MT Script Capitals", "Meiryo", "Meiryo UI", "Microsoft Himalaya", "Microsoft JhengHei", "Microsoft New Tai Lue", "Microsoft PhagsPa", "Microsoft Sans Serif", "Microsoft Tai Le",
+ "Microsoft Uighur", "Microsoft YaHei", "Microsoft Yi Baiti", "MingLiU", "MingLiU_HKSCS", "MingLiU_HKSCS-ExtB", "MingLiU-ExtB", "Minion", "Minion Pro", "Miriam", "Miriam Fixed", "Mistral", "Modern", "Modern No. 20", "Mona Lisa Solid ITC TT", "Monaco", "Mongolian Baiti",
+ "MONO", "Monotype Corsiva", "MoolBoran", "Mrs Eaves", "MS Gothic", "MS LineDraw", "MS Mincho", "MS Outlook", "MS PGothic", "MS PMincho", "MS Reference Sans Serif", "MS Reference Specialty", "MS Sans Serif", "MS Serif", "MS UI Gothic", "MT Extra", "MUSEO", "MV Boli", "MYRIAD",
+ "MYRIAD PRO", "Nadeem", "Narkisim", "NEVIS", "News Gothic", "News GothicMT", "NewsGoth BT", "Niagara Engraved", "Niagara Solid", "Noteworthy", "NSimSun", "Nyala", "OCR A Extended", "Old Century", "Old English Text MT", "Onyx", "Onyx BT", "OPTIMA", "Oriya Sangam MN",
+ "OSAKA", "OzHandicraft BT", "Palace Script MT", "Palatino", "Palatino Linotype", "Papyrus", "Parchment", "Party LET", "Pegasus", "Perpetua", "Perpetua Titling MT", "PetitaBold", "Pickwick", "Plantagenet Cherokee", "Playbill", "PMingLiU", "PMingLiU-ExtB",
+ "Poor Richard", "Poster", "PosterBodoni BT", "PRINCETOWN LET", "Pristina", "PTBarnum BT", "Pythagoras", "Raavi", "Rage Italic", "Ravie", "Ribbon131 Bd BT", "Rockwell", "Rockwell Condensed", "Rockwell Extra Bold", "Rod", "Roman", "Sakkal Majalla",
+ "Santa Fe LET", "Savoye LET", "Sceptre", "Script", "Script MT Bold", "SCRIPTINA", "Segoe Print", "Segoe Script", "Segoe UI", "Segoe UI Light", "Segoe UI Semibold", "Segoe UI Symbol", "Serifa", "Serifa BT", "Serifa Th BT", "ShelleyVolante BT", "Sherwood",
+ "Shonar Bangla", "Showcard Gothic", "Shruti", "Signboard", "SILKSCREEN", "SimHei", "Simplified Arabic", "Simplified Arabic Fixed", "SimSun", "SimSun-ExtB", "Sinhala Sangam MN", "Sketch Rockwell", "Skia", "Small Fonts", "Snap ITC", "Snell Roundhand", "Socket",
+ "Souvenir Lt BT", "Staccato222 BT", "Steamer", "Stencil", "Storybook", "Styllo", "Subway", "Swis721 BlkEx BT", "Swiss911 XCm BT", "Sylfaen", "Synchro LET", "System", "Tahoma", "Tamil Sangam MN", "Technical", "Teletype", "Telugu Sangam MN", "Tempus Sans ITC",
+ "Terminal", "Thonburi", "Times", "Times New Roman", "Times New Roman PS", "Traditional Arabic", "Trajan", "TRAJAN PRO", "Trebuchet MS", "Tristan", "Tubular", "Tunga", "Tw Cen MT", "Tw Cen MT Condensed", "Tw Cen MT Condensed Extra Bold",
+ "TypoUpright BT", "Unicorn", "Univers", "Univers CE 55 Medium", "Univers Condensed", "Utsaah", "Vagabond", "Vani", "Verdana", "Vijaya", "Viner Hand ITC", "VisualUI", "Vivaldi", "Vladimir Script", "Vrinda", "Westminster", "WHITNEY", "Wide Latin", "Wingdings",
+ "Wingdings 2", "Wingdings 3", "ZapfEllipt BT", "ZapfHumnst BT", "ZapfHumnst Dm BT", "Zapfino", "Zurich BlkEx BT", "Zurich Ex BT", "ZWAdobeF"];
+ var available = [];
+ for (var i = 0, l = fontList.length; i < l; i++) {
+ if(detect(fontList[i])) {
+ available.push(fontList[i]);
+ }
+ }
+ return available;
+}
+
+let fonts = getFonts();
+console.log("detected fonts:", fonts);
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/protectionsUI/head.js b/browser/base/content/test/protectionsUI/head.js
new file mode 100644
index 0000000000..98e5063129
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/head.js
@@ -0,0 +1,247 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { Sqlite } = ChromeUtils.importESModule(
+ "resource://gre/modules/Sqlite.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "TrackingDBService",
+ "@mozilla.org/tracking-db-service;1",
+ "nsITrackingDBService"
+);
+
+ChromeUtils.defineLazyGetter(this, "TRACK_DB_PATH", function () {
+ return PathUtils.join(PathUtils.profileDir, "protections.sqlite");
+});
+
+ChromeUtils.defineESModuleGetters(this, {
+ ContentBlockingAllowList:
+ "resource://gre/modules/ContentBlockingAllowList.sys.mjs",
+});
+
+var { UrlClassifierTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
+);
+
+async function waitForProtectionsPanelToast() {
+ await BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popupshown"
+ );
+ Assert.ok(
+ gProtectionsHandler._protectionsPopup.hasAttribute("toast"),
+ "Protections panel toast is shown."
+ );
+
+ await BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+}
+
+async function openProtectionsPanel(toast, win = window) {
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ win,
+ "popupshown",
+ true,
+ e => e.target.id == "protections-popup"
+ );
+ let shieldIconContainer = win.document.getElementById(
+ "tracking-protection-icon-container"
+ );
+
+ // Register a promise to wait for the tooltip to be shown.
+ let tooltip = win.document.getElementById("tracking-protection-icon-tooltip");
+ let tooltipShownPromise = BrowserTestUtils.waitForPopupEvent(
+ tooltip,
+ "shown"
+ );
+
+ // Move out than move over the shield icon to trigger the hover event in
+ // order to fetch tracker count.
+ EventUtils.synthesizeMouseAtCenter(
+ win.gURLBar.textbox,
+ {
+ type: "mousemove",
+ },
+ win
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ shieldIconContainer,
+ {
+ type: "mousemove",
+ },
+ win
+ );
+
+ // Wait for the tooltip to be shown.
+ await tooltipShownPromise;
+
+ if (!toast) {
+ EventUtils.synthesizeMouseAtCenter(shieldIconContainer, {}, win);
+ } else {
+ win.gProtectionsHandler.showProtectionsPopup({ toast });
+ }
+
+ await popupShownPromise;
+}
+
+async function openProtectionsPanelWithKeyNav() {
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ window,
+ "popupshown",
+ true,
+ e => e.target.id == "protections-popup"
+ );
+
+ gURLBar.focus();
+
+ // This will trigger the focus event for the shield icon for pre-fetching
+ // the tracker count.
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ EventUtils.synthesizeKey("KEY_Enter", {});
+
+ await popupShownPromise;
+}
+
+async function closeProtectionsPanel(win = window) {
+ let protectionsPopup = win.document.getElementById("protections-popup");
+ if (!protectionsPopup) {
+ return;
+ }
+ let popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ protectionsPopup,
+ "popuphidden"
+ );
+
+ PanelMultiView.hidePopup(protectionsPopup);
+ await popuphiddenPromise;
+}
+
+function checkClickTelemetry(objectName, value, source = "protectionspopup") {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS
+ ).parent;
+ let buttonEvents = events.filter(
+ e =>
+ e[1] == `security.ui.${source}` &&
+ e[2] == "click" &&
+ e[3] == objectName &&
+ e[4] === value
+ );
+ is(buttonEvents.length, 1, `recorded ${objectName} telemetry event`);
+}
+
+async function addTrackerDataIntoDB(count) {
+ const insertSQL =
+ "INSERT INTO events (type, count, timestamp)" +
+ "VALUES (:type, :count, date(:timestamp));";
+
+ let db = await Sqlite.openConnection({ path: TRACK_DB_PATH });
+ let date = new Date().toISOString();
+
+ await db.execute(insertSQL, {
+ type: TrackingDBService.TRACKERS_ID,
+ count,
+ timestamp: date,
+ });
+
+ await db.close();
+}
+
+async function waitForAboutProtectionsTab() {
+ let tab = await BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:protections",
+ true
+ );
+
+ // When the graph is built it means the messaging has finished,
+ // we can close the tab.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ let bars = content.document.querySelectorAll(".graph-bar");
+ return bars.length;
+ }, "The graph has been built");
+ });
+
+ return tab;
+}
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url) {
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ if (url) {
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url);
+ }
+
+ return loaded;
+}
+
+function waitForSecurityChange(numChanges = 1, win = null) {
+ if (!win) {
+ win = window;
+ }
+ return new Promise(resolve => {
+ let n = 0;
+ let listener = {
+ onSecurityChange() {
+ n = n + 1;
+ info("Received onSecurityChange event " + n + " of " + numChanges);
+ if (n >= numChanges) {
+ win.gBrowser.removeProgressListener(listener);
+ resolve(n);
+ }
+ },
+ };
+ win.gBrowser.addProgressListener(listener);
+ });
+}
+
+function waitForContentBlockingEvent(numChanges = 1, win = null) {
+ if (!win) {
+ win = window;
+ }
+ return new Promise(resolve => {
+ let n = 0;
+ let listener = {
+ onContentBlockingEvent(webProgress, request, event) {
+ n = n + 1;
+ info(
+ `Received onContentBlockingEvent event: ${event} (${n} of ${numChanges})`
+ );
+ if (n >= numChanges) {
+ win.gBrowser.removeProgressListener(listener);
+ resolve(n);
+ }
+ },
+ };
+ win.gBrowser.addProgressListener(listener);
+ });
+}
diff --git a/browser/base/content/test/protectionsUI/sandboxed.html b/browser/base/content/test/protectionsUI/sandboxed.html
new file mode 100644
index 0000000000..661fb0b8e2
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/sandboxed.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <iframe src="http://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/protectionsUI/sandboxed.html^headers^ b/browser/base/content/test/protectionsUI/sandboxed.html^headers^
new file mode 100644
index 0000000000..4705ce9ded
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/sandboxed.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts;
diff --git a/browser/base/content/test/protectionsUI/trackingAPI.js b/browser/base/content/test/protectionsUI/trackingAPI.js
new file mode 100644
index 0000000000..de5479a70f
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/trackingAPI.js
@@ -0,0 +1,77 @@
+function createIframe(src) {
+ let ifr = document.createElement("iframe");
+ ifr.src = src;
+ document.body.appendChild(ifr);
+}
+
+function createImage(src) {
+ let img = document.createElement("img");
+ img.src = src;
+ img.onload = () => {
+ parent.postMessage("done", "*");
+ };
+ document.body.appendChild(img);
+}
+
+onmessage = event => {
+ switch (event.data) {
+ case "tracking":
+ createIframe("https://trackertest.org/");
+ break;
+ case "socialtracking":
+ createIframe(
+ "https://social-tracking.example.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs"
+ );
+ break;
+ case "cryptomining":
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ createIframe("http://cryptomining.example.com/");
+ break;
+ case "fingerprinting":
+ createIframe("https://fingerprinting.example.com/");
+ break;
+ case "more-tracking":
+ createIframe("https://itisatracker.org/");
+ break;
+ case "more-tracking-2":
+ createIframe("https://tracking.example.com/");
+ break;
+ case "cookie":
+ createIframe(
+ "https://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs"
+ );
+ break;
+ case "first-party-cookie":
+ // Since the content blocking log doesn't seem to get updated for
+ // top-level cookies right now, we just create an iframe with the
+ // first party domain...
+ createIframe(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/cookieServer.sjs"
+ );
+ break;
+ case "third-party-cookie":
+ createIframe(
+ "https://test1.example.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs"
+ );
+ break;
+ case "image":
+ createImage(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs?type=image-no-cookie"
+ );
+ break;
+ case "window-open":
+ window.win = window.open(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://trackertest.org/browser/browser/base/content/test/protectionsUI/cookieServer.sjs",
+ "_blank",
+ "width=100,height=100"
+ );
+ break;
+ case "window-close":
+ window.win.close();
+ window.win = null;
+ break;
+ }
+};
diff --git a/browser/base/content/test/protectionsUI/trackingPage.html b/browser/base/content/test/protectionsUI/trackingPage.html
new file mode 100644
index 0000000000..60ee20203b
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/trackingPage.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ <script src="trackingAPI.js" type="text/javascript"></script>
+ </head>
+ <body>
+ <iframe src="http://trackertest.org/"></iframe>
+ </body>
+</html>