tor-browser

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

aboutCompat.js (7853B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 /* globals browser */
      8 
      9 let availablePatches;
     10 
     11 const portToAddon = (function () {
     12  let port;
     13 
     14  function connect() {
     15    port = browser.runtime.connect({ name: "AboutCompatTab" });
     16    port.onMessage.addListener(onMessageFromAddon);
     17    port.onDisconnect.addListener(() => {
     18      port = undefined;
     19    });
     20  }
     21 
     22  connect();
     23 
     24  async function send(message) {
     25    if (port) {
     26      return port.postMessage(message);
     27    }
     28    return Promise.reject("background script port disconnected");
     29  }
     30 
     31  return { send };
     32 })();
     33 
     34 const $ = function (sel) {
     35  return document.querySelector(sel);
     36 };
     37 
     38 const DOMContentLoadedPromise = new Promise(resolve => {
     39  document.addEventListener(
     40    "DOMContentLoaded",
     41    () => {
     42      resolve();
     43    },
     44    { once: true }
     45  );
     46 });
     47 
     48 const ua = navigator.userAgent;
     49 if (ua.includes("Tablet") || ua.includes("Mobile")) {
     50  document.documentElement.classList.add("mobile");
     51 }
     52 
     53 Promise.all([
     54  browser.runtime.sendMessage("getAllInterventions"),
     55  DOMContentLoadedPromise,
     56 ]).then(([info]) => {
     57  // alphabetize the interventions and shims
     58  if (info.interventions) {
     59    info.interventions = info.interventions.sort((a, b) =>
     60      a.domain.localeCompare(b.domain)
     61    );
     62  }
     63  if (info.shims) {
     64    info.shims = info.shims.sort((a, b) => a.name.localeCompare(b.name));
     65  }
     66 
     67  document.body.addEventListener("click", async evt => {
     68    const ele = evt.target;
     69    if (ele.nodeName === "BUTTON") {
     70      const row = ele.closest("[data-id]");
     71      if (row) {
     72        evt.preventDefault();
     73        ele.disabled = true;
     74        const id = row.getAttribute("data-id");
     75        try {
     76          await browser.runtime.sendMessage({ command: "toggle", id });
     77        } catch (_) {
     78          ele.disabled = false;
     79        }
     80      }
     81    } else if (ele.classList.contains("tab")) {
     82      document.querySelectorAll(".tab").forEach(tab => {
     83        tab.classList.remove("active");
     84      });
     85      ele.classList.add("active");
     86    }
     87  });
     88 
     89  availablePatches = info;
     90  redraw();
     91 });
     92 
     93 async function onMessageFromAddon(msg) {
     94  const alsoShowHidden = location.hash === "#all";
     95 
     96  await DOMContentLoadedPromise;
     97 
     98  if ("interventionsChanged" in msg) {
     99    redrawTable($("#interventions"), msg.interventionsChanged, alsoShowHidden);
    100  }
    101 
    102  if ("shimsChanged" in msg) {
    103    updateShimTables(msg.shimsChanged, alsoShowHidden);
    104  }
    105 
    106  const id = msg.toggling || msg.toggled;
    107  const button = $(`[data-id="${id}"] button`);
    108  if (!button) {
    109    return;
    110  }
    111  const active = msg.active;
    112  document.l10n.setAttributes(
    113    button,
    114    active ? "label-disable" : "label-enable"
    115  );
    116  button.disabled = !!msg.toggling;
    117 }
    118 
    119 function redraw() {
    120  if (!availablePatches) {
    121    return;
    122  }
    123  const { interventions, shims } = availablePatches;
    124  const alsoShowHidden = location.hash === "#all";
    125  redrawTable($("#interventions"), interventions, alsoShowHidden);
    126  updateShimTables(shims, alsoShowHidden);
    127 }
    128 
    129 function clearTableAndAddMessage(table, msgId) {
    130  table.querySelectorAll("tr").forEach(tr => {
    131    tr.remove();
    132  });
    133 
    134  const tr = document.createElement("tr");
    135  tr.className = "message";
    136  tr.id = msgId;
    137 
    138  const td = document.createElement("td");
    139  td.setAttribute("colspan", "3");
    140  document.l10n.setAttributes(td, msgId);
    141  tr.appendChild(td);
    142 
    143  table.appendChild(tr);
    144 }
    145 
    146 function hideMessagesOnTable(table) {
    147  table.querySelectorAll("tr.message").forEach(tr => {
    148    tr.remove();
    149  });
    150 }
    151 
    152 function updateShimTables(shimsChanged, alsoShowHidden) {
    153  const tables = document.querySelectorAll("table.shims");
    154  if (!tables.length) {
    155    return;
    156  }
    157 
    158  for (const { bug, disabledReason, hidden, id, name, type } of shimsChanged) {
    159    // if any shim is disabled by global pref, all of them are. just show the
    160    // "disabled in about:config" message on each shim table in that case.
    161    if (disabledReason === "globalPref") {
    162      for (const table of tables) {
    163        clearTableAndAddMessage(table, "text-disabled-in-about-config");
    164      }
    165      return;
    166    }
    167 
    168    // otherwise, find which table the shim belongs in. if there is none,
    169    // ignore the shim (we're not showing it on the UI for whatever reason).
    170    const table = document.querySelector(`table.shims#${type}`);
    171    if (!table) {
    172      continue;
    173    }
    174 
    175    // similarly, skip shims hidden from the UI (only for testing, etc).
    176    if (!alsoShowHidden && hidden) {
    177      continue;
    178    }
    179 
    180    // also, hide the shim if it is disabled because it is not meant for this
    181    // platform, release (etc) rather than being disabled by pref/about:compat
    182    const notApplicable =
    183      disabledReason &&
    184      disabledReason !== "pref" &&
    185      disabledReason !== "session";
    186    if (!alsoShowHidden && notApplicable) {
    187      continue;
    188    }
    189 
    190    // create an updated table-row for the shim
    191    const tr = document.createElement("tr");
    192    tr.setAttribute("data-id", id);
    193 
    194    let td = document.createElement("td");
    195    td.innerText = name;
    196    tr.appendChild(td);
    197 
    198    td = document.createElement("td");
    199    const a = document.createElement("a");
    200    a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`;
    201    document.l10n.setAttributes(a, "label-more-information", { bug });
    202    a.target = "_blank";
    203    td.appendChild(a);
    204    tr.appendChild(td);
    205 
    206    td = document.createElement("td");
    207    tr.appendChild(td);
    208    const button = document.createElement("button");
    209    document.l10n.setAttributes(
    210      button,
    211      disabledReason ? "label-enable" : "label-disable"
    212    );
    213    td.appendChild(button);
    214 
    215    // is it already in the table?
    216    const row = table.querySelector(`tr[data-id="${id}"]`);
    217    if (row) {
    218      row.replaceWith(tr);
    219    } else {
    220      table.appendChild(tr);
    221    }
    222  }
    223 
    224  for (const table of tables) {
    225    if (!table.querySelector("tr:not(.message)")) {
    226      // no shims? then add a message that none are available for this platform/config
    227      clearTableAndAddMessage(table, `text-no-${table.id}`);
    228    } else {
    229      // otherwise hide any such message, since we have shims on the list
    230      hideMessagesOnTable(table);
    231    }
    232  }
    233 }
    234 
    235 function redrawTable(table, data, alsoShowHidden) {
    236  const df = document.createDocumentFragment();
    237  table.querySelectorAll("tr").forEach(tr => {
    238    tr.remove();
    239  });
    240 
    241  let noEntriesMessage;
    242  if (data === false) {
    243    noEntriesMessage = "text-disabled-in-about-config";
    244  } else if (data.length === 0) {
    245    noEntriesMessage = `text-no-${table.id}`;
    246  }
    247 
    248  if (noEntriesMessage) {
    249    const tr = document.createElement("tr");
    250    df.appendChild(tr);
    251 
    252    const td = document.createElement("td");
    253    td.setAttribute("colspan", "3");
    254    document.l10n.setAttributes(td, noEntriesMessage);
    255    tr.appendChild(td);
    256 
    257    table.appendChild(df);
    258    return;
    259  }
    260 
    261  for (const row of data) {
    262    if (row.hidden && !alsoShowHidden) {
    263      continue;
    264    }
    265 
    266    const tr = document.createElement("tr");
    267    tr.setAttribute("data-id", row.id);
    268    df.appendChild(tr);
    269 
    270    let td = document.createElement("td");
    271    td.innerText = row.domain;
    272    tr.appendChild(td);
    273 
    274    td = document.createElement("td");
    275    const a = document.createElement("a");
    276    const bug = row.bug;
    277    a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`;
    278    document.l10n.setAttributes(a, "label-more-information", { bug });
    279    a.target = "_blank";
    280    td.appendChild(a);
    281    tr.appendChild(td);
    282 
    283    td = document.createElement("td");
    284    tr.appendChild(td);
    285    const button = document.createElement("button");
    286    document.l10n.setAttributes(
    287      button,
    288      row.active ? "label-disable" : "label-enable"
    289    );
    290    td.appendChild(button);
    291  }
    292  table.appendChild(df);
    293 }
    294 
    295 window.onhashchange = redraw;