From 522131845a9331828543e6ba684d2f0229d12142 Mon Sep 17 00:00:00 2001
From: Paul Zuehlcke <pbz@mozilla.com>
Date: Tue, 13 Apr 2021 16:11:21 +0000
Subject: [PATCH] Bug 1699668 - Tests for shim warning.
 r=webcompat-reviewers,johannh,denschub

Differential Revision: https://phabricator.services.mozilla.com/D109718
---
 .../content/test/protectionsUI/browser.ini    |   1 +
 .../protectionsUI/browser_protectionsUI.js    |   2 +-
 .../protectionsUI/browser_protectionsUI_3.js  |  18 +-
 .../browser_protectionsUI_animation_2.js      |  16 +-
 .../browser_protectionsUI_categories.js       |   4 +-
 .../browser_protectionsUI_cryptominers.js     |   5 +
 .../browser_protectionsUI_fingerprinters.js   |   5 +
 .../browser_protectionsUI_open_preferences.js |   2 +-
 ...browser_protectionsUI_pbmode_exceptions.js |   7 +-
 .../browser_protectionsUI_socialtracking.js   |   5 +
 .../browser_protectionsUI_state.js            |  19 +-
 .../browser_protectionsUI_subview_shim.js     | 399 ++++++++++++++++++
 .../browser_protectionsUI_telemetry.js        |   3 +-
 .../browser_protectionsUI_trackers_subview.js |   6 +-
 .../content/test/protectionsUI/trackingAPI.js |   3 +
 .../webcompat/tests/browser/head.js           |   3 +-
 .../tests/UrlClassifierTestUtils.jsm          |  58 +++
 tools/lint/rejected-words.yml                 |   1 +
 18 files changed, 533 insertions(+), 24 deletions(-)
 create mode 100644 browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js

diff --git a/browser/base/content/test/protectionsUI/browser.ini b/browser/base/content/test/protectionsUI/browser.ini
index a73ec7b50fc75..cda174e45a37c 100644
--- a/browser/base/content/test/protectionsUI/browser.ini
+++ b/browser/base/content/test/protectionsUI/browser.ini
@@ -40,3 +40,4 @@ support-files =
 [browser_protectionsUI_state_reset.js]
 [browser_protectionsUI_telemetry.js]
 [browser_protectionsUI_trackers_subview.js]
