forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			318 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			318 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* - This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | 
						|
   - You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
/* import-globals-from preferences.js */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "AddonManager",
 | 
						|
  "resource://gre/modules/AddonManager.jsm"
 | 
						|
);
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "BrowserUtils",
 | 
						|
  "resource://gre/modules/BrowserUtils.jsm"
 | 
						|
);
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "DeferredTask",
 | 
						|
  "resource://gre/modules/DeferredTask.jsm"
 | 
						|
);
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "ExtensionSettingsStore",
 | 
						|
  "resource://gre/modules/ExtensionSettingsStore.jsm"
 | 
						|
);
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "ExtensionPreferencesManager",
 | 
						|
  "resource://gre/modules/ExtensionPreferencesManager.jsm"
 | 
						|
);
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "Management",
 | 
						|
  "resource://gre/modules/Extension.jsm"
 | 
						|
);
 | 
						|
 | 
						|
const PREF_SETTING_TYPE = "prefs";
 | 
						|
const PROXY_KEY = "proxy.settings";
 | 
						|
const API_PROXY_PREFS = [
 | 
						|
  "network.proxy.type",
 | 
						|
  "network.proxy.http",
 | 
						|
  "network.proxy.http_port",
 | 
						|
  "network.proxy.share_proxy_settings",
 | 
						|
  "network.proxy.ssl",
 | 
						|
  "network.proxy.ssl_port",
 | 
						|
  "network.proxy.socks",
 | 
						|
  "network.proxy.socks_port",
 | 
						|
  "network.proxy.socks_version",
 | 
						|
  "network.proxy.socks_remote_dns",
 | 
						|
  "network.proxy.no_proxies_on",
 | 
						|
  "network.proxy.autoconfig_url",
 | 
						|
  "signon.autologin.proxy",
 | 
						|
];
 | 
						|
 | 
						|
