From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel.baumann@progress-linux.org>
Date: Sun, 7 Apr 2024 21:33:14 +0200
Subject: Adding upstream version 115.7.0esr.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
---
 .../components/preferences/extensionControlled.js  | 309 +++++++++++++++++++++
 1 file changed, 309 insertions(+)
 create mode 100644 browser/components/preferences/extensionControlled.js

(limited to 'browser/components/preferences/extensionControlled.js')

diff --git a/browser/components/preferences/extensionControlled.js b/browser/components/preferences/extensionControlled.js
new file mode 100644
index 0000000000..3c6f78ab42
--- /dev/null
+++ b/browser/components/preferences/extensionControlled.js
@@ -0,0 +1,309 @@
+/* - 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";
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+  "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+// Note: we get loaded in dialogs so we need to define our
+// own getters, separate from preferences.js .
+ChromeUtils.defineESModuleGetters(this, {
+  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
+  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
+  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
+
+  ExtensionPreferencesManager:
+    "resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
+
+  ExtensionSettingsStore:
+    "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
+
+  Management: "resource://gre/modules/Extension.sys.mjs",
+});
+
+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-controlling-${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.setAttribute("role", "presentation");
+    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.setAttribute("role", "presentation");
+    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);
+  });
+}
-- 
cgit v1.2.3