browser_protectionsUI_subview_shim.js (11212B)
1 /* eslint-disable mozilla/no-arbitrary-setTimeout */ 2 /* Any copyright is dedicated to the Public Domain. 3 * http://creativecommons.org/publicdomain/zero/1.0/ */ 4 5 "use strict"; 6 7 /** 8 * Tests the warning and list indicators that are shown in the protections panel 9 * subview when a tracking channel is allowed via the 10 * "urlclassifier-before-block-channel" event. 11 */ 12 13 // Choose origin so that all tracking origins used are third-parties. 14 const TRACKING_PAGE = 15 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 16 "http://example.net/browser/browser/base/content/test/protectionsUI/trackingPage.html"; 17 18 add_setup(async function () { 19 await SpecialPowers.pushPrefEnv({ 20 set: [ 21 ["privacy.trackingprotection.enabled", true], 22 ["privacy.trackingprotection.annotate_channels", true], 23 ["privacy.trackingprotection.cryptomining.enabled", true], 24 ["privacy.trackingprotection.socialtracking.enabled", true], 25 ["privacy.trackingprotection.fingerprinting.enabled", true], 26 ["privacy.socialtracking.block_cookies.enabled", true], 27 // Allowlist trackertest.org loaded by default in trackingPage.html 28 ["urlclassifier.trackingSkipURLs", "*://trackertest.org/*"], 29 ["urlclassifier.trackingAnnotationSkipURLs", "*://trackertest.org/*"], 30 // Additional denylisted hosts. 31 [ 32 "urlclassifier.trackingAnnotationTable.testEntries", 33 "tracking.example.com", 34 ], 35 [ 36 "urlclassifier.features.cryptomining.blacklistHosts", 37 "cryptomining.example.com", 38 ], 39 [ 40 "urlclassifier.features.cryptomining.annotate.blacklistHosts", 41 "cryptomining.example.com", 42 ], 43 [ 44 "urlclassifier.features.fingerprinting.blacklistHosts", 45 "fingerprinting.example.com", 46 ], 47 [ 48 "urlclassifier.features.fingerprinting.annotate.blacklistHosts", 49 "fingerprinting.example.com", 50 ], 51 ], 52 }); 53 54 await UrlClassifierTestUtils.addTestTrackers(); 55 registerCleanupFunction(() => { 56 UrlClassifierTestUtils.cleanupTestTrackers(); 57 }); 58 }); 59 60 async function assertSubViewState(category, expectedState) { 61 await openProtectionsPanel(); 62 63 // Sort the expected state by origin and transform it into an array. 64 let expectedStateSorted = Object.keys(expectedState) 65 .sort() 66 .reduce((stateArr, key) => { 67 let obj = expectedState[key]; 68 obj.origin = key; 69 stateArr.push(obj); 70 return stateArr; 71 }, []); 72 73 if (!expectedStateSorted.length) { 74 ok( 75 BrowserTestUtils.isVisible( 76 document.getElementById( 77 "protections-popup-no-trackers-found-description" 78 ) 79 ), 80 "No Trackers detected should be shown" 81 ); 82 return; 83 } 84 85 let categoryItem = document.getElementById( 86 `protections-popup-category-${category}` 87 ); 88 89 // Explicitly waiting for the category item becoming visible. 90 await TestUtils.waitForCondition(() => { 91 return BrowserTestUtils.isVisible(categoryItem); 92 }); 93 94 ok( 95 BrowserTestUtils.isVisible(categoryItem), 96 `${category} category item is visible` 97 ); 98 99 ok(!categoryItem.disabled, `${category} category item is enabled`); 100 101 let subView = document.getElementById(`protections-popup-${category}View`); 102 let viewShown = BrowserTestUtils.waitForEvent(subView, "ViewShown"); 103 categoryItem.click(); 104 await viewShown; 105 106 ok(true, `${category} subView was shown`); 107 108 info("Testing tracker list"); 109 110 // Get the listed trackers in the UI and sort them by origin. 111 let items = Array.from( 112 subView.querySelectorAll( 113 `#protections-popup-${category}View-list .protections-popup-list-item` 114 ) 115 ).sort((a, b) => { 116 let originA = a.querySelector("label").value; 117 let originB = b.querySelector("label").value; 118 return originA.localeCompare(originB); 119 }); 120 121 is( 122 items.length, 123 expectedStateSorted.length, 124 "List has expected amount of entries" 125 ); 126 127 for (let i = 0; i < expectedStateSorted.length; i += 1) { 128 let expected = expectedStateSorted[i]; 129 let item = items[i]; 130 131 let label = item.querySelector(".protections-popup-list-host-label"); 132 ok(label, "Item has label."); 133 is(label.tooltipText, expected.origin, "Label has correct tooltip."); 134 is(label.value, expected.origin, "Label has correct text."); 135 136 is( 137 item.classList.contains("allowed"), 138 !expected.block, 139 "Item has allowed class if tracker is not blocked" 140 ); 141 142 let shimAllowIndicator = item.querySelector( 143 ".protections-popup-list-host-shim-allow-indicator" 144 ); 145 146 if (expected.shimAllow) { 147 is(item.childNodes.length, 2, "Item has two childNodes."); 148 ok(shimAllowIndicator, "Item has shim allow indicator icon."); 149 ok( 150 shimAllowIndicator.tooltipText, 151 "Shim allow indicator icon has tooltip text" 152 ); 153 } else { 154 is(item.childNodes.length, 1, "Item has one childNode."); 155 ok(!shimAllowIndicator, "Item does not have shim allow indicator icon."); 156 } 157 } 158 159 let shimAllowSection = document.getElementById( 160 `protections-popup-${category}View-shim-allow-hint` 161 ); 162 ok(shimAllowSection, `Category ${category} has shim-allow hint.`); 163 164 if (Object.values(expectedState).some(entry => entry.shimAllow)) { 165 BrowserTestUtils.isVisible(shimAllowSection, "Shim allow hint is visible."); 166 } else { 167 BrowserTestUtils.isHidden(shimAllowSection, "Shim allow hint is hidden."); 168 } 169 170 await closeProtectionsPanel(); 171 } 172 173 async function runTestForCategoryAndState(category, action) { 174 // Maps the protection categories to the test tracking origins defined in 175 // ./trackingAPI.js and the UI class identifiers to look for in the 176 // protections UI. 177 let categoryToTestData = { 178 tracking: { 179 apiMessage: "more-tracking", 180 origin: "https://itisatracker.org", 181 elementId: "trackers", 182 }, 183 socialtracking: { 184 origin: "https://social-tracking.example.org", 185 elementId: "socialblock", 186 }, 187 cryptomining: { 188 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 189 origin: "http://cryptomining.example.com", 190 elementId: "cryptominers", 191 }, 192 fingerprinting: { 193 origin: "https://fingerprinting.example.com", 194 elementId: "fingerprinters", 195 }, 196 }; 197 198 let promise = BrowserTestUtils.openNewForegroundTab({ 199 url: TRACKING_PAGE, 200 gBrowser, 201 }); 202 // Wait for the tab to load and the initial blocking events from the 203 // classifier. 204 let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); 205 206 let { 207 origin: trackingOrigin, 208 elementId: categoryElementId, 209 apiMessage, 210 } = categoryToTestData[category]; 211 if (!apiMessage) { 212 apiMessage = category; 213 } 214 215 // For allow or replace actions we need to hook into before-block-channel. 216 // If we don't hook into the event, the tracking channel will be blocked. 217 let beforeBlockChannelPromise; 218 if (action != "block") { 219 beforeBlockChannelPromise = UrlClassifierTestUtils.handleBeforeBlockChannel( 220 { 221 filterOrigin: trackingOrigin, 222 action, 223 } 224 ); 225 } 226 // Load the test tracker matching the category. 227 await SpecialPowers.spawn( 228 tab.linkedBrowser, 229 [{ apiMessage }], 230 function (args) { 231 content.postMessage(args.apiMessage, "*"); 232 } 233 ); 234 await beforeBlockChannelPromise; 235 236 // Next, test if the UI state is correct for the given category and action. 237 let expectedState = {}; 238 expectedState[trackingOrigin] = { 239 block: action == "block", 240 shimAllow: action == "allow", 241 }; 242 243 await assertSubViewState(categoryElementId, expectedState); 244 245 BrowserTestUtils.removeTab(tab); 246 } 247 248 /** 249 * Test mixed allow/block/replace states for the tracking protection category. 250 * 251 * @param {object} options - States to test. 252 * @param {boolean} options.block - Test tracker block state. 253 * @param {boolean} options.allow - Test tracker allow state. 254 * @param {boolean} options.replace - Test tracker replace state. 255 */ 256 async function runTestMixed({ block, allow, replace }) { 257 const ORIGIN_BLOCK = "https://trackertest.org"; 258 const ORIGIN_ALLOW = "https://itisatracker.org"; 259 const ORIGIN_REPLACE = "https://tracking.example.com"; 260 261 let promise = BrowserTestUtils.openNewForegroundTab({ 262 url: TRACKING_PAGE, 263 gBrowser, 264 }); 265 266 let [tab] = await Promise.all([promise, waitForContentBlockingEvent()]); 267 268 if (block) { 269 // Temporarily remove trackertest.org from the allowlist. 270 await SpecialPowers.pushPrefEnv({ 271 clear: [ 272 ["urlclassifier.trackingSkipURLs"], 273 ["urlclassifier.trackingAnnotationSkipURLs"], 274 ], 275 }); 276 let blockEventPromise = waitForContentBlockingEvent(); 277 await SpecialPowers.spawn(tab.linkedBrowser, [], function () { 278 content.postMessage("tracking", "*"); 279 }); 280 await blockEventPromise; 281 await SpecialPowers.popPrefEnv(); 282 } 283 284 if (allow) { 285 let promiseEvent = waitForContentBlockingEvent(); 286 let promiseAllow = UrlClassifierTestUtils.handleBeforeBlockChannel({ 287 filterOrigin: ORIGIN_ALLOW, 288 action: "allow", 289 }); 290 291 await SpecialPowers.spawn(tab.linkedBrowser, [], function () { 292 content.postMessage("more-tracking", "*"); 293 }); 294 295 await promiseAllow; 296 await promiseEvent; 297 } 298 299 if (replace) { 300 let promiseReplace = UrlClassifierTestUtils.handleBeforeBlockChannel({ 301 filterOrigin: ORIGIN_REPLACE, 302 action: "replace", 303 }); 304 305 await SpecialPowers.spawn(tab.linkedBrowser, [], function () { 306 content.postMessage("more-tracking-2", "*"); 307 }); 308 309 await promiseReplace; 310 } 311 312 let expectedState = {}; 313 314 if (block) { 315 expectedState[ORIGIN_BLOCK] = { 316 shimAllow: false, 317 block: true, 318 }; 319 } 320 321 if (replace) { 322 expectedState[ORIGIN_REPLACE] = { 323 shimAllow: false, 324 block: false, 325 }; 326 } 327 328 if (allow) { 329 expectedState[ORIGIN_ALLOW] = { 330 shimAllow: true, 331 block: false, 332 }; 333 } 334 335 // Check the protection categories subview with the block list. 336 await assertSubViewState("trackers", expectedState); 337 338 BrowserTestUtils.removeTab(tab); 339 } 340 341 add_task(async function testNoShim() { 342 await runTestMixed({ 343 allow: false, 344 replace: false, 345 block: false, 346 }); 347 await runTestMixed({ 348 allow: false, 349 replace: false, 350 block: true, 351 }); 352 }); 353 354 add_task(async function testShimAllow() { 355 await runTestMixed({ 356 allow: true, 357 replace: false, 358 block: false, 359 }); 360 await runTestMixed({ 361 allow: true, 362 replace: false, 363 block: true, 364 }); 365 }); 366 367 add_task(async function testShimReplace() { 368 await runTestMixed({ 369 allow: false, 370 replace: true, 371 block: false, 372 }); 373 await runTestMixed({ 374 allow: false, 375 replace: true, 376 block: true, 377 }); 378 }); 379 380 add_task(async function testShimMixed() { 381 await runTestMixed({ 382 allow: true, 383 replace: true, 384 block: true, 385 }); 386 }); 387 388 add_task(async function testShimCategorySubviews() { 389 let categories = [ 390 "tracking", 391 "socialtracking", 392 "cryptomining", 393 "fingerprinting", 394 ]; 395 for (let category of categories) { 396 for (let action of ["block", "allow", "replace"]) { 397 info(`Test category subview. category: ${category}, action: ${action}`); 398 await runTestForCategoryAndState(category, action); 399 } 400 } 401 });