tor-browser

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

extensionControlled.js (10544B)


      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 file,
      3   - You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /* import-globals-from preferences.js */
      6 
      7 "use strict";
      8 
      9 var { XPCOMUtils } = ChromeUtils.importESModule(
     10  "resource://gre/modules/XPCOMUtils.sys.mjs"
     11 );
     12 
     13 // Note: we get loaded in dialogs so we need to define our
     14 // own getters, separate from preferences.js .
     15 ChromeUtils.defineESModuleGetters(this, {
     16  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
     17  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
     18  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
     19 
     20  ExtensionPreferencesManager:
     21    "resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
     22 
     23  ExtensionSettingsStore:
     24    "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
     25 
     26  Management: "resource://gre/modules/Extension.sys.mjs",
     27 });
     28 
     29 const PREF_SETTING_TYPE = "prefs";
     30 const PROXY_KEY = "proxy.settings";
     31 const API_PROXY_PREFS = [
     32  "network.proxy.type",
     33  "network.proxy.http",
     34  "network.proxy.http_port",
     35  "network.proxy.share_proxy_settings",
     36  "network.proxy.ssl",
     37  "network.proxy.ssl_port",
     38  "network.proxy.socks",
     39  "network.proxy.socks_port",
     40  "network.proxy.socks_version",
     41  "network.proxy.socks_remote_dns",
     42  "network.proxy.socks5_remote_dns",
     43  "network.proxy.no_proxies_on",
     44  "network.proxy.autoconfig_url",
     45  "signon.autologin.proxy",
     46 ];
     47 
     48 let extensionControlledContentIds = {
     49  webNotificationsDisabled: "browserNotificationsPermissionExtensionContent",
     50  "services.passwordSavingEnabled": "passwordManagerExtensionContent",
     51  "proxy.settings": "proxyExtensionContent",
     52  get "websites.trackingProtectionMode"() {
     53    return {
     54      button: "contentBlockingDisableTrackingProtectionExtension",
     55      section: "contentBlockingTrackingProtectionExtensionContentLabel",
     56    };
     57  },
     58 };
     59 
     60 const extensionControlledL10nKeys = {
     61  webNotificationsDisabled: "extension-controlling-web-notifications",
     62  "services.passwordSavingEnabled": "extension-controlling-password-saving",
     63  "privacy.containers": "extension-controlling-privacy-containers",
     64  "websites.trackingProtectionMode":
     65    "extension-controlling-websites-content-blocking-all-trackers",
     66  "proxy.settings": "extension-controlling-proxy-config",
     67 };
     68 
     69 let extensionControlledIds = {};
     70 
     71 /**
     72 * Check if a pref is being managed by an extension.
     73 */
     74 async function getControllingExtensionInfo(type, settingName) {
     75  await ExtensionSettingsStore.initialize();
     76  return ExtensionSettingsStore.getSetting(type, settingName);
     77 }
     78 
     79 function getControllingExtensionEls(settingName) {
     80  let idInfo = extensionControlledContentIds[settingName];
     81  let section = document.getElementById(idInfo.section || idInfo);
     82  let button = idInfo.button
     83    ? document.getElementById(idInfo.button)
     84    : section.querySelector("button");
     85  return {
     86    section,
     87    button,
     88    description: section.querySelector("description"),
     89  };
     90 }
     91 
     92 async function getControllingExtension(type, settingName) {
     93  let info = await getControllingExtensionInfo(type, settingName);
     94  let addon = info && info.id && (await AddonManager.getAddonByID(info.id));
     95  return addon;
     96 }
     97 
     98 async function handleControllingExtension(type, settingName) {
     99  let addon = await getControllingExtension(type, settingName);
    100 
    101  // Sometimes the ExtensionSettingsStore gets in a bad state where it thinks
    102  // an extension is controlling a setting but the extension has been uninstalled
    103  // outside of the regular lifecycle. If the extension isn't currently installed
    104  // then we should treat the setting as not being controlled.
    105  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1411046 for an example.
    106  if (addon) {
    107    extensionControlledIds[settingName] = addon.id;
    108    showControllingExtension(settingName, addon);
    109  } else {
    110    let elements = getControllingExtensionEls(settingName);
    111    if (
    112      extensionControlledIds[settingName] &&
    113      !document.hidden &&
    114      elements.button
    115    ) {
    116      showEnableExtensionMessage(settingName);
    117    } else {
    118      hideControllingExtension(settingName);
    119    }
    120    delete extensionControlledIds[settingName];
    121  }
    122 
    123  return !!addon;
    124 }
    125 
    126 function settingNameToL10nID(settingName) {
    127  if (!extensionControlledL10nKeys.hasOwnProperty(settingName)) {
    128    throw new Error(
    129      `Unknown extension controlled setting name: ${settingName}`
    130    );
    131  }
    132  return extensionControlledL10nKeys[settingName];
    133 }
    134 
    135 /**
    136 * Set the localization data for the description of the controlling extension.
    137 *
    138 * The function alters the inner DOM structure of the fragment to, depending
    139 * on the `addon` argument, remove the `<img/>` element or ensure it's
    140 * set to the correct src.
    141 * This allows Fluent DOM Overlays to localize the fragment.
    142 *
    143 * @param elem {Element}
    144 *        <description> element to be annotated
    145 * @param addon {Object?}
    146 *        Addon object with meta information about the addon (or null)
    147 * @param settingName {String}
    148 *        If `addon` is set this handled the name of the setting that will be used
    149 *        to fetch the l10n id for the given message.
    150 *        If `addon` is set to null, this will be the full l10n-id assigned to the
    151 *        element.
    152 */
    153 function setControllingExtensionDescription(elem, addon, settingName) {
    154  const existingImg = elem.querySelector("img");
    155  if (addon === null) {
    156    // If the element has an image child element,
    157    // remove it.
    158    if (existingImg) {
    159      existingImg.remove();
    160    }
    161    document.l10n.setAttributes(elem, settingName);
    162    return;
    163  }
    164 
    165  const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
    166  const src = addon.iconURL || defaultIcon;
    167 
    168  if (!existingImg) {
    169    // If an element doesn't have an image child
    170    // node, add it.
    171    let image = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
    172    image.setAttribute("src", src);
    173    image.setAttribute("data-l10n-name", "icon");
    174    image.setAttribute("role", "presentation");
    175    image.classList.add("extension-controlled-icon");
    176    elem.appendChild(image);
    177  } else if (existingImg.getAttribute("src") !== src) {
    178    existingImg.setAttribute("src", src);
    179  }
    180 
    181  const l10nId = settingNameToL10nID(settingName);
    182  document.l10n.setAttributes(elem, l10nId, {
    183    name: addon.name,
    184  });
    185 }
    186 
    187 async function showControllingExtension(settingName, addon) {
    188  // Tell the user what extension is controlling the setting.
    189  let elements = getControllingExtensionEls(settingName);
    190 
    191  elements.section.classList.remove("extension-controlled-disabled");
    192  let description = elements.description;
    193 
    194  setControllingExtensionDescription(description, addon, settingName);
    195 
    196  if (elements.button) {
    197    elements.button.hidden = false;
    198  }
    199 
    200  // Show the controlling extension row and hide the old label.
    201  elements.section.hidden = false;
    202 }
    203 
    204 function hideControllingExtension(settingName) {
    205  let elements = getControllingExtensionEls(settingName);
    206  elements.section.hidden = true;
    207  if (elements.button) {
    208    elements.button.hidden = true;
    209  }
    210 }
    211 
    212 function showEnableExtensionMessage(settingName) {
    213  let elements = getControllingExtensionEls(settingName);
    214 
    215  elements.button.hidden = true;
    216  elements.section.classList.add("extension-controlled-disabled");
    217 
    218  elements.description.textContent = "";
    219 
    220  // We replace localization of the <description> with a DOM Fragment containing
    221  // the enable-extension-enable message. That means a change from:
    222  //
    223  // <description data-l10n-id="..."/>
    224  //
    225  // to:
    226  //
    227  // <description>
    228  //   <img/>
    229  //   <label data-l10n-id="..."/>
    230  // </description>
    231  //
    232  // We need to remove the l10n-id annotation from the <description> to prevent
    233  // Fluent from overwriting the element in case of any retranslation.
    234  elements.description.removeAttribute("data-l10n-id");
    235 
    236  let icon = (url, name) => {
    237    let img = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
    238    img.src = url;
    239    img.setAttribute("data-l10n-name", name);
    240    img.setAttribute("role", "presentation");
    241    img.className = "extension-controlled-icon";
    242    return img;
    243  };
    244  let label = document.createXULElement("label");
    245  let addonIcon = icon(
    246    "chrome://mozapps/skin/extensions/extensionGeneric.svg",
    247    "addons-icon"
    248  );
    249  let toolbarIcon = icon("chrome://browser/skin/menu.svg", "menu-icon");
    250  label.appendChild(addonIcon);
    251  label.appendChild(toolbarIcon);
    252  document.l10n.setAttributes(label, "extension-controlled-enable");
    253  elements.description.appendChild(label);
    254  let dismissButton = document.createXULElement("image");
    255  dismissButton.setAttribute("class", "extension-controlled-icon close-icon");
    256  dismissButton.addEventListener("click", function dismissHandler() {
    257    hideControllingExtension(settingName);
    258    dismissButton.removeEventListener("click", dismissHandler);
    259  });
    260  elements.description.appendChild(dismissButton);
    261 }
    262 
    263 function makeDisableControllingExtension(type, settingName) {
    264  return async function disableExtension() {
    265    let { id } = await getControllingExtensionInfo(type, settingName);
    266    let addon = await AddonManager.getAddonByID(id);
    267    await addon.disable();
    268  };
    269 }
    270 
    271 /**
    272 *  Initialize listeners though the Management API to update the UI
    273 *  when an extension is controlling a pref.
    274 *
    275 * @param {string} type
    276 * @param {string} prefId The unique id of the setting
    277 * @param {HTMLElement} controlledElement
    278 */
    279 async function initListenersForPrefChange(type, prefId, controlledElement) {
    280  await Management.asyncLoadSettingsModules();
    281 
    282  let managementObserver = async () => {
    283    let managementControlled = await handleControllingExtension(type, prefId);
    284    // Enterprise policy may have locked the pref, so we need to preserve that
    285    controlledElement.disabled =
    286      managementControlled || Services.prefs.prefIsLocked(prefId);
    287  };
    288  managementObserver();
    289  Management.on(`extension-setting-changed:${prefId}`, managementObserver);
    290 
    291  window.addEventListener("unload", () => {
    292    Management.off(`extension-setting-changed:${prefId}`, managementObserver);
    293  });
    294 }
    295 
    296 function initializeProxyUI(container) {
    297  let deferredUpdate = new DeferredTask(() => {
    298    container.updateProxySettingsUI();
    299  }, 10);
    300  let proxyObserver = {
    301    observe: (subject, topic, data) => {
    302      if (API_PROXY_PREFS.includes(data)) {
    303        deferredUpdate.arm();
    304      }
    305    },
    306  };
    307  Services.prefs.addObserver("", proxyObserver);
    308  window.addEventListener("unload", () => {
    309    Services.prefs.removeObserver("", proxyObserver);
    310  });
    311 }