+[browser_protectionsUI_subview_shim.js]
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI.js b/browser/base/content/test/protectionsUI/browser_protectionsUI.js
index 265b1bce7837b..7e347ecb1dfe0 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI.js
@@ -617,7 +617,7 @@ add_task(async function testNumberOfBlockedTrackers() {
 
 add_task(async function testSubViewTelemetry() {
   let items = [
-    ["protections-popup-category-tracking-protection", "trackers"],
+    ["protections-popup-category-trackers", "trackers"],
     ["protections-popup-category-socialblock", "social"],
     ["protections-popup-category-cookies", "cookies"],
     ["protections-popup-category-cryptominers", "cryptominers"],
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_3.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_3.js
index 7621a0c6bec07..1a747e054b389 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_3.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_3.js
@@ -15,8 +15,13 @@ registerCleanupFunction(function() {
 });
 
 add_task(async function testNormalBrowsing() {
-  let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
-  ok(TrackingProtection, "TP is attached to the browser window");
+  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);
@@ -35,8 +40,13 @@ add_task(async function testPrivateBrowsing() {
   let privateWin = await BrowserTestUtils.openNewBrowserWindow({
     private: true,
   });
-  let TrackingProtection = privateWin.gBrowser.ownerGlobal.TrackingProtection;
-  ok(TrackingProtection, "TP is attached to the browser window");
+  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);
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_animation_2.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_animation_2.js
index 517655fcc9580..876cba897a441 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_animation_2.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_animation_2.js
@@ -212,9 +212,13 @@ add_task(async function testNormalBrowsing() {
     gProtectionsHandler,
     "gProtectionsHandler is attached to the browser window"
   );
-  let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+
+  let {
+    TrackingProtection,
+  } = gBrowser.ownerGlobal.gProtectionsHandler.blockers;
   ok(TrackingProtection, "TP is attached to the browser window");
-  let ThirdPartyCookies = gBrowser.ownerGlobal.ThirdPartyCookies;
+
+  let { ThirdPartyCookies } = gBrowser.ownerGlobal.gProtectionsHandler.blockers;
   ok(ThirdPartyCookies, "TPC is attached to the browser window");
 
   Services.prefs.setBoolPref(TP_PREF, true);
@@ -242,9 +246,13 @@ add_task(async function testPrivateBrowsing() {
     gProtectionsHandler,
     "gProtectionsHandler is attached to the private window"
   );
-  let TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+  let {
+    TrackingProtection,
+  } = tabbrowser.ownerGlobal.gProtectionsHandler.blockers;
   ok(TrackingProtection, "TP is attached to the private window");
-  let ThirdPartyCookies = tabbrowser.ownerGlobal.ThirdPartyCookies;
+  let {
+    ThirdPartyCookies,
+  } = tabbrowser.ownerGlobal.gProtectionsHandler.blockers;
   ok(ThirdPartyCookies, "TPC is attached to the browser window");
 
   Services.prefs.setBoolPref(TP_PB_PREF, true);
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js
index a2b7c3716ead1..f8a327c9e1754 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_categories.js
@@ -182,7 +182,7 @@ add_task(async function testCategorySections() {
     await closeProtectionsPanel();
 
     let categoryItems = [
-      "protections-popup-category-tracking-protection",
+      "protections-popup-category-trackers",
       "protections-popup-category-socialblock",
       "protections-popup-category-cookies",
       "protections-popup-category-cryptominers",
@@ -234,7 +234,7 @@ add_task(async function testCategorySections() {
  */
 add_task(async function testCategorySectionInitial() {
   let categoryItems = [
-    "protections-popup-category-tracking-protection",
+    "protections-popup-category-trackers",
     "protections-popup-category-socialblock",
     "protections-popup-category-cookies",
     "protections-popup-category-cryptominers",
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js
index 0ab558a0505d7..8be2103071aec 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js
@@ -153,6 +153,11 @@ async function testSubview(hasException) {
   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];
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js
index accf84ccd564d..441b465853dad 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js
@@ -228,6 +228,11 @@ async function testSubview(hasException) {
   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];
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js
index 992d5538b9ad6..0cad097569092 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js
@@ -67,7 +67,7 @@ add_task(async function testOpenPreferencesFromTrackersSubview() {
   await openProtectionsPanel();
 
   let categoryItem = document.getElementById(
-    "protections-popup-category-tracking-protection"
+    "protections-popup-category-trackers"
   );
 
   // Explicitly waiting for the category item becoming visible.
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js
index e3191a0f80656..53db3236bb4d8 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js
@@ -91,7 +91,9 @@ add_task(async function testExceptionAddition() {
 
   gProtectionsHandler = browser.ownerGlobal.gProtectionsHandler;
   ok(gProtectionsHandler, "CB is attached to the private window");
-  TrackingProtection = browser.ownerGlobal.TrackingProtection;
+
+  TrackingProtection =
+    browser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the private window");
 
   Services.prefs.setBoolPref(TP_PB_PREF, true);
@@ -135,7 +137,8 @@ add_task(async function testExceptionPersistence() {
 
   gProtectionsHandler = browser.ownerGlobal.gProtectionsHandler;
   ok(gProtectionsHandler, "CB is attached to the private window");
-  TrackingProtection = browser.ownerGlobal.TrackingProtection;
+  TrackingProtection =
+    browser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the private window");
 
   ok(TrackingProtection.enabled, "TP is still enabled");
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js
index 65580ac0cb38e..93a4b0dfc0e0a 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js
@@ -159,6 +159,11 @@ async function testSubview(hasException) {
   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];
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js
index d6a96e9c9e4bf..123f61df6dd9f 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_state.js
@@ -88,7 +88,7 @@ async function testBenignPage() {
     "Cookie restrictions category is not found"
   );
   ok(
-    notFound("protections-popup-category-tracking-protection"),
+    notFound("protections-popup-category-trackers"),
     "Trackers category is not found"
   );
   await closeProtectionsPanel(win);
@@ -125,7 +125,7 @@ async function testBenignPageWithException() {
     "Cookie restrictions category is not found"
   );
   ok(
-    notFound("protections-popup-category-tracking-protection"),
+    notFound("protections-popup-category-trackers"),
     "Trackers category is not found"
   );
   await closeProtectionsPanel(win);
@@ -170,7 +170,7 @@ async function testTrackingPage(window) {
 
   await openProtectionsPanel(false, window);
   ok(
-    !notFound("protections-popup-category-tracking-protection"),
+    !notFound("protections-popup-category-trackers"),
     "Trackers category is detected"
   );
   if (gTrackingPageURL == COOKIE_PAGE) {
@@ -213,7 +213,7 @@ async function testTrackingPageUnblocked(blockedByTP, window) {
 
   await openProtectionsPanel(false, window);
   ok(
-    !notFound("protections-popup-category-tracking-protection"),
+    !notFound("protections-popup-category-trackers"),
     "Trackers category is detected"
   );
   if (gTrackingPageURL == COOKIE_PAGE) {
@@ -284,7 +284,9 @@ add_task(async function testNormalBrowsing() {
     gProtectionsHandler,
     "gProtectionsHandler is attached to the browser window"
   );
-  TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+
+  TrackingProtection =
+    gBrowser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the browser window");
   is(
     TrackingProtection.enabled,
@@ -323,7 +325,9 @@ add_task(async function testPrivateBrowsing() {
     gProtectionsHandler,
     "gProtectionsHandler is attached to the private window"
   );
-  TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+
+  TrackingProtection =
+    tabbrowser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the private window");
   is(
     TrackingProtection.enabled,
@@ -355,7 +359,8 @@ add_task(async function testThirdPartyCookies() {
     gProtectionsHandler,
     "gProtectionsHandler is attached to the browser window"
   );
-  ThirdPartyCookies = gBrowser.ownerGlobal.ThirdPartyCookies;
+  ThirdPartyCookies =
+    gBrowser.ownerGlobal.gProtectionsHandler.blockers.ThirdPartyCookies;
   ok(ThirdPartyCookies, "TP is attached to the browser window");
   is(
     ThirdPartyCookies.enabled,
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 0000000000000..736b008253227
--- /dev/null
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js
@@ -0,0 +1,399 @@
+/* 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 =
+  "http://example.net/browser/browser/base/content/test/protectionsUI/trackingPage.html";
+
+add_task(async function setup() {
+  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: {
+      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
index e7e85d1fbf11e..70cc24b7fd16e 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_telemetry.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_telemetry.js
@@ -33,7 +33,8 @@ add_task(async function setup() {
   await UrlClassifierTestUtils.addTestTrackers();
   Services.prefs.setBoolPref(DTSCBN_PREF, true);
 
-  let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+  let TrackingProtection =
+    gBrowser.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the browser window");
   ok(!TrackingProtection.enabled, "TP is not enabled");
 
diff --git a/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js b/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js
index a229b4a1ecafa..3dea57cb75fd3 100644
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_trackers_subview.js
@@ -33,7 +33,7 @@ async function assertSitesListed(blocked) {
   await openProtectionsPanel();
 
   let categoryItem = document.getElementById(
-    "protections-popup-category-tracking-protection"
+    "protections-popup-category-trackers"
   );
 
   // Explicitly waiting for the category item becoming visible.
@@ -49,6 +49,10 @@ async function assertSitesListed(blocked) {
 
   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");
 
diff --git a/browser/base/content/test/protectionsUI/trackingAPI.js b/browser/base/content/test/protectionsUI/trackingAPI.js
index cc15eacf5620c..b515358a3281b 100644
--- a/browser/base/content/test/protectionsUI/trackingAPI.js
+++ b/browser/base/content/test/protectionsUI/trackingAPI.js
@@ -32,6 +32,9 @@ onmessage = event => {
     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"
diff --git a/browser/extensions/webcompat/tests/browser/head.js b/browser/extensions/webcompat/tests/browser/head.js
index 7bd1dde9504b1..9935d3747154e 100644
--- a/browser/extensions/webcompat/tests/browser/head.js
+++ b/browser/extensions/webcompat/tests/browser/head.js
@@ -41,7 +41,8 @@ async function testShimRuns(
     waitForLoad: true,
   });
 
-  const TrackingProtection = tab.ownerGlobal.TrackingProtection;
+  const TrackingProtection =
+    tab.ownerGlobal.gProtectionsHandler.blockers.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the tab");
   ok(TrackingProtection.enabled, "TP is enabled");
 
diff --git a/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.jsm b/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.jsm
index 400582d1d74f3..e827199276347 100644
--- a/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.jsm
+++ b/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.jsm
@@ -233,4 +233,62 @@ var UrlClassifierTestUtils = {
       }
     });
   },
+
+  /**
+   * Handle the next "urlclassifier-before-block-channel" event.
+   * @param {Object} options
+   * @param {String} [options.filterOrigin] - Only handle event for channels
+   * with matching origin.
+   * @param {function} [options.onBeforeBlockChannel] - Optional callback for
+   * the event. Called before acting on the channel.
+   * @param {("allow"|"replace")} [options.action] - Whether to allow or replace
+   * the channel.
+   * @returns {Promise} - Resolves once event has been handled.
+   */
+  handleBeforeBlockChannel({
+    filterOrigin = null,
+    onBeforeBlockChannel,
+    action,
+  }) {
+    if (action && action != "allow" && action != "replace") {
+      throw new Error("Invalid action " + action);
+    }
+    let channelClassifierService = Cc[
+      "@mozilla.org/url-classifier/channel-classifier-service;1"
+    ].getService(Ci.nsIChannelClassifierService);
+
+    let resolver;
+    let promise = new Promise(resolve => {
+      resolver = resolve;
+    });
+
+    let observer = {
+      observe(subject, topic) {
+        if (topic != "urlclassifier-before-block-channel") {
+          return;
+        }
+        let channel = subject.QueryInterface(Ci.nsIUrlClassifierBlockedChannel);
+
+        if (filterOrigin) {
+          let { url } = channel;
+          let { origin } = new URL(url);
+          if (filterOrigin != origin) {
+            return;
+          }
+        }
+
+        if (onBeforeBlockChannel) {
+          onBeforeBlockChannel(channel);
+        }
+        if (action) {
+          channel[action]();
+        }
+
+        channelClassifierService.removeListener(observer);
+        resolver();
+      },
+    };
+    channelClassifierService.addListener(observer);
+    return promise;
+  },
 };
diff --git a/tools/lint/rejected-words.yml b/tools/lint/rejected-words.yml
index 51883067254f1..ead8389124a3b 100644
--- a/tools/lint/rejected-words.yml
+++ b/tools/lint/rejected-words.yml
@@ -38,6 +38,7 @@ avoid-blacklist-and-whitelist:
         - browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js
         - browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js
         - browser/base/content/test/protectionsUI/browser_protectionsUI_state.js
+        - browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js
         - browser/base/content/test/siteIdentity/browser_no_mcb_for_loopback.js
         - browser/base/content/test/siteIdentity/browser_no_mcb_for_onions.js
         - browser/base/content/test/static/browser_all_files_referenced.js
-- 
GitLab