summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/protectionsUI
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /browser/base/content/test/protectionsUI
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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.ini63
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI.js713
-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.js300
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_cookie_banner.js475
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_cookies_subview.js537
-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.js39
-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_milestones.js95
-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.js175
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js404
-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.js405
-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.js403
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_telemetry.js89
-rw-r--r--browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js134
-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/head.js221
-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
38 files changed, 6299 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.ini b/browser/base/content/test/protectionsUI/browser.ini
new file mode 100644
index 0000000000..acf3f7125b
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser.ini
@@ -0,0 +1,63 @@
+[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
+skip-if =
+ os == 'linux' && !debug && tsan # Bug 1675107
+[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
+[browser_protectionsUI_icon_state.js]
+https_first_disabled = true
+[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 || asan # Bug 1546797
+[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_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..ea8737f28a
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI.js
@@ -0,0 +1,713 @@
+/* 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"
+);
+
+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();
+ });
+});
+
+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"
+ );
+ is(buttonEvents.length, 1, "recorded telemetry for opening the popup");
+
+ // Check the visibility of the "Site not working?" link.
+ ok(
+ BrowserTestUtils.is_visible(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageLink
+ ),
+ "The 'Site not working?' link should be visible."
+ );
+
+ // The 'Site Fixed?' link should be hidden.
+ ok(
+ BrowserTestUtils.is_hidden(
+ 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("enabled"),
+ "TP Switch should be enabled"
+ );
+ let popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ gProtectionsHandler._protectionsPopupTPSwitch.click();
+
+ // The 'Site not working?' link should be hidden after clicking the TP switch.
+ ok(
+ BrowserTestUtils.is_hidden(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageLink
+ ),
+ "The 'Site not working?' link should be hidden after TP switch turns to off."
+ );
+ // Same for the 'Site Fixed?' link
+ ok(
+ BrowserTestUtils.is_hidden(
+ 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 popupShownPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popupshown"
+ );
+ popuphiddenPromise = BrowserTestUtils.waitForEvent(
+ gProtectionsHandler._protectionsPopup,
+ "popuphidden"
+ );
+
+ await browserLoadedPromise;
+
+ // Wait until the toast is shown and hidden.
+ await popupShownPromise;
+ await popuphiddenPromise;
+
+ await openProtectionsPanel();
+ ok(
+ !gProtectionsHandler._protectionsPopupTPSwitch.hasAttribute("enabled"),
+ "TP Switch should be disabled"
+ );
+
+ // The 'Site not working?' link should be hidden if the TP is off.
+ ok(
+ BrowserTestUtils.is_hidden(
+ 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.is_visible(
+ 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);
+ gProtectionsHandler._protectionsPopupTPSwitch.click();
+
+ ok(
+ BrowserTestUtils.is_hidden(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageLink
+ ),
+ `The 'Site not working?' link should be still hidden after toggling TP
+ switch to on from off.`
+ );
+ ok(
+ BrowserTestUtils.is_hidden(
+ gProtectionsHandler._protectionsPopupTPSwitchBreakageFixedLink
+ ),
+ "The 'Site Fixed?' link should be hidden."
+ );
+
+ await browserLoadedPromise;
+ 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.is_visible(item),
+ `The section '${item.id}' is hidden in the toast.`
+ );
+ } else {
+ ok(
+ BrowserTestUtils.is_visible(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.
+ gProtectionsHandler._protectionsPopupTPSwitch.click();
+
+ // 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"
+ );
+ document.getElementById("protections-popup-mainView-panel-header").click();
+ 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);
+ gProtectionsHandler._protectionsPopupTPSwitch.click();
+
+ // 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.is_hidden(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.is_visible(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.is_visible(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.
+ gProtectionsHandler._protectionsPopupTPSwitch.click();
+ 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..b647b44d64
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js
@@ -0,0 +1,300 @@
+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 { CustomizableUITestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CustomizableUITestUtils.sys.mjs"
+);
+
+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"));
+ await TestUtils.waitForCondition(
+ () =>
+ categoryLabel.textContent ==
+ gNavigatorBundle.getString(
+ "contentBlocking.cookies.blockingAll2.label"
+ ),
+ "The category label has updated correctly"
+ );
+ ok(
+ categoryLabel.textContent ==
+ gNavigatorBundle.getString(
+ "contentBlocking.cookies.blockingAll2.label"
+ )
+ );
+
+ 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"));
+ await TestUtils.waitForCondition(
+ () =>
+ categoryLabel.textContent ==
+ gNavigatorBundle.getString(
+ "contentBlocking.cookies.blocking3rdParty2.label"
+ ),
+ "The category label has updated correctly"
+ );
+ ok(
+ categoryLabel.textContent ==
+ gNavigatorBundle.getString(
+ "contentBlocking.cookies.blocking3rdParty2.label"
+ )
+ );
+
+ 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"));
+ await TestUtils.waitForCondition(
+ () =>
+ categoryLabel.textContent ==
+ gNavigatorBundle.getString(
+ "contentBlocking.cookies.blockingTrackers3.label"
+ ),
+ "The category label has updated correctly"
+ );
+ ok(
+ categoryLabel.textContent ==
+ gNavigatorBundle.getString(
+ "contentBlocking.cookies.blockingTrackers3.label"
+ )
+ );
+
+ 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 ==
+ gNavigatorBundle.getString(
+ "contentBlocking.cookies.blockingTrackers3.label"
+ ),
+ "The category label has updated correctly"
+ );
+ ok(
+ categoryLabel.textContent ==
+ gNavigatorBundle.getString(
+ "contentBlocking.cookies.blockingTrackers3.label"
+ )
+ );
+
+ 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}`);
+
+ ok(
+ 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);
+
+ 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() {
+ 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..1d71f718fb
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_cookie_banner.js
@@ -0,0 +1,475 @@
+/* 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.is_visible(el.section),
+ expectVisible,
+ `Cookie banner section should be ${
+ expectVisible ? "visible" : "not visible"
+ }.`
+ );
+ is(
+ BrowserTestUtils.is_visible(el.sectionSeparator),
+ expectVisible,
+ `Cookie banner section separator should be ${
+ expectVisible ? "visible" : "not visible"
+ }.`
+ );
+ is(
+ BrowserTestUtils.is_visible(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") {
+ ok(el.section.dataset.state == "detected", "CBH switch is set to ON");
+
+ ok(BrowserTestUtils.is_visible(el.labelON), "ON label should be visible");
+ ok(
+ !BrowserTestUtils.is_visible(el.labelOFF),
+ "OFF label should not be visible"
+ );
+ ok(
+ !BrowserTestUtils.is_visible(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") {
+ ok(el.section.dataset.state == "site-disabled", "CBH switch is set to OFF");
+
+ ok(
+ !BrowserTestUtils.is_visible(el.labelON),
+ "ON label should not be visible"
+ );
+ ok(BrowserTestUtils.is_visible(el.labelOFF), "OFF label should be visible");
+ ok(
+ !BrowserTestUtils.is_visible(el.labelUNDETECTED),
+ "UNDETECTED label should not be visible"
+ );
+
+ is(
+ pref,
+ MODE_DISABLED,
+ `There should be a per-site exception for ${currentURI.spec}.`
+ );
+ } else {
+ ok(el.section.dataset.state == "undetected", "CBH not supported for site");
+
+ ok(
+ !BrowserTestUtils.is_visible(el.labelON),
+ "ON label should not be visible"
+ );
+ ok(
+ !BrowserTestUtils.is_visible(el.labelOFF),
+ "OFF label should not be visible"
+ );
+ ok(
+ BrowserTestUtils.is_visible(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.is_visible(enableButton), "Enable button is visible");
+ enableButton.click();
+ } else {
+ ok(BrowserTestUtils.is_visible(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() {
+ // 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..5eb16dbac7
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_cookies_subview.js
@@ -0,0 +1,537 @@
+/* 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.is_visible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.is_visible(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.is_visible(listHeaders[0]),
+ "Only one header, should be hidden"
+ );
+ } else {
+ for (let header of listHeaders) {
+ ok(
+ BrowserTestUtils.is_visible(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.is_visible(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.is_visible(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.is_visible(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.is_visible(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.is_visible(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.is_visible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.is_visible(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.is_visible(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.is_visible(stateLabel), "State label is visible");
+ is(
+ stateLabel.value,
+ gNavigatorBundle.getString("contentBlocking.cookiesView.allowed.label"),
+ "State label has correct text"
+ );
+
+ let button = listItem.querySelector(
+ ".permission-popup-permission-remove-button"
+ );
+ ok(
+ BrowserTestUtils.is_visible(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.is_visible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.is_visible(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.is_visible(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.is_visible(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() {
+ 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.is_visible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.is_visible(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.is_visible(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..13fb3e08ee
--- /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.is_visible(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.is_visible(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.is_visible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.is_visible(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.is_visible(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..6b97b83087
--- /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.is_visible(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.is_visible(categoryItem)
+ );
+
+ ok(BrowserTestUtils.is_visible(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.is_visible(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.is_visible(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.is_visible(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..639d8982fc
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_fetch.js
@@ -0,0 +1,39 @@
+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) {
+ 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;
+
+ let gProtectionsHandler = newTabBrowser.ownerGlobal.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.textContent,
+ gNavigatorBundle.getString("trackingProtection.icon.activeTooltip2"),
+ "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..aaa6745628
--- /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.is_hidden(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.is_visible(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.is_visible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.is_visible(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.is_visible(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_milestones.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_milestones.js
new file mode 100644
index 0000000000..9909e5b876
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_milestones.js
@@ -0,0 +1,95 @@
+/* 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() {
+ // 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.is_visible(
+ gProtectionsHandler._protectionsPopupMilestonesText
+ ),
+ "Milestones section should be visible in the panel."
+ );
+
+ await closeProtectionsPanel();
+ await openProtectionsPanel();
+
+ ok(
+ BrowserTestUtils.is_visible(
+ 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.is_visible(
+ 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..c50e93b9d2
--- /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.is_visible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.is_visible(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.is_visible(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.is_visible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.is_visible(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.is_visible(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..ebe67ea0c0
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js
@@ -0,0 +1,175 @@
+/* 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.is_visible(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.textContent,
+ gNavigatorBundle.getString("trackingProtection.icon.activeTooltip2"),
+ "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.textContent,
+ gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip2"),
+ "correct tooltip"
+ );
+
+ ok(
+ BrowserTestUtils.is_visible(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..b5400954db
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js
@@ -0,0 +1,404 @@
+/* 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 PREF_REPORT_BREAKAGE_URL = "browser.contentblocking.reportBreakage.url";
+
+let { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+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();
+
+ 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(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",
+ ],
+ ],
+ });
+});
+
+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.is_visible(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.is_visible(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.is_hidden(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.is_visible(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",
+ ];
+ 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.is_visible(errorMessage)
+ );
+ is(
+ comments.value,
+ "This is a comment",
+ "Comment not cleared in case of an error"
+ );
+ gProtectionsHandler._protectionsPopup.hidePopup();
+ } else {
+ ok(BrowserTestUtils.is_hidden(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..539ac077ac
--- /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.is_hidden(
+ 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..8506016067
--- /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.is_visible(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.is_visible(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.is_visible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.is_visible(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.is_visible(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.is_visible(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.is_visible(categoryItem), "Item should be hidden");
+ ok(
+ !gProtectionsHandler._protectionsPopup.hasAttribute("detected"),
+ "trackers are not detected"
+ );
+ ok(
+ BrowserTestUtils.is_visible(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.is_visible(categoryItem), "Item should not be visible");
+ ok(
+ BrowserTestUtils.is_visible(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.is_visible(categoryItem), "Item should be visible");
+ ok(
+ !BrowserTestUtils.is_visible(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..b524d2d7c7
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js
@@ -0,0 +1,405 @@
+/*
+ * 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;
+
+const sBrandBundle = Services.strings.createBundle(
+ "chrome://branding/locale/brand.properties"
+);
+const sNoTrackerIconTooltip = gNavigatorBundle.getFormattedString(
+ "trackingProtection.icon.noTrackersDetectedTooltip",
+ [sBrandBundle.GetStringFromName("brandShortName")]
+);
+const sActiveIconTooltip = gNavigatorBundle.getString(
+ "trackingProtection.icon.activeTooltip2"
+);
+const sDisabledIconTooltip = gNavigatorBundle.getString(
+ "trackingProtection.icon.disabledTooltip2"
+);
+
+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.textContent,
+ sNoTrackerIconTooltip,
+ "correct tooltip"
+ );
+ ok(
+ BrowserTestUtils.is_visible(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.textContent,
+ sDisabledIconTooltip,
+ "correct tooltip"
+ );
+
+ ok(
+ !BrowserTestUtils.is_hidden(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.is_visible(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.textContent,
+ blockedByTP ? sActiveIconTooltip : sNoTrackerIconTooltip,
+ "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.textContent,
+ sDisabledIconTooltip,
+ "correct tooltip"
+ );
+
+ ok(
+ BrowserTestUtils.is_visible(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() {
+ 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..cf120a33da
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js
@@ -0,0 +1,403 @@
+/* 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.is_visible(
+ 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.is_visible(categoryItem);
+ });
+
+ ok(
+ BrowserTestUtils.is_visible(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.is_visible(
+ shimAllowSection,
+ "Shim allow hint is visible."
+ );
+ } else {
+ BrowserTestUtils.is_hidden(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_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..7fe52065ea
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js
@@ -0,0 +1,134 @@
+/* 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 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.is_visible(categoryItem);
+ });
+
+ ok(BrowserTestUtils.is_visible(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");
+
+ 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.is_visible(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.is_visible(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/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/head.js b/browser/base/content/test/protectionsUI/head.js
new file mode 100644
index 0000000000..28395ad732
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/head.js
@@ -0,0 +1,221 @@
+/* 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"
+);
+
+XPCOMUtils.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 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"
+ );
+
+ // 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
+ );
+
+ 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.loadURIString(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>