tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });