tor-browser

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

browser_interventions.js (10990B)


      1 "use strict";
      2 
      3 const { WebExtensionPolicy } = SpecialPowers.Cu.getGlobalForObject(
      4  SpecialPowers.Services
      5 );
      6 
      7 const ValidIssueList = [
      8  "blocked-content",
      9  "broken-audio",
     10  "broken-captcha",
     11  "broken-comments",
     12  "broken-cookie-banner",
     13  "broken-editor",
     14  "broken-font",
     15  "broken-images",
     16  "broken-interactive-elements",
     17  "broken-layout",
     18  "broken-login",
     19  "broken-map",
     20  "broken-meetings",
     21  "broken-printing",
     22  "broken-redirect",
     23  "broken-scrolling",
     24  "broken-videos",
     25  "broken-zooming",
     26  "desktop-layout-not-mobile",
     27  "extra-scrollbars",
     28  "firefox-blocked-completely",
     29  "frozen-tab",
     30  "incorrect-viewport-dimensions",
     31  "page-fails-to-load",
     32  "redirect-loop",
     33  "slow-performance",
     34  "unsupported-warning",
     35  "user-interface-frustration",
     36 ];
     37 
     38 function addon_url(path) {
     39  const uuid = WebExtensionPolicy.getByID(
     40    "webcompat@mozilla.org"
     41  ).mozExtensionHostname;
     42  return `moz-extension://${uuid}/${path}`;
     43 }
     44 
     45 async function check_path_exists(path) {
     46  try {
     47    await (await fetch(addon_url(path))).text();
     48  } catch (e) {
     49    return false;
     50  }
     51  return true;
     52 }
     53 
     54 function check_valid_array(a, key, id) {
     55  if (a === undefined) {
     56    return false;
     57  }
     58  const valid = Array.isArray(a);
     59  ok(valid, `if defined, ${key} is an array for id ${id}`);
     60  return valid;
     61 }
     62 
     63 // eslint-disable-next-line complexity
     64 add_task(async function test_json_data() {
     65  const addon = await AddonManager.getAddonByID("webcompat@mozilla.org");
     66  const addonURI = addon.getResourceURI();
     67  const checkableGlobalPrefs =
     68    await WebCompatExtension.getCheckableGlobalPrefs();
     69 
     70  const exports = {};
     71  Services.scriptloader.loadSubScript(
     72    addonURI.resolve("lib/intervention_helpers.js"),
     73    exports
     74  );
     75  Services.scriptloader.loadSubScript(
     76    addonURI.resolve("lib/custom_functions.js"),
     77    exports
     78  );
     79  const helpers = exports.InterventionHelpers;
     80  const custom_fns = exports.CUSTOM_FUNCTIONS;
     81 
     82  for (const [name, fn] of Object.entries(helpers.skip_if_functions)) {
     83    Assert.strictEqual(typeof fn, "function", `Skip-if ${name} is a function`);
     84  }
     85 
     86  for (const [name, { disable, enable }] of Object.entries(custom_fns)) {
     87    Assert.strictEqual(
     88      typeof enable,
     89      "function",
     90      `Custom function ${name} has enable function`
     91    );
     92    Assert.strictEqual(
     93      typeof disable,
     94      "function",
     95      `Custom function ${name} has disable function`
     96    );
     97  }
     98 
     99  const json = await (await fetch(addon_url("data/interventions.json"))).json();
    100  const ids = new Set();
    101  for (const [id, config] of Object.entries(json)) {
    102    const { bugs, hidden, interventions, label } = config;
    103    ok(!!id, `id key exists for intervention ${JSON.stringify(config)}`);
    104    if (id) {
    105      ok(!ids.has(id), `id ${id} is defined more than once`);
    106      ids.add(id);
    107    }
    108 
    109    if (hidden) {
    110      ok(
    111        hidden === false || hidden === true,
    112        `hidden key is true or false for id ${id}`
    113      );
    114    }
    115 
    116    ok(
    117      typeof label === "string" && !!label,
    118      `label key exists and is set for id ${id}`
    119    );
    120 
    121    ok(
    122      typeof bugs === "object" && Object.keys(bugs).length,
    123      `bugs key exists and has entries for id ${id}`
    124    );
    125    for (const [bug, { issue, blocks, matches }] of Object.entries(bugs)) {
    126      ok(
    127        typeof bug === "string" && bug == String(parseInt(bug)),
    128        `bug number is set properly for all bugs in id ${id}`
    129      );
    130 
    131      ok(
    132        ValidIssueList.includes(issue),
    133        `issue key exists and is set for all bugs in id ${id}`
    134      );
    135 
    136      ok(
    137        !interventions.find(i => i.content_scripts || i.ua_string) ||
    138          (!!matches && Array.isArray(matches) && matches.length),
    139        `matches key exists and is an array with items for id ${id}`
    140      );
    141      try {
    142        new MatchPatternSet(matches);
    143      } catch (e) {
    144        ok(false, `invalid matches entries for id ${id}: ${e}`);
    145      }
    146 
    147      if (blocks) {
    148        ok(
    149          Array.isArray(blocks) && matches.length,
    150          `matches key exists and is an array with items for id ${id}`
    151        );
    152        try {
    153          new MatchPatternSet(blocks);
    154        } catch (e) {
    155          ok(false, `invalid blocks entries for id ${id}: ${e}`);
    156        }
    157      }
    158    }
    159 
    160    const non_custom_names = [
    161      "content_scripts",
    162      "max_version",
    163      "min_version",
    164      "not_platforms",
    165      "platforms",
    166      "not_channels",
    167      "only_channels",
    168      "pref_check",
    169      "skip_if",
    170      "ua_string",
    171    ];
    172    let custom_found = false;
    173    for (let intervention of interventions) {
    174      for (const name in intervention) {
    175        const is_custom = name in custom_fns;
    176        const is_non_custom = non_custom_names.includes(name);
    177        ok(
    178          is_custom || is_non_custom,
    179          `key '${name}' is actually expected for id ${id}`
    180        );
    181        if (is_custom) {
    182          custom_found = true;
    183          const { details, optionalDetails } = custom_fns[name];
    184          for (const customArgs of intervention[name]) {
    185            for (const detailName in customArgs) {
    186              ok(
    187                details.includes(detailName) ||
    188                  optionalDetails.includes(detailName),
    189                `detail '${detailName}' is actually expected for custom function ${name} in id ${id}`
    190              );
    191            }
    192            for (const detailName of details) {
    193              ok(
    194                detailName in customArgs,
    195                `expected detail '${detailName}' is being passed to custom function ${name} in id ${id}`
    196              );
    197            }
    198          }
    199        }
    200      }
    201      for (const version_type of ["min_version", "max_version"]) {
    202        if (version_type in intervention) {
    203          const val = intervention[version_type];
    204          ok(
    205            typeof val == "number" && val > 0,
    206            `Invalid ${version_type} value ${JSON.stringify(val)}, should be a positive number`
    207          );
    208        }
    209      }
    210      let {
    211        content_scripts,
    212        not_platforms,
    213        not_channels,
    214        only_channels,
    215        platforms,
    216        pref_check,
    217        skip_if,
    218        ua_string,
    219      } = intervention;
    220      ok(
    221        !!platforms || !!not_platforms,
    222        `platforms or not_platforms key exists for id ${id} intervention ${JSON.stringify(intervention)}`
    223      );
    224      if (check_valid_array(not_platforms, "not_platforms", id)) {
    225        let skipped = 0;
    226        let possible = helpers.valid_platforms.length - 2; // without "all" and "desktop"
    227        for (const platform of not_platforms) {
    228          ok(
    229            helpers.valid_platforms.includes(platform),
    230            `Not-platform ${platform} is valid in id ${id}`
    231          );
    232          if (platform == "desktop") {
    233            skipped += possible - 1;
    234          } else if (platform == "all") {
    235            skipped = possible;
    236          } else {
    237            ++skipped;
    238          }
    239        }
    240        Assert.less(
    241          skipped,
    242          possible,
    243          `Not skipping all platforms for id ${id} intervention ${JSON.stringify(intervention)}`
    244        );
    245      }
    246      if (check_valid_array(platforms, "platforms", id)) {
    247        for (const platform of platforms) {
    248          ok(
    249            helpers.valid_platforms.includes(platform),
    250            `Platform ${platform} is valid in id ${id}`
    251          );
    252        }
    253      }
    254      if (check_valid_array(not_channels, "not_channels", id)) {
    255        let skipped = 0;
    256        let possible = helpers.valid_channels.length;
    257        for (const channel of not_channels) {
    258          ok(
    259            helpers.valid_channels.includes(channel),
    260            `Not-channel ${channel} is valid in id ${id}`
    261          );
    262          ++skipped;
    263        }
    264        Assert.less(
    265          skipped,
    266          possible,
    267          `Not skipping all channels for id ${id} intervention ${JSON.stringify(intervention)}`
    268        );
    269      }
    270      if (check_valid_array(only_channels, "only_channels", id)) {
    271        for (const channel of only_channels) {
    272          ok(
    273            helpers.valid_channels.includes(channel),
    274            `Channel ${channel} is valid in id ${id}`
    275          );
    276        }
    277      }
    278      ok(
    279        content_scripts || ua_string || custom_found,
    280        `Interventions are defined for id ${id}`
    281      );
    282      ok(
    283        pref_check === undefined || typeof pref_check === "object",
    284        `pref_check is not given or is an object ${id}`
    285      );
    286      if (pref_check) {
    287        for (const [pref, value] of Object.entries(pref_check)) {
    288          ok(
    289            checkableGlobalPrefs.includes(pref),
    290            `'${pref}' is allow-listed in AboutConfigPrefsAPI.ALLOWED_GLOBAL_PREFS`
    291          );
    292          const type = typeof value;
    293          const expectedType = Services.prefs.getPrefType(pref);
    294          if (expectedType !== 0) {
    295            // will be 0 if not defined/available on the given platform
    296            ok(
    297              (type === "boolean" &&
    298                expectedType === Ci.nsIPrefBranch.PREF_BOOL) ||
    299                (type === "number" &&
    300                  expectedType === Ci.nsIPrefBranch.PREF_INT) ||
    301                (type === "string" &&
    302                  expectedType === Ci.nsIPrefBranch.PREF_STRING),
    303              `Given value (${JSON.stringify(value)}) for '${pref}' matches the pref's type`
    304            );
    305          }
    306        }
    307      }
    308      if (check_valid_array(skip_if, "skip_if", id)) {
    309        for (const fn of skip_if) {
    310          ok(
    311            fn in helpers.skip_if_functions,
    312            `'${fn}' is not in the skip_if_functions`
    313          );
    314        }
    315      }
    316      if (content_scripts) {
    317        if ("all_frames" in content_scripts) {
    318          const all = content_scripts.all_frames;
    319          ok(
    320            all === false || all === true,
    321            `all_frames key is true or false for content_scripts for id ${id}`
    322          );
    323        }
    324        for (const type of ["css", "js"]) {
    325          if (!(type in content_scripts)) {
    326            continue;
    327          }
    328          const paths = content_scripts[type];
    329          const check = Array.isArray(paths) && paths.length;
    330          ok(
    331            check,
    332            `${type} content_scripts should be an array with at least one string for id ${id}`
    333          );
    334          if (!check) {
    335            continue;
    336          }
    337          for (let path of paths) {
    338            if (!path.includes("/")) {
    339              path = `injections/${type}/${path}`;
    340            }
    341            ok(
    342              path.endsWith(`.${type}`),
    343              `${path} should be a ${type.toUpperCase()} file`
    344            );
    345            ok(await check_path_exists(path), `${path} exists for id ${id}`);
    346          }
    347        }
    348      }
    349      if (check_valid_array(ua_string, "ua_string", id)) {
    350        for (let change of ua_string) {
    351          if (typeof change !== "string") {
    352            change = change.change;
    353          }
    354          ok(
    355            change in helpers.ua_change_functions,
    356            `'${change}' is not in the ua_change_functions`
    357          );
    358        }
    359      }
    360    }
    361  }
    362 });