--- a/browser/components/preferences/connection.js
+++ b/browser/components/preferences/connection.js
@@ -1,15 +1,16 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
/* 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 ../../base/content/utilityOverlay.js */
/* import-globals-from ../../../toolkit/content/preferencesBindings.js */
+/* import-globals-from in-content/extensionControlled.js */
Preferences.addAll([
{ id: "network.proxy.type", type: "int" },
{ id: "network.proxy.http", type: "string" },
{ id: "network.proxy.http_port", type: "int" },
{ id: "network.proxy.ftp", type: "string" },
{ id: "network.proxy.ftp_port", type: "int" },
{ id: "network.proxy.ssl", type: "string" },
@@ -31,16 +32,24 @@ Preferences.addAll([
{ id: "network.proxy.backup.socks_port", type: "int" },
]);
window.addEventListener("DOMContentLoaded", () => {
Preferences.get("network.proxy.type").on("change",
gConnectionsDialog.proxyTypeChanged.bind(gConnectionsDialog));
Preferences.get("network.proxy.socks_version").on("change",
gConnectionsDialog.updateDNSPref.bind(gConnectionsDialog));
+
+ document
+ .getElementById("disableProxyExtension")
+ .addEventListener(
+ "command", makeDisableControllingExtension(
+ PREF_SETTING_TYPE, PROXY_KEY).bind(gConnectionsDialog));
+ gConnectionsDialog.updateProxySettingsUI();
+ initializeProxyUI(gConnectionsDialog);
}, { once: true, capture: true });
var gConnectionsDialog = {
beforeAccept() {
var proxyTypePref = Preferences.get("network.proxy.type");
if (proxyTypePref.value == 2) {
this.doAutoconfigURLFixup();
return true;
@@ -222,10 +231,45 @@ var gConnectionsDialog = {
return undefined;
},
readHTTPProxyPort() {
var shareProxiesPref = Preferences.get("network.proxy.share_proxy_settings");
if (shareProxiesPref.value)
this.updateProtocolPrefs();
return undefined;
+ },
+
+ getProxyControls() {
+ let controlGroup = document.getElementById("networkProxyType");
+ return [
+ ...controlGroup.querySelectorAll(":scope > radio"),
+ ...controlGroup.querySelectorAll("label"),
+ ...controlGroup.querySelectorAll("textbox"),
+ ...controlGroup.querySelectorAll("checkbox"),
+ ...document.querySelectorAll("#networkProxySOCKSVersion > radio"),
+ ...document.querySelectorAll("#ConnectionsDialogPane > checkbox"),
+ ];
+ },
+
+ // Update the UI to show/hide the extension controlled message for
+ // proxy settings.
+ async updateProxySettingsUI() {
+ let isLocked = API_PROXY_PREFS.some(
+ pref => Services.prefs.prefIsLocked(pref));
+
+ function setInputsDisabledState(isControlled) {
+ let disabled = isLocked || isControlled;
+ for (let element of gConnectionsDialog.getProxyControls()) {
+ element.disabled = disabled;
+ }
+ }
+
+ if (isLocked) {
+ // An extension can't control this setting if any pref is locked.
+ hideControllingExtension(PROXY_KEY);
+ setInputsDisabledState(false);
+ } else {
+ handleControllingExtension(PREF_SETTING_TYPE, PROXY_KEY)
+ .then(setInputsDisabledState);
+ }
}
};
--- a/browser/components/preferences/connection.xul
+++ b/browser/components/preferences/connection.xul
@@ -4,16 +4,18 @@
- 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/. -->
<!DOCTYPE dialog [
<!ENTITY % preferencesDTD SYSTEM "chrome://global/locale/preferences.dtd">
%preferencesDTD;
<!ENTITY % connectionDTD SYSTEM "chrome://browser/locale/preferences/connection.dtd">
%connectionDTD;
+ <!ENTITY % mainDTD SYSTEM "chrome://browser/locale/preferences/main.dtd">
+ %mainDTD;
]>
<?xml-stylesheet href="chrome://global/skin/"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
<dialog id="ConnectionsDialog" type="child" class="prefwindow"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&connectionsDialog.title;"
@@ -30,28 +32,37 @@
#ifdef XP_MACOSX
style="width: &window.macWidth2; !important;">
#else
style="width: &window.width2; !important;">
#endif
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
<script type="application/javascript" src="chrome://global/content/preferencesBindings.js"/>
+ <script type="application/javascript" src="chrome://browser/content/preferences/in-content/extensionControlled.js"/>
<keyset>
<key key="&windowClose.key;" modifiers="accel" oncommand="Preferences.close(event)"/>
</keyset>
<vbox id="ConnectionsDialogPane" class="prefpane largeDialogContainer">
- <stringbundle id="preferencesBundle" src="chrome://browser/locale/preferences/preferences.properties"/>
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+ <stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/>
<script type="application/javascript" src="chrome://browser/content/preferences/connection.js"/>
+ <hbox id="proxyExtensionContent" align="top" hidden="true">
+ <description control="disableProxyExtension" flex="1" />
+ <button id="disableProxyExtension"
+ class="extension-controlled-button accessory-button"
+ label="&disableExtension.label;" />
+ </hbox>
+
<groupbox>
- <caption><label>&proxyTitle.label;</label></caption>
+ <caption><label>&proxyTitle.label2;</label></caption>
<radiogroup id="networkProxyType" preference="network.proxy.type"
onsyncfrompreference="return gConnectionsDialog.readProxyType();">
<radio value="0" label="&noProxyTypeRadio.label;" accesskey="&noProxyTypeRadio.accesskey;"/>
<radio value="4" label="&WPADTypeRadio.label;" accesskey="&WPADTypeRadio.accesskey;"/>
<radio value="5" label="&systemTypeRadio.label;" accesskey="&systemTypeRadio.accesskey;" id="systemPref" hidden="true"/>
<radio value="1" label="&manualTypeRadio2.label;" accesskey="&manualTypeRadio2.accesskey;"/>
<grid class="indent" flex="1">
--- a/browser/components/preferences/in-content/extensionControlled.js
+++ b/browser/components/preferences/in-content/extensionControlled.js
@@ -1,44 +1,76 @@
/* - 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.import("resource://gre/modules/XPCOMUtils.jsm");
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");
XPCOMUtils.defineLazyPreferenceGetter(this, "trackingprotectionUiEnabled",
"privacy.trackingprotection.ui.enabled");
const PREF_SETTING_TYPE = "prefs";
+const PROXY_KEY = "proxyConfig";
+const API_PROXY_PREFS = [
+ "network.proxy.type",
+ "network.proxy.http",
+ "network.proxy.http_port",
+ "network.proxy.share_proxy_settings",
+ "network.proxy.ftp",
+ "network.proxy.ftp_port",
+ "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",
"homepage_override": "browserHomePageExtensionContent",
"newTabURL": "browserNewTabExtensionContent",
"defaultSearch": "browserDefaultSearchExtensionContent",
+ "proxyConfig": "proxyExtensionContent",
get "websites.trackingProtectionMode"() {
return {
button: "trackingProtectionExtensionContentButton",
section:
trackingprotectionUiEnabled ?
"trackingProtectionExtensionContentLabel" :
"trackingProtectionPBMExtensionContentLabel",
};
}
};
+function getExtensionControlledArgs(settingName) {
+ switch (settingName) {
+ case "proxyConfig":
+ return [document.getElementById("bundleBrand").getString("brandShortName")];
+ default:
+ return [];
+ }
+}
+
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);
@@ -52,67 +84,77 @@ function getControllingExtensionEls(sett
section.querySelector("button");
return {
section,
button,
description: section.querySelector("description"),
};
}
-async function handleControllingExtension(type, settingName) {
+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] = info.id;
+ 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 getControllingExtensionFragment(settingName, addon, ...extraArgs) {
+ let msg = document.getElementById("bundlePreferences")
+ .getString(`extensionControlled.${settingName}`);
+ let image = document.createElement("image");
+ const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+ image.setAttribute("src", addon.iconURL || defaultIcon);
+ image.classList.add("extension-controlled-icon");
+ let addonBit = document.createDocumentFragment();
+ addonBit.appendChild(image);
+ addonBit.appendChild(document.createTextNode(" " + addon.name));
+ return BrowserUtils.getLocalizedFragment(document, msg, addonBit, ...extraArgs);
+}
+
async function showControllingExtension(settingName, addon) {
// Tell the user what extension is controlling the setting.
let elements = getControllingExtensionEls(settingName);
+ let extraArgs = getExtensionControlledArgs(settingName);
elements.section.classList.remove("extension-controlled-disabled");
let description = elements.description;
// Remove the old content from the description.
while (description.firstChild) {
description.firstChild.remove();
}
- // Populate the description.
- let msg = document.getElementById("bundlePreferences")
- .getString(`extensionControlled.${settingName}`);
- let image = document.createElement("image");
- const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
- image.setAttribute("src", addon.iconURL || defaultIcon);
- image.classList.add("extension-controlled-icon");
- let addonBit = document.createDocumentFragment();
- addonBit.appendChild(image);
- addonBit.appendChild(document.createTextNode(" " + addon.name));
- let fragment = BrowserUtils.getLocalizedFragment(document, msg, addonBit);
+ let fragment = getControllingExtensionFragment(
+ settingName, addon, ...extraArgs);
description.appendChild(fragment);
if (elements.button) {
elements.button.hidden = false;
}
// Show the controlling extension row and hide the old label.
elements.section.hidden = false;
@@ -155,8 +197,25 @@ function showEnableExtensionMessage(sett
function makeDisableControllingExtension(type, settingName) {
return async function disableExtension() {
let {id} = await getControllingExtensionInfo(type, settingName);
let addon = await AddonManager.getAddonByID(id);
addon.userDisabled = true;
};
}
+
+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);
+ });
+}
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -1,12 +1,13 @@
/* 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 extensionControlled.js */
/* import-globals-from preferences.js */
var gSearchResultsPane = {
listSearchTooltips: new Set(),
listSearchMenuitemIndicators: new Set(),
searchInput: null,
inited: false,
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -363,16 +363,23 @@ var gMainPane = {
handleControllingExtension(URL_OVERRIDES_TYPE, NEW_TAB_KEY);
},
};
Services.obs.addObserver(newTabObserver, "newtab-url-changed");
window.addEventListener("unload", () => {
Services.obs.removeObserver(newTabObserver, "newtab-url-changed");
});
+ let connectionSettingsLink = document.getElementById("connectionSettingsLearnMore");
+ let connectionSettingsUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") +
+ "prefs-connection-settings";
+ connectionSettingsLink.setAttribute("href", connectionSettingsUrl);
+ this.updateProxySettingsUI();
+ initializeProxyUI(gMainPane);
+
if (AppConstants.platform == "win") {
// Functionality for "Show tabs in taskbar" on Windows 7 and up.
try {
let ver = parseFloat(Services.sysinfo.getProperty("version"));
let showTabsInTaskbar = document.getElementById("showTabsInTaskbar");
showTabsInTaskbar.hidden = ver < 6.1;
} catch (ex) { }
}
@@ -1086,17 +1093,38 @@ var gMainPane = {
gSubDialog.open("chrome://browser/content/preferences/colors.xul", "resizable=no");
},
// NETWORK
/**
* Displays a dialog in which proxy settings may be changed.
*/
showConnections() {
- gSubDialog.open("chrome://browser/content/preferences/connection.xul");
+ gSubDialog.open("chrome://browser/content/preferences/connection.xul",
+ null, null, this.updateProxySettingsUI.bind(this));
+ },
+
+ // Update the UI to show the proper description depending on whether an
+ // extension is in control or not.
+ async updateProxySettingsUI() {
+ let controllingExtension = await getControllingExtension(PREF_SETTING_TYPE, PROXY_KEY);
+ let fragment = controllingExtension ?
+ getControllingExtensionFragment(PROXY_KEY, controllingExtension, this._brandShortName) :
+ BrowserUtils.getLocalizedFragment(
+ document,
+ this._prefsBundle.getString("connectionDesc.label"),
+ this._brandShortName);
+ let description = document.getElementById("connectionSettingsDescription");
+
+ // Remove the old content from the description.
+ while (description.firstChild) {
+ description.firstChild.remove();
+ }
+
+ description.appendChild(fragment);
},
checkBrowserContainers(event) {
let checkbox = document.getElementById("browserContainersCheckbox");
if (checkbox.checked) {
Services.prefs.setBoolPref("privacy.userContext.enabled", true);
return;
}
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -744,17 +744,25 @@
<label class="header-name" flex="1">&networkProxy.label;</label>
</hbox>
<!-- Network Proxy-->
<groupbox id="connectionGroup" data-category="paneGeneral" hidden="true">
<caption class="search-header" hidden="true"><label>&networkProxy.label;</label></caption>
<hbox align="center">
- <description flex="1" control="connectionSettings">&connectionDesc.label;</description>
+ <hbox align="center" flex="1">
+ <description id="connectionSettingsDescription" control="connectionSettings"></description>
+ <spacer width="5"/>
+ <label id="connectionSettingsLearnMore" class="learnMore text-link">
+ &connectionSettingsLearnMore.label;
+ </label>
+ <separator orient="vertical"/>
+ </hbox>
+
<!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
<hbox>
<button id="connectionSettings"
class="accessory-button"
icon="network"
label="&connectionSettings.label;"
accesskey="&connectionSettings.accesskey;"
searchkeywords="&connectionsDialog.title;
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -10,32 +10,22 @@
/* import-globals-from privacy.js */
/* import-globals-from sync.js */
/* import-globals-from findInPage.js */
/* import-globals-from ../../../base/content/utilityOverlay.js */
/* import-globals-from ../../../../toolkit/content/preferencesBindings.js */
"use strict";
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
-ChromeUtils.defineModuleGetter(this, "AddonManager",
- "resource://gre/modules/AddonManager.jsm");
-ChromeUtils.defineModuleGetter(this, "BrowserUtils",
- "resource://gre/modules/BrowserUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "ExtensionSettingsStore",
- "resource://gre/modules/ExtensionSettingsStore.jsm");
ChromeUtils.defineModuleGetter(this, "formAutofillParent",
"resource://formautofill/FormAutofillParent.jsm");
-XPCOMUtils.defineLazyPreferenceGetter(this, "trackingprotectionUiEnabled",
- "privacy.trackingprotection.ui.enabled");
-
var gLastHash = "";
var gCategoryInits = new Map();
function init_category_if_required(category) {
let categoryInfo = gCategoryInits.get(category);
if (!categoryInfo) {
throw "Unknown in-content prefs category! Can't init " + category;
}
@@ -412,153 +402,8 @@ function confirmRestartPrompt(aRestartTo
function appendSearchKeywords(aId, keywords) {
let element = document.getElementById(aId);
let searchKeywords = element.getAttribute("searchkeywords");
if (searchKeywords) {
keywords.push(searchKeywords);
}
element.setAttribute("searchkeywords", keywords.join(" "));
}
-
-const PREF_SETTING_TYPE = "prefs";
-
-let extensionControlledContentIds = {
- "privacy.containers": "browserContainersExtensionContent",
- "homepage_override": "browserHomePageExtensionContent",
- "newTabURL": "browserNewTabExtensionContent",
- "defaultSearch": "browserDefaultSearchExtensionContent",
- get "websites.trackingProtectionMode"() {
- return {
- button: "trackingProtectionExtensionContentButton",
- section:
- trackingprotectionUiEnabled ?
- "trackingProtectionExtensionContentLabel" :
- "trackingProtectionPBMExtensionContentLabel",
- };
- }
-};
-
-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 handleControllingExtension(type, settingName) {
- let info = await getControllingExtensionInfo(type, settingName);
- let addon = info && info.id
- && await AddonManager.getAddonByID(info.id);
-
- // 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] = info.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;
-}
-
-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;
-
- // Remove the old content from the description.
- while (description.firstChild) {
- description.firstChild.remove();
- }
-
- // Populate the description.
- let msg = document.getElementById("bundlePreferences")
- .getString(`extensionControlled.${settingName}`);
- let image = document.createElement("image");
- const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
- image.setAttribute("src", addon.iconURL || defaultIcon);
- image.classList.add("extension-controlled-icon");
- let addonBit = document.createDocumentFragment();
- addonBit.appendChild(image);
- addonBit.appendChild(document.createTextNode(" " + addon.name));
- let fragment = BrowserUtils.getLocalizedFragment(document, msg, addonBit);
- description.appendChild(fragment);
-
- 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");
- let icon = url => {
- let img = document.createElement("image");
- img.src = url;
- img.className = "extension-controlled-icon";
- return img;
- };
- let addonIcon = icon("chrome://mozapps/skin/extensions/extensionGeneric-16.svg");
- let toolbarIcon = icon("chrome://browser/skin/menu.svg");
- let message = document.getElementById("bundlePreferences")
- .getString("extensionControlled.enable");
- let frag = BrowserUtils.getLocalizedFragment(document, message, addonIcon, toolbarIcon);
- elements.description.innerHTML = "";
- elements.description.appendChild(frag);
- let dismissButton = document.createElement("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);
- addon.userDisabled = true;
- };
-}
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -1,12 +1,13 @@
/* 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 extensionControlled.js */
/* import-globals-from preferences.js */
/* FIXME: ESlint globals workaround should be removed once bug 1395426 gets fixed */
/* globals DownloadUtils, LoadContextInfo */
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
--- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -1,15 +1,18 @@
/* eslint-env webextensions */
+const PROXY_PREF = "network.proxy.type";
+
ChromeUtils.defineModuleGetter(this, "ExtensionSettingsStore",
"resource://gre/modules/ExtensionSettingsStore.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
+XPCOMUtils.defineLazyPreferenceGetter(this, "proxyType", PROXY_PREF);
const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
const CHROME_URL_ROOT = TEST_DIR + "/";
function getSupportsFile(path) {
let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIChromeRegistry);
let uri = Services.io.newURI(CHROME_URL_ROOT + path);
@@ -43,36 +46,47 @@ function waitForMutation(target, opts, c
observer.disconnect();
resolve();
}
});
observer.observe(target, opts);
});
}
-function waitForMessageChange(id, cb, opts = { attributes: true, attributeFilter: ["hidden"] }) {
- // eslint-disable-next-line mozilla/no-cpows-in-tests
- return waitForMutation(gBrowser.contentDocument.getElementById(id), opts, cb);
+function waitForMessageChange(element, cb, opts = { attributes: true, attributeFilter: ["hidden"] }) {
+ return waitForMutation(element, opts, cb);
}
-function waitForMessageHidden(messageId) {
- return waitForMessageChange(messageId, target => target.hidden);
+// eslint-disable-next-line mozilla/no-cpows-in-tests
+function getElement(id, doc = gBrowser.contentDocument) {
+ return doc.getElementById(id);
+}
+
+function waitForMessageHidden(messageId, doc) {
+ return waitForMessageChange(getElement(messageId, doc), target => target.hidden);
}
-function waitForMessageShown(messageId) {
- return waitForMessageChange(messageId, target => !target.hidden);
+function waitForMessageShown(messageId, doc) {
+ return waitForMessageChange(getElement(messageId, doc), target => !target.hidden);
}
-function waitForEnableMessage(messageId) {
+function waitForEnableMessage(messageId, doc) {
return waitForMessageChange(
- messageId,
+ getElement(messageId, doc),
target => target.classList.contains("extension-controlled-disabled"),
{ attributeFilter: ["class"], attributes: true });
}
+function waitForMessageContent(messageId, content, doc) {
+ return waitForMessageChange(
+ getElement(messageId, doc),
+ target => target.textContent === content,
+ { childList: true });
+}
+
add_task(async function testExtensionControlledHomepage() {
await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
// eslint-disable-next-line mozilla/no-cpows-in-tests
let doc = gBrowser.contentDocument;
is(gBrowser.currentURI.spec, "about:preferences#general",
"#general should be in the URI for about:preferences");
let homepagePref = () => Services.prefs.getCharPref("browser.startup.homepage");
let originalHomepagePref = homepagePref();
@@ -599,8 +613,198 @@ add_task(async function testExtensionCon
// ExtensionPreferencesManager to clean up properly.
// TODO: BUG 1408226
await reEnableExtension(addon);
await extension.unload();
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
+
+add_task(async function testExtensionControlledProxyConfig() {
+ const proxySvc = Ci.nsIProtocolProxyService;
+ const PROXY_DEFAULT = proxySvc.PROXYCONFIG_SYSTEM;
+ const EXTENSION_ID = "@set_proxy";
+ const CONTROLLED_SECTION_ID = "proxyExtensionContent";
+ const CONTROLLED_BUTTON_ID = "disableProxyExtension";
+ const CONNECTION_SETTINGS_DESC_ID = "connectionSettingsDescription";
+ const PANEL_URL = "chrome://browser/content/preferences/connection.xul";
+
+ await SpecialPowers.pushPrefEnv({"set": [[PROXY_PREF, PROXY_DEFAULT]]});
+
+ function background() {
+ browser.browserSettings.proxyConfig.set({value: {proxyType: "none"}});
+ }
+
+ function expectedConnectionSettingsMessage(doc, isControlled) {
+ let brandShortName = doc.getElementById("bundleBrand").getString("brandShortName");
+ return isControlled ?
+ `An extension, set_proxy, is controlling how ${brandShortName} connects to the internet.` :
+ `Configure how ${brandShortName} connects to the internet.`;
+ }
+
+ function connectionSettingsMessagePromise(doc, isControlled) {
+ return waitForMessageContent(
+ CONNECTION_SETTINGS_DESC_ID,
+ expectedConnectionSettingsMessage(doc, isControlled)
+ );
+ }
+
+ function verifyState(doc, isControlled) {
+ let isPanel = doc.getElementById(CONTROLLED_BUTTON_ID);
+ let brandShortName = doc.getElementById("bundleBrand").getString("brandShortName");
+ is(proxyType === proxySvc.PROXYCONFIG_DIRECT, isControlled,
+ "Proxy pref is set to the expected value.");
+
+ if (isPanel) {
+ let controlledSection = doc.getElementById(CONTROLLED_SECTION_ID);
+
+ is(controlledSection.hidden, !isControlled, "The extension controlled row's visibility is as expected.");
+ if (isPanel) {
+ is(doc.getElementById(CONTROLLED_BUTTON_ID).hidden, !isControlled,
+ "The disable extension button's visibility is as expected.");
+ }
+ if (isControlled) {
+ let controlledDesc = controlledSection.querySelector("description");
+ // There are two spaces before "set_proxy" because it's " <image /> set_proxy".
+ is(controlledDesc.textContent, `An extension, set_proxy, is controlling how ${brandShortName} connects to the internet.`,
+ "The user is notified that an extension is controlling proxy settings.");
+ }
+ function getProxyControls() {
+ let controlGroup = doc.getElementById("networkProxyType");
+ return [
+ ...controlGroup.querySelectorAll(":scope > radio"),
+ ...controlGroup.querySelectorAll("label"),
+ ...controlGroup.querySelectorAll("textbox"),
+ ...controlGroup.querySelectorAll("checkbox"),
+ ...doc.querySelectorAll("#networkProxySOCKSVersion > radio"),
+ ...doc.querySelectorAll("#ConnectionsDialogPane > checkbox"),
+ ];
+ }
+ let controlState = isControlled ? "disabled" : "enabled";
+ for (let element of getProxyControls()) {
+ is(element.disabled, isControlled, `Proxy controls are ${controlState}.`);
+ }
+ } else {
+ is(doc.getElementById(CONNECTION_SETTINGS_DESC_ID).textContent,
+ expectedConnectionSettingsMessage(doc, isControlled),
+ "The connection settings description is as expected.");
+ }
+ }
+
+ async function disableViaClick() {
+ let sectionId = CONTROLLED_SECTION_ID;
+ let controlledSection = panelDoc.getElementById(sectionId);
+
+ let enableMessageShown = waitForEnableMessage(sectionId, panelDoc);
+ panelDoc.getElementById(CONTROLLED_BUTTON_ID).click();
+ await enableMessageShown;
+
+ // The user is notified how to enable the extension.
+ let controlledDesc = controlledSection.querySelector("description");
+ is(controlledDesc.textContent, "To enable the extension go to Add-ons in the menu.",
+ "The user is notified of how to enable the extension again");
+
+ // The user can dismiss the enable instructions.
+ let hidden = waitForMessageHidden(sectionId, panelDoc);
+ controlledSection.querySelector("image:last-of-type").click();
+ return hidden;
+ }
+
+ async function reEnableExtension(addon) {
+ let messageChanged = connectionSettingsMessagePromise(mainDoc, true);
+ addon.userDisabled = false;
+ await messageChanged;
+ }
+
+ async function openProxyPanel() {
+ let panel = await openAndLoadSubDialog(PANEL_URL);
+ let closingPromise = waitForEvent(panel.document.documentElement, "dialogclosing");
+ ok(panel, "Proxy panel opened.");
+ return {panel, closingPromise};
+ }
+
+ async function closeProxyPanel(panelObj) {
+ panelObj.panel.document.documentElement.cancelDialog();
+ let panelClosingEvent = await panelObj.closingPromise;
+ ok(panelClosingEvent, "Proxy panel closed.");
+ }
+
+ await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
+ // eslint-disable-next-line mozilla/no-cpows-in-tests
+ let mainDoc = gBrowser.contentDocument;
+
+ is(gBrowser.currentURI.spec, "about:preferences#general",
+ "#general should be in the URI for about:preferences");
+
+ verifyState(mainDoc, false);
+
+ // Open the connections panel.
+ let panelObj = await openProxyPanel();
+ let panelDoc = panelObj.panel.document;
+
+ verifyState(panelDoc, false);
+
+ await closeProxyPanel(panelObj);
+
+ verifyState(mainDoc, false);
+
+ // Install an extension that sets Tracking Protection.
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ name: "set_proxy",
+ applications: {gecko: {id: EXTENSION_ID}},
+ permissions: ["browserSettings"],
+ },
+ background,
+ });
+
+ let messageChanged = connectionSettingsMessagePromise(mainDoc, true);
+ await extension.startup();
+ await messageChanged;
+ let addon = await AddonManager.getAddonByID(EXTENSION_ID);
+
+ verifyState(mainDoc, true);
+ messageChanged = connectionSettingsMessagePromise(mainDoc, false);
+
+ panelObj = await openProxyPanel();
+ panelDoc = panelObj.panel.document;
+
+ verifyState(panelDoc, true);
+
+ await disableViaClick();
+
+ verifyState(panelDoc, false);
+
+ await closeProxyPanel(panelObj);
+ await messageChanged;
+
+ verifyState(mainDoc, false);
+
+ await reEnableExtension(addon);
+
+ verifyState(mainDoc, true);
+ messageChanged = connectionSettingsMessagePromise(mainDoc, false);
+
+ panelObj = await openProxyPanel();
+ panelDoc = panelObj.panel.document;
+
+ verifyState(panelDoc, true);
+
+ await disableViaClick();
+
+ verifyState(panelDoc, false);
+
+ await closeProxyPanel(panelObj);
+ await messageChanged;
+
+ verifyState(mainDoc, false);
+
+ // Enable the extension so we get the UNINSTALL event, which is needed by
+ // ExtensionPreferencesManager to clean up properly.
+ // TODO: BUG 1408226
+ await reEnableExtension(addon);
+
+ await extension.unload();
+
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
--- a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
@@ -40,17 +40,17 @@ available. -->
<!ENTITY alwaysSubmitCrashReports1.label "Allow &brandShortName; to send crash reports to Mozilla">
<!ENTITY alwaysSubmitCrashReports1.accesskey "c">
<!ENTITY crashReporterLearnMore.label "Learn more">
<!ENTITY networkTab.label "Network">
<!ENTITY networkProxy.label "Network Proxy">
-<!ENTITY connectionDesc.label "Configure how &brandShortName; connects to the Internet">
+<!ENTITY connectionSettingsLearnMore.label "Learn more">
<!ENTITY connectionSettings.label "Settingsā¦">
<!ENTITY connectionSettings.accesskey "e">
<!ENTITY httpCache.label "Cached Web Content">
<!-- Site Data section manages sites using Storage API and is under Network -->
<!ENTITY siteData.label "Site Data">
<!ENTITY clearSiteData.label "Clear All Data">
--- a/browser/locales/en-US/chrome/browser/preferences/connection.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/connection.dtd
@@ -2,17 +2,17 @@
- 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/. -->
<!ENTITY connectionsDialog.title "Connection Settings">
<!ENTITY window.width2 "49em">
<!ENTITY window.macWidth2 "44em">
-<!ENTITY proxyTitle.label "Configure Proxies to Access the Internet">
+<!ENTITY proxyTitle.label2 "Configure Proxy Access to the Internet">
<!ENTITY noProxyTypeRadio.label "No proxy">
<!ENTITY noProxyTypeRadio.accesskey "y">
<!ENTITY systemTypeRadio.label "Use system proxy settings">
<!ENTITY systemTypeRadio.accesskey "u">
<!ENTITY WPADTypeRadio.label "Auto-detect proxy settings for this network">
<!ENTITY WPADTypeRadio.accesskey "w">
<!ENTITY manualTypeRadio2.label "Manual proxy configuration">
<!ENTITY manualTypeRadio2.accesskey "m">
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -290,13 +290,23 @@ extensionControlled.defaultSearch = An e
# This string is shown to notify the user that Container Tabs are being enabled by an extension
# %S is the container addon controlling it
extensionControlled.privacy.containers = An extension, %S, requires Container Tabs.
# LOCALIZATION NOTE (extensionControlled.websites.trackingProtectionMode):
# This string is shown to notify the user that their tracking protection preferences are being controlled by an extension.
extensionControlled.websites.trackingProtectionMode = An extension, %S, is controlling tracking protection.
+# LOCALIZATION NOTE (extensionControlled.proxyConfig):
+# This string is shown to notify the user that their proxy configuration preferences are being controlled by an extension.
+# %1$S is the icon and name of the extension.
+# %2$S is the brandShortName from brand.properties (for example "Nightly")
+extensionControlled.proxyConfig = An extension, %1$S, is controlling how %2$S connects to the internet.
+
# LOCALIZATION NOTE (extensionControlled.enable):
# %1$S is replaced with the icon for the add-ons menu.
# %2$S is replaced with the icon for the toolbar menu.
# This string is shown to notify the user how to enable an extension that they disabled.
extensionControlled.enable = To enable the extension go to %1$S Add-ons in the %2$S menu.
+
+# LOCALIZATION NOTE (connectionDesc.label):
+# %S is the brandShortName from brand.properties (for example "Nightly")
+connectionDesc.label = Configure how %S connects to the internet.