let extensionControlledContentIds = {
 | 
						|
  "privacy.containers": "browserContainersExtensionContent",
 | 
						|
  webNotificationsDisabled: "browserNotificationsPermissionExtensionContent",
 | 
						|
  "services.passwordSavingEnabled": "passwordManagerExtensionContent",
 | 
						|
  "proxy.settings": "proxyExtensionContent",
 | 
						|
  get "websites.trackingProtectionMode"() {
 | 
						|
    return {
 | 
						|
      button: "contentBlockingDisableTrackingProtectionExtension",
 | 
						|
      section: "contentBlockingTrackingProtectionExtensionContentLabel",
 | 
						|
    };
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
const extensionControlledL10nKeys = {
 | 
						|
  webNotificationsDisabled: "web-notifications",
 | 
						|
  "services.passwordSavingEnabled": "password-saving",
 | 
						|
  "privacy.containers": "privacy-containers",
 | 
						|
  "websites.trackingProtectionMode": "websites-content-blocking-all-trackers",
 | 
						|
  "proxy.settings": "proxy-config",
 | 
						|
};
 | 
						|
 | 
						|
let extensionControlledIds = {};
 | 
						|
 | 
						|
/**
 | 
						|
 * Check if a pref is being managed by an extension.
 | 
						|
 */
 | 
						|
async function getControllingExtensionInfo(type, settingName) {
 | 
						|
  await ExtensionSettingsStore.initialize();
 | 
						|
  return ExtensionSettingsStore.getSetting(type, settingName);
 | 
						|
}
 | 
						|
 | 
						|
function getControllingExtensionEls(settingName) {
 | 
						|
  let idInfo = extensionControlledContentIds[settingName];
 | 
						|
  let section = document.getElementById(idInfo.section || idInfo);
 | 
						|
  let button = idInfo.button
 | 
						|
    ? document.getElementById(idInfo.button)
 | 
						|
    : section.querySelector("button");
 | 
						|
  return {
 | 
						|
    section,
 | 
						|
    button,
 | 
						|
    description: section.querySelector("description"),
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
async function getControllingExtension(type, settingName) {
 | 
						|
  let info = await getControllingExtensionInfo(type, settingName);
 | 
						|
  let addon = info && info.id && (await AddonManager.getAddonByID(info.id));
 | 
						|
  return addon;
 | 
						|
}
 | 
						|
 | 
						|
async function handleControllingExtension(type, settingName) {
 | 
						|
  let addon = await getControllingExtension(type, settingName);
 | 
						|
 | 
						|
  // Sometimes the ExtensionSettingsStore gets in a bad state where it thinks
 | 
						|
  // an extension is controlling a setting but the extension has been uninstalled
 | 
						|
  // outside of the regular lifecycle. If the extension isn't currently installed
 | 
						|
  // then we should treat the setting as not being controlled.
 | 
						|
  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1411046 for an example.
 | 
						|
  if (addon) {
 | 
						|
    extensionControlledIds[settingName] = addon.id;
 | 
						|
    showControllingExtension(settingName, addon);
 | 
						|
  } else {
 | 
						|
    let elements = getControllingExtensionEls(settingName);
 | 
						|
    if (
 | 
						|
      extensionControlledIds[settingName] &&
 | 
						|
      !document.hidden &&
 | 
						|
      elements.button
 | 
						|
    ) {
 | 
						|
      showEnableExtensionMessage(settingName);
 | 
						|
    } else {
 | 
						|
      hideControllingExtension(settingName);
 | 
						|
    }
 | 
						|
    delete extensionControlledIds[settingName];
 | 
						|
  }
 | 
						|
 | 
						|
  return !!addon;
 | 
						|
}
 | 
						|
 | 
						|
function settingNameToL10nID(settingName) {
 | 
						|
  if (!extensionControlledL10nKeys.hasOwnProperty(settingName)) {
 | 
						|
    throw new Error(
 | 
						|
      `Unknown extension controlled setting name: ${settingName}`
 | 
						|
    );
 | 
						|
  }
 | 
						|
  return `extension-controlled-${extensionControlledL10nKeys[settingName]}`;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Set the localization data for the description of the controlling extension.
 | 
						|
 *
 | 
						|
 * The function alters the inner DOM structure of the fragment to, depending
 | 
						|
 * on the `addon` argument, remove the `<img/>` element or ensure it's
 | 
						|
 * set to the correct src.
 | 
						|
 * This allows Fluent DOM Overlays to localize the fragment.
 | 
						|
 *
 | 
						|
 * @param elem {Element}
 | 
						|
 *        <description> element to be annotated
 | 
						|
 * @param addon {Object?}
 | 
						|
 *        Addon object with meta information about the addon (or null)
 | 
						|
 * @param settingName {String}
 | 
						|
 *        If `addon` is set this handled the name of the setting that will be used
 | 
						|
 *        to fetch the l10n id for the given message.
 | 
						|
 *        If `addon` is set to null, this will be the full l10n-id assigned to the
 | 
						|
 *        element.
 | 
						|
 */
 | 
						|
function setControllingExtensionDescription(elem, addon, settingName) {
 | 
						|
  const existingImg = elem.querySelector("img");
 | 
						|
  if (addon === null) {
 | 
						|
    // If the element has an image child element,
 | 
						|
    // remove it.
 | 
						|
    if (existingImg) {
 | 
						|
      existingImg.remove();
 | 
						|
    }
 | 
						|
    document.l10n.setAttributes(elem, settingName);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
 | 
						|
  const src = addon.iconURL || defaultIcon;
 | 
						|
 | 
						|
  if (!existingImg) {
 | 
						|
    // If an element doesn't have an image child
 | 
						|
    // node, add it.
 | 
						|
    let image = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
 | 
						|
    image.setAttribute("src", src);
 | 
						|
    image.setAttribute("data-l10n-name", "icon");
 | 
						|
    image.classList.add("extension-controlled-icon");
 | 
						|
    elem.appendChild(image);
 | 
						|
  } else if (existingImg.getAttribute("src") !== src) {
 | 
						|
    existingImg.setAttribute("src", src);
 | 
						|
  }
 | 
						|
 | 
						|
  const l10nId = settingNameToL10nID(settingName);
 | 
						|
  document.l10n.setAttributes(elem, l10nId, {
 | 
						|
    name: addon.name,
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
async function showControllingExtension(settingName, addon) {
 | 
						|
  // Tell the user what extension is controlling the setting.
 | 
						|
  let elements = getControllingExtensionEls(settingName);
 | 
						|
 | 
						|
  elements.section.classList.remove("extension-controlled-disabled");
 | 
						|
  let description = elements.description;
 | 
						|
 | 
						|
  setControllingExtensionDescription(description, addon, settingName);
 | 
						|
 | 
						|
  if (elements.button) {
 | 
						|
    elements.button.hidden = false;
 | 
						|
  }
 | 
						|
 | 
						|
  // Show the controlling extension row and hide the old label.
 | 
						|
  elements.section.hidden = false;
 | 
						|
}
 | 
						|
 | 
						|
function hideControllingExtension(settingName) {
 | 
						|
  let elements = getControllingExtensionEls(settingName);
 | 
						|
  elements.section.hidden = true;
 | 
						|
  if (elements.button) {
 | 
						|
    elements.button.hidden = true;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function showEnableExtensionMessage(settingName) {
 | 
						|
  let elements = getControllingExtensionEls(settingName);
 | 
						|
 | 
						|
  elements.button.hidden = true;
 | 
						|
  elements.section.classList.add("extension-controlled-disabled");
 | 
						|
 | 
						|
  elements.description.textContent = "";
 | 
						|
 | 
						|
  // We replace localization of the <description> with a DOM Fragment containing
 | 
						|
  // the enable-extension-enable message. That means a change from:
 | 
						|
  //
 | 
						|
  // <description data-l10n-id="..."/>
 | 
						|
  //
 | 
						|
  // to:
 | 
						|
  //
 | 
						|
  // <description>
 | 
						|
  //   <img/>
 | 
						|
  //   <label data-l10n-id="..."/>
 | 
						|
  // </description>
 | 
						|
  //
 | 
						|
  // We need to remove the l10n-id annotation from the <description> to prevent
 | 
						|
  // Fluent from overwriting the element in case of any retranslation.
 | 
						|
  elements.description.removeAttribute("data-l10n-id");
 | 
						|
 | 
						|
  let icon = (url, name) => {
 | 
						|
    let img = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
 | 
						|
    img.src = url;
 | 
						|
    img.setAttribute("data-l10n-name", name);
 | 
						|
    img.className = "extension-controlled-icon";
 | 
						|
    return img;
 | 
						|
  };
 | 
						|
  let label = document.createXULElement("label");
 | 
						|
  let addonIcon = icon(
 | 
						|
    "chrome://mozapps/skin/extensions/extensionGeneric.svg",
 | 
						|
    "addons-icon"
 | 
						|
  );
 | 
						|
  let toolbarIcon = icon("chrome://browser/skin/menu.svg", "menu-icon");
 | 
						|
  label.appendChild(addonIcon);
 | 
						|
  label.appendChild(toolbarIcon);
 | 
						|
  document.l10n.setAttributes(label, "extension-controlled-enable");
 | 
						|
  elements.description.appendChild(label);
 | 
						|
  let dismissButton = document.createXULElement("image");
 | 
						|
  dismissButton.setAttribute("class", "extension-controlled-icon close-icon");
 | 
						|
  dismissButton.addEventListener("click", function dismissHandler() {
 | 
						|
    hideControllingExtension(settingName);
 | 
						|
    dismissButton.removeEventListener("click", dismissHandler);
 | 
						|
  });
 | 
						|
  elements.description.appendChild(dismissButton);
 | 
						|
}
 | 
						|
 | 
						|
function makeDisableControllingExtension(type, settingName) {
 | 
						|
  return async function disableExtension() {
 | 
						|
    let { id } = await getControllingExtensionInfo(type, settingName);
 | 
						|
    let addon = await AddonManager.getAddonByID(id);
 | 
						|
    await addon.disable();
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 *  Initialize listeners though the Management API to update the UI
 | 
						|
 *  when an extension is controlling a pref.
 | 
						|
 * @param {string} type
 | 
						|
 * @param {string} prefId The unique id of the setting
 | 
						|
 * @param {HTMLElement} controlledElement
 | 
						|
 */
 | 
						|
async function initListenersForPrefChange(type, prefId, controlledElement) {
 | 
						|
  await Management.asyncLoadSettingsModules();
 | 
						|
 | 
						|
  let managementObserver = async () => {
 | 
						|
    let managementControlled = await handleControllingExtension(type, prefId);
 | 
						|
    // Enterprise policy may have locked the pref, so we need to preserve that
 | 
						|
    controlledElement.disabled =
 | 
						|
      managementControlled || Services.prefs.prefIsLocked(prefId);
 | 
						|
  };
 | 
						|
  managementObserver();
 | 
						|
  Management.on(`extension-setting-changed:${prefId}`, managementObserver);
 | 
						|
 | 
						|
  window.addEventListener("unload", () => {
 | 
						|
    Management.off(`extension-setting-changed:${prefId}`, managementObserver);
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function initializeProxyUI(container) {
 | 
						|
  let deferredUpdate = new DeferredTask(() => {
 | 
						|
    container.updateProxySettingsUI();
 | 
						|
  }, 10);
 | 
						|
  let proxyObserver = {
 | 
						|
    observe: (subject, topic, data) => {
 | 
						|
      if (API_PROXY_PREFS.includes(data)) {
 | 
						|
        deferredUpdate.arm();
 | 
						|
      }
 | 
						|
    },
 | 
						|
  };
 | 
						|
  Services.prefs.addObserver("", proxyObserver);
 | 
						|
  window.addEventListener("unload", () => {
 | 
						|
    Services.prefs.removeObserver("", proxyObserver);
 | 
						|
  });
 | 
						|
}
 |