forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			401 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			401 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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);
 | |
|     }
 | |
|   }
 | |
| });
 | 
