Bug 1455755 Move browserSettings.proxyConfig to proxy.settings, r?aswan, mstriemer draft
authorShane Caraveo <scaraveo@mozilla.com>
Wed, 25 Apr 2018 18:37:09 -0500
changeset 788138 1775ab61da6db9604ba5cc158a671997525766bc
parent 788112 99c19a66c3a2fbf8108d4b8a161cded31e948409
push id107913
push usermixedpuppy@gmail.com
push dateWed, 25 Apr 2018 23:37:46 +0000
reviewersaswan, mstriemer
bugs1455755
milestone61.0a1
Bug 1455755 Move browserSettings.proxyConfig to proxy.settings, r?aswan, mstriemer MozReview-Commit-ID: I2gMgr4D2HC
browser/components/preferences/connection.js
browser/components/preferences/in-content/extensionControlled.js
browser/components/preferences/in-content/tests/browser_extension_controlled.js
toolkit/components/extensions/ExtensionPreferencesManager.jsm
toolkit/components/extensions/parent/ext-browserSettings.js
toolkit/components/extensions/parent/ext-proxy.js
toolkit/components/extensions/schemas/browser_settings.json
toolkit/components/extensions/schemas/proxy.json
toolkit/components/extensions/test/xpcshell/test_ext_browserSettings.js
toolkit/components/extensions/test/xpcshell/test_ext_proxy_config.js
toolkit/components/extensions/test/xpcshell/test_ext_proxy_settings.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
--- a/browser/components/preferences/connection.js
+++ b/browser/components/preferences/connection.js
@@ -269,13 +269,13 @@ var gConnectionsDialog = {
       }
     }
 
     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)
+      handleControllingExtension(PREF_SETTING_TYPE, PROXY_KEY, "extensionControlled.proxyConfig")
         .then(setInputsDisabledState);
     }
   }
 };
--- a/browser/components/preferences/in-content/extensionControlled.js
+++ b/browser/components/preferences/in-content/extensionControlled.js
@@ -16,17 +16,17 @@ ChromeUtils.defineModuleGetter(this, "De
                                   "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 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.ftp",
   "network.proxy.ftp_port",
   "network.proxy.ssl",
@@ -40,31 +40,31 @@ const API_PROXY_PREFS = [
   "signon.autologin.proxy",
 ];
 
 let extensionControlledContentIds = {
   "privacy.containers": "browserContainersExtensionContent",
   "homepage_override": "browserHomePageExtensionContent",
   "newTabURL": "browserNewTabExtensionContent",
   "defaultSearch": "browserDefaultSearchExtensionContent",
-  "proxyConfig": "proxyExtensionContent",
+  "proxy.settings": "proxyExtensionContent",
   get "websites.trackingProtectionMode"() {
     return {
       button: "trackingProtectionExtensionContentButton",
       section:
         trackingprotectionUiEnabled ?
           "trackingProtectionExtensionContentLabel" :
           "trackingProtectionPBMExtensionContentLabel",
     };
   }
 };
 
 function getExtensionControlledArgs(settingName) {
   switch (settingName) {
-    case "proxyConfig":
+    case "proxy.settings":
       return [document.getElementById("bundleBrand").getString("brandShortName")];
     default:
       return [];
   }
 }
 
 let extensionControlledIds = {};
 
--- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -641,17 +641,17 @@ add_task(async function testExtensionCon
   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"}});
+    browser.proxy.settings.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.`;
   }
@@ -775,17 +775,17 @@ add_task(async function testExtensionCon
   verifyState(mainDoc, false);
 
   // Install an extension that controls proxy settings.
   let extension = ExtensionTestUtils.loadExtension({
     useAddonManager: "permanent",
     manifest: {
       name: "set_proxy",
       applications: {gecko: {id: EXTENSION_ID}},
-      permissions: ["browserSettings"],
+      permissions: ["proxy"],
     },
     background,
   });
 
   let messageChanged = connectionSettingsMessagePromise(mainDoc, true);
   await extension.startup();
   await messageChanged;
   let addon = await AddonManager.getAddonByID(EXTENSION_ID);
--- a/toolkit/components/extensions/ExtensionPreferencesManager.jsm
+++ b/toolkit/components/extensions/ExtensionPreferencesManager.jsm
@@ -344,9 +344,62 @@ this.ExtensionPreferencesManager = {
         if (Preferences.locked(prefName)) {
           return "not_controllable";
         }
       }
     }
     await ExtensionSettingsStore.initialize();
     return ExtensionSettingsStore.getLevelOfControl(id, storeType, name);
   },
+
+  /**
+   * Returns an API object with get/set/clear used for a setting.
+   *
+   * @param {string} extensionId
+   * @param {string} name
+   *        The unique id of the setting.
+   * @param {Function} callback
+   *        The function that retreives the current setting from prefs.
+   * @param {string} storeType
+   *        The name of the store in ExtensionSettingsStore.
+   *        Defaults to STORE_TYPE.
+   * @param {boolean} readOnly
+   * @param {Function} validate
+   *        Utility function for any specific validation, such as checking
+   *        for supported platform.  Function should throw an error if necessary.
+   *
+   * @returns {object} API object with get/set/clear methods
+   */
+  getSettingsAPI(extensionId, name, callback, storeType, readOnly = false, validate = () => {}) {
+    return {
+      async get(details) {
+        validate();
+        let levelOfControl = details.incognito ?
+          "not_controllable" :
+          await ExtensionPreferencesManager.getLevelOfControl(
+            extensionId, name, storeType);
+        levelOfControl =
+          (readOnly && levelOfControl === "controllable_by_this_extension") ?
+            "not_controllable" :
+            levelOfControl;
+        return {
+          levelOfControl,
+          value: await callback(),
+        };
+      },
+      set(details) {
+        validate();
+        if (!readOnly) {
+          return ExtensionPreferencesManager.setSetting(
+            extensionId, name, details.value);
+        }
+        return false;
+      },
+      clear(details) {
+        validate();
+        if (!readOnly) {
+          return ExtensionPreferencesManager.removeSetting(extensionId, name);
+        }
+        return false;
+      },
+    };
+  },
 };
--- a/toolkit/components/extensions/parent/ext-browserSettings.js
+++ b/toolkit/components/extensions/parent/ext-browserSettings.js
@@ -11,76 +11,27 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 ChromeUtils.import("resource://gre/modules/ExtensionPreferencesManager.jsm");
 
 var {
   ExtensionError,
 } = ExtensionUtils;
-
-const proxySvc = Ci.nsIProtocolProxyService;
-
-const PROXY_TYPES_MAP = new Map([
-  ["none", proxySvc.PROXYCONFIG_DIRECT],
-  ["autoDetect", proxySvc.PROXYCONFIG_WPAD],
-  ["system", proxySvc.PROXYCONFIG_SYSTEM],
-  ["manual", proxySvc.PROXYCONFIG_MANUAL],
-  ["autoConfig", proxySvc.PROXYCONFIG_PAC],
-]);
+var {
+  getSettingsAPI,
+} = ExtensionPreferencesManager;
 
 const HOMEPAGE_OVERRIDE_SETTING = "homepage_override";
 const HOMEPAGE_URL_PREF = "browser.startup.homepage";
 const URL_STORE_TYPE = "url_overrides";
 const NEW_TAB_OVERRIDE_SETTING = "newTabURL";
 
 const PERM_DENY_ACTION = Services.perms.DENY_ACTION;
 
-const checkUnsupported = (name, unsupportedPlatforms) => {
-  if (unsupportedPlatforms.includes(AppConstants.platform)) {
-    throw new ExtensionError(
-      `${AppConstants.platform} is not a supported platform for the ${name} setting.`);
-  }
-};
-
-const getSettingsAPI = (extension, name, callback, storeType, readOnly = false, unsupportedPlatforms = []) => {
-  return {
-    async get(details) {
-      checkUnsupported(name, unsupportedPlatforms);
-      let levelOfControl = details.incognito ?
-        "not_controllable" :
-        await ExtensionPreferencesManager.getLevelOfControl(
-          extension.id, name, storeType);
-      levelOfControl =
-        (readOnly && levelOfControl === "controllable_by_this_extension") ?
-          "not_controllable" :
-          levelOfControl;
-      return {
-        levelOfControl,
-        value: await callback(),
-      };
-    },
-    set(details) {
-      checkUnsupported(name, unsupportedPlatforms);
-      if (!readOnly) {
-        return ExtensionPreferencesManager.setSetting(
-          extension.id, name, details.value);
-      }
-      return false;
-    },
-    clear(details) {
-      checkUnsupported(name, unsupportedPlatforms);
-      if (!readOnly) {
-        return ExtensionPreferencesManager.removeSetting(extension.id, name);
-      }
-      return false;
-    },
-  };
-};
-
 // Add settings objects for supported APIs to the preferences manager.
 ExtensionPreferencesManager.addSetting("allowPopupsForUserEvents", {
   prefNames: [
     "dom.popup_allowed_events",
   ],
 
   setCallback(value) {
     let returnObj = {};
@@ -174,62 +125,16 @@ ExtensionPreferencesManager.addSetting("
     "browser.urlbar.openintab",
   ],
 
   setCallback(value) {
     return {[this.prefNames[0]]: value};
   },
 });
 
-ExtensionPreferencesManager.addSetting("proxyConfig", {
-  prefNames: [
-    "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",
-  ],
-
-  setCallback(value) {
-    let prefs = {
-      "network.proxy.type": PROXY_TYPES_MAP.get(value.proxyType),
-      "signon.autologin.proxy": value.autoLogin,
-      "network.proxy.socks_remote_dns": value.proxyDNS,
-      "network.proxy.autoconfig_url": value.autoConfigUrl,
-      "network.proxy.share_proxy_settings": value.httpProxyAll,
-      "network.proxy.socks_version": value.socksVersion,
-      "network.proxy.no_proxies_on": value.passthrough,
-    };
-
-    for (let prop of ["http", "ftp", "ssl", "socks"]) {
-      if (value[prop]) {
-        let url = new URL(`http://${value[prop]}`);
-        prefs[`network.proxy.${prop}`] = url.hostname;
-        let port = parseInt(url.port, 10);
-        prefs[`network.proxy.${prop}_port`] = isNaN(port) ? 0 : port;
-      } else {
-        prefs[`network.proxy.${prop}`] = undefined;
-        prefs[`network.proxy.${prop}_port`] = undefined;
-      }
-    }
-
-    return prefs;
-  },
-});
-
 ExtensionPreferencesManager.addSetting("webNotificationsDisabled", {
   prefNames: [
     "permissions.default.desktop-notification",
   ],
 
   setCallback(value) {
     return {[this.prefNames[0]]: value ? PERM_DENY_ACTION : undefined};
   },
@@ -256,34 +161,39 @@ ExtensionPreferencesManager.addSetting("
 });
 
 this.browserSettings = class extends ExtensionAPI {
   getAPI(context) {
     let {extension} = context;
     return {
       browserSettings: {
         allowPopupsForUserEvents: getSettingsAPI(
-          extension, "allowPopupsForUserEvents",
+          extension.id, "allowPopupsForUserEvents",
           () => {
             return Services.prefs.getCharPref("dom.popup_allowed_events") != "";
           }),
         cacheEnabled: getSettingsAPI(
-          extension, "cacheEnabled",
+          extension.id, "cacheEnabled",
           () => {
             return Services.prefs.getBoolPref("browser.cache.disk.enable") &&
               Services.prefs.getBoolPref("browser.cache.memory.enable");
           }),
         closeTabsByDoubleClick: getSettingsAPI(
-          extension, "closeTabsByDoubleClick",
+          extension.id, "closeTabsByDoubleClick",
           () => {
             return Services.prefs.getBoolPref("browser.tabs.closeTabByDblclick");
-          }, undefined, false, ["android"]),
+          }, undefined, false, () => {
+            if (AppConstants.platform == "android") {
+              throw new ExtensionError(
+                `android is not a supported platform for the closeTabsByDoubleClick setting.`);
+            }
+          }),
         contextMenuShowEvent: Object.assign(
           getSettingsAPI(
-            extension, "contextMenuShowEvent",
+            extension.id, "contextMenuShowEvent",
             () => {
               if (AppConstants.platform === "win") {
                 return "mouseup";
               }
               let prefValue = Services.prefs.getBoolPref(
                 "ui.context_menus.after_mouseup", null);
               return prefValue ? "mouseup" : "mousedown";
             }
@@ -300,165 +210,68 @@ this.browserSettings = class extends Ext
                 return false;
               }
               return ExtensionPreferencesManager.setSetting(
                 extension.id, "contextMenuShowEvent", details.value);
             },
           }
         ),
         homepageOverride: getSettingsAPI(
-          extension, HOMEPAGE_OVERRIDE_SETTING,
+          extension.id, HOMEPAGE_OVERRIDE_SETTING,
           () => {
             return Services.prefs.getComplexValue(
               HOMEPAGE_URL_PREF, Ci.nsIPrefLocalizedString).data;
           }, undefined, true),
         imageAnimationBehavior: getSettingsAPI(
-          extension, "imageAnimationBehavior",
+          extension.id, "imageAnimationBehavior",
           () => {
             return Services.prefs.getCharPref("image.animation_mode");
           }),
         newTabPosition: getSettingsAPI(
-          extension, "newTabPosition",
+          extension.id, "newTabPosition",
           () => {
             if (Services.prefs.getBoolPref("browser.tabs.insertAfterCurrent")) {
               return "afterCurrent";
             }
             if (Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
               return "relatedAfterCurrent";
             }
             return "atEnd";
           }),
         newTabPageOverride: getSettingsAPI(
-          extension, NEW_TAB_OVERRIDE_SETTING,
+          extension.id, NEW_TAB_OVERRIDE_SETTING,
           () => {
             return aboutNewTabService.newTabURL;
           }, URL_STORE_TYPE, true),
         openBookmarksInNewTabs: getSettingsAPI(
-          extension, "openBookmarksInNewTabs",
+          extension.id, "openBookmarksInNewTabs",
           () => {
             return Services.prefs.getBoolPref("browser.tabs.loadBookmarksInTabs");
           }),
         openSearchResultsInNewTabs: getSettingsAPI(
-          extension, "openSearchResultsInNewTabs",
+          extension.id, "openSearchResultsInNewTabs",
           () => {
             return Services.prefs.getBoolPref("browser.search.openintab");
           }),
         openUrlbarResultsInNewTabs: getSettingsAPI(
-          extension, "openUrlbarResultsInNewTabs",
+          extension.id, "openUrlbarResultsInNewTabs",
           () => {
             return Services.prefs.getBoolPref("browser.urlbar.openintab");
           }),
-        proxyConfig: Object.assign(
-          getSettingsAPI(
-            extension, "proxyConfig",
-            () => {
-              let prefValue = Services.prefs.getIntPref("network.proxy.type");
-              let proxyConfig = {
-                proxyType:
-                  Array.from(
-                    PROXY_TYPES_MAP.entries()).find(entry => entry[1] === prefValue)[0],
-                autoConfigUrl: Services.prefs.getCharPref("network.proxy.autoconfig_url"),
-                autoLogin: Services.prefs.getBoolPref("signon.autologin.proxy"),
-                proxyDNS: Services.prefs.getBoolPref("network.proxy.socks_remote_dns"),
-                httpProxyAll: Services.prefs.getBoolPref("network.proxy.share_proxy_settings"),
-                socksVersion: Services.prefs.getIntPref("network.proxy.socks_version"),
-                passthrough: Services.prefs.getCharPref("network.proxy.no_proxies_on"),
-              };
-
-              for (let prop of ["http", "ftp", "ssl", "socks"]) {
-                let host = Services.prefs.getCharPref(`network.proxy.${prop}`);
-                let port = Services.prefs.getIntPref(`network.proxy.${prop}_port`);
-                proxyConfig[prop] = port ? `${host}:${port}` : host;
-              }
-
-              return proxyConfig;
-            },
-            // proxyConfig is unsupported on android.
-            undefined, false, ["android"]
-          ),
-          {
-            set: details => {
-              if (AppConstants.platform === "android") {
-                throw new ExtensionError(
-                  "proxyConfig is not supported on android.");
-              }
-
-              if (!Services.policies.isAllowed("changeProxySettings")) {
-                throw new ExtensionError(
-                  "Proxy settings are being managed by the Policies manager.");
-              }
-
-              let value = details.value;
-
-              if (!PROXY_TYPES_MAP.has(value.proxyType)) {
-                throw new ExtensionError(
-                  `${value.proxyType} is not a valid value for proxyType.`);
-              }
-
-              if (value.httpProxyAll) {
-                // Match what about:preferences does with proxy settings
-                // since the proxy service does not check the value
-                // of share_proxy_settings.
-                for (let prop of ["ftp", "ssl", "socks"]) {
-                  value[prop] = value.http;
-                }
-              }
-
-              for (let prop of ["http", "ftp", "ssl", "socks"]) {
-                let host = value[prop];
-                if (host) {
-                  try {
-                    // Fixup in case a full url is passed.
-                    if (host.includes("://")) {
-                      value[prop] = new URL(host).host;
-                    } else {
-                      // Validate the host value.
-                      new URL(`http://${host}`);
-                    }
-                  } catch (e) {
-                    throw new ExtensionError(
-                      `${value[prop]} is not a valid value for ${prop}.`);
-                  }
-                }
-              }
-
-              if (value.proxyType === "autoConfig" || value.autoConfigUrl) {
-                try {
-                  new URL(value.autoConfigUrl);
-                } catch (e) {
-                  throw new ExtensionError(
-                    `${value.autoConfigUrl} is not a valid value for autoConfigUrl.`);
-                }
-              }
-
-              if (value.socksVersion !== undefined) {
-                if (!Number.isInteger(value.socksVersion) ||
-                    value.socksVersion < 4 ||
-                    value.socksVersion > 5) {
-                  throw new ExtensionError(
-                    `${value.socksVersion} is not a valid value for socksVersion.`);
-                }
-              }
-
-              return ExtensionPreferencesManager.setSetting(
-                extension.id, "proxyConfig", value);
-            },
-          }
-        ),
         webNotificationsDisabled: getSettingsAPI(
-          extension, "webNotificationsDisabled",
+          extension.id, "webNotificationsDisabled",
           () => {
             let prefValue =
               Services.prefs.getIntPref(
                 "permissions.default.desktop-notification", null);
             return prefValue === PERM_DENY_ACTION;
           }),
         overrideDocumentColors: Object.assign(
           getSettingsAPI(
-            extension, "overrideDocumentColors",
+            extension.id, "overrideDocumentColors",
             () => {
               let prefValue = Services.prefs.getIntPref("browser.display.document_color_use");
               if (prefValue === 1) {
                 return "never";
               } else if (prefValue === 2) {
                 return "always";
               }
               return "high-contrast-only";
@@ -478,17 +291,17 @@ this.browserSettings = class extends Ext
               }
               return ExtensionPreferencesManager.setSetting(
                 extension.id, "overrideDocumentColors", prefValue);
             },
           }
         ),
         useDocumentFonts: Object.assign(
           getSettingsAPI(
-            extension, "useDocumentFonts",
+            extension.id, "useDocumentFonts",
             () => {
               return Services.prefs.getIntPref("browser.display.use_document_fonts") !== 0;
             }
           ),
           {
             set: details => {
               if (typeof details.value !== "boolean") {
                 throw new ExtensionError(
--- a/toolkit/components/extensions/parent/ext-proxy.js
+++ b/toolkit/components/extensions/parent/ext-proxy.js
@@ -5,20 +5,84 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 ChromeUtils.defineModuleGetter(this, "ProxyScriptContext",
                                "resource://gre/modules/ProxyScriptContext.jsm");
 ChromeUtils.defineModuleGetter(this, "ProxyChannelFilter",
                                "resource://gre/modules/ProxyScriptContext.jsm");
+ChromeUtils.import("resource://gre/modules/ExtensionPreferencesManager.jsm");
+
+var {
+  ExtensionError,
+} = ExtensionUtils;
+var {
+  getSettingsAPI,
+} = ExtensionPreferencesManager;
 
 // WeakMap[Extension -> ProxyScriptContext]
 const proxyScriptContextMap = new WeakMap();
 
+const proxySvc = Ci.nsIProtocolProxyService;
+
+const PROXY_TYPES_MAP = new Map([
+  ["none", proxySvc.PROXYCONFIG_DIRECT],
+  ["autoDetect", proxySvc.PROXYCONFIG_WPAD],
+  ["system", proxySvc.PROXYCONFIG_SYSTEM],
+  ["manual", proxySvc.PROXYCONFIG_MANUAL],
+  ["autoConfig", proxySvc.PROXYCONFIG_PAC],
+]);
+
+ExtensionPreferencesManager.addSetting("proxy.settings", {
+  prefNames: [
+    "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",
+  ],
+
+  setCallback(value) {
+    let prefs = {
+      "network.proxy.type": PROXY_TYPES_MAP.get(value.proxyType),
+      "signon.autologin.proxy": value.autoLogin,
+      "network.proxy.socks_remote_dns": value.proxyDNS,
+      "network.proxy.autoconfig_url": value.autoConfigUrl,
+      "network.proxy.share_proxy_settings": value.httpProxyAll,
+      "network.proxy.socks_version": value.socksVersion,
+      "network.proxy.no_proxies_on": value.passthrough,
+    };
+
+    for (let prop of ["http", "ftp", "ssl", "socks"]) {
+      if (value[prop]) {
+        let url = new URL(`http://${value[prop]}`);
+        prefs[`network.proxy.${prop}`] = url.hostname;
+        let port = parseInt(url.port, 10);
+        prefs[`network.proxy.${prop}_port`] = isNaN(port) ? 0 : port;
+      } else {
+        prefs[`network.proxy.${prop}`] = undefined;
+        prefs[`network.proxy.${prop}_port`] = undefined;
+      }
+    }
+
+    return prefs;
+  },
+});
+
 // EventManager-like class specifically for Proxy filters. Inherits from
 // EventManager. Takes care of converting |details| parameter
 // when invoking listeners.
 class ProxyFilterEventManager extends EventManager {
   constructor(context, eventName) {
     let name = `proxy.${eventName}`;
     let register = (fire, filterProps, extraInfoSpec = []) => {
       let listener = (data) => {
@@ -99,12 +163,115 @@ this.proxy = class extends ExtensionAPI 
         },
 
         onRequest: new ProxyFilterEventManager(context, "onRequest").api(),
 
         onError,
 
         // TODO Bug 1388619 deprecate onProxyError.
         onProxyError: onError,
+
+        settings: Object.assign(
+          getSettingsAPI(
+            extension.id, "proxy.settings",
+            () => {
+              let prefValue = Services.prefs.getIntPref("network.proxy.type");
+              let proxyConfig = {
+                proxyType:
+                  Array.from(
+                    PROXY_TYPES_MAP.entries()).find(entry => entry[1] === prefValue)[0],
+                autoConfigUrl: Services.prefs.getCharPref("network.proxy.autoconfig_url"),
+                autoLogin: Services.prefs.getBoolPref("signon.autologin.proxy"),
+                proxyDNS: Services.prefs.getBoolPref("network.proxy.socks_remote_dns"),
+                httpProxyAll: Services.prefs.getBoolPref("network.proxy.share_proxy_settings"),
+                socksVersion: Services.prefs.getIntPref("network.proxy.socks_version"),
+                passthrough: Services.prefs.getCharPref("network.proxy.no_proxies_on"),
+              };
+
+              for (let prop of ["http", "ftp", "ssl", "socks"]) {
+                let host = Services.prefs.getCharPref(`network.proxy.${prop}`);
+                let port = Services.prefs.getIntPref(`network.proxy.${prop}_port`);
+                proxyConfig[prop] = port ? `${host}:${port}` : host;
+              }
+
+              return proxyConfig;
+            },
+            // proxy.settings is unsupported on android.
+            undefined, false, () => {
+              if (AppConstants.platform == "android") {
+                throw new ExtensionError(
+                  `proxy.settings is not supported on android.`);
+              }
+            },
+          ),
+          {
+            set: details => {
+              if (AppConstants.platform === "android") {
+                throw new ExtensionError(
+                  "proxy.settings is not supported on android.");
+              }
+
+              if (!Services.policies.isAllowed("changeProxySettings")) {
+                throw new ExtensionError(
+                  "Proxy settings are being managed by the Policies manager.");
+              }
+
+              let value = details.value;
+
+              if (!PROXY_TYPES_MAP.has(value.proxyType)) {
+                throw new ExtensionError(
+                  `${value.proxyType} is not a valid value for proxyType.`);
+              }
+
+              if (value.httpProxyAll) {
+                // Match what about:preferences does with proxy settings
+                // since the proxy service does not check the value
+                // of share_proxy_settings.
+                for (let prop of ["ftp", "ssl", "socks"]) {
+                  value[prop] = value.http;
+                }
+              }
+
+              for (let prop of ["http", "ftp", "ssl", "socks"]) {
+                let host = value[prop];
+                if (host) {
+                  try {
+                    // Fixup in case a full url is passed.
+                    if (host.includes("://")) {
+                      value[prop] = new URL(host).host;
+                    } else {
+                      // Validate the host value.
+                      new URL(`http://${host}`);
+                    }
+                  } catch (e) {
+                    throw new ExtensionError(
+                      `${value[prop]} is not a valid value for ${prop}.`);
+                  }
+                }
+              }
+
+              if (value.proxyType === "autoConfig" || value.autoConfigUrl) {
+                try {
+                  new URL(value.autoConfigUrl);
+                } catch (e) {
+                  throw new ExtensionError(
+                    `${value.autoConfigUrl} is not a valid value for autoConfigUrl.`);
+                }
+              }
+
+              if (value.socksVersion !== undefined) {
+                if (!Number.isInteger(value.socksVersion) ||
+                    value.socksVersion < 4 ||
+                    value.socksVersion > 5) {
+                  throw new ExtensionError(
+                    `${value.socksVersion} is not a valid value for socksVersion.`);
+                }
+              }
+
+              return ExtensionPreferencesManager.setSetting(
+                extension.id, "proxy.settings", value);
+            },
+          }
+        ),
       },
     };
   }
 };
--- a/toolkit/components/extensions/schemas/browser_settings.json
+++ b/toolkit/components/extensions/schemas/browser_settings.json
@@ -30,87 +30,16 @@
         "enum": ["normal", "none", "once"],
         "description": "How images should be animated in the browser."
       },
       {
         "id": "ContextMenuMouseEvent",
         "type": "string",
         "enum": ["mouseup", "mousedown"],
         "description": "After which mouse event context menus should popup."
-      },
-      {
-        "id": "ProxyConfig",
-        "type": "object",
-        "description": "An object which describes proxy settings.",
-        "properties": {
-          "proxyType": {
-            "type": "string",
-            "optional": true,
-            "enum": [
-              "none",
-              "autoDetect",
-              "system",
-              "manual",
-              "autoConfig"
-            ],
-            "description": "The type of proxy to use."
-          },
-          "http": {
-            "type": "string",
-            "optional": true,
-            "description": "The address of the http proxy, can include a port."
-          },
-          "httpProxyAll":{
-            "type": "boolean",
-            "optional": true,
-            "description": "Use the http proxy server for all protocols."
-          },
-          "ftp": {
-            "type": "string",
-            "optional": true,
-            "description": "The address of the ftp proxy, can include a port."
-          },
-          "ssl": {
-            "type": "string",
-            "optional": true,
-            "description": "The address of the ssl proxy, can include a port."
-          },
-          "socks": {
-            "type": "string",
-            "optional": true,
-            "description": "The address of the socks proxy, can include a port."
-          },
-          "socksVersion": {
-            "type": "integer",
-            "optional": true,
-            "description": "The version of the socks proxy.",
-            "minimum": 4,
-            "maximum": 5
-          },
-          "passthrough": {
-            "type": "string",
-            "optional": true,
-            "description": "A list of hosts which should not be proxied."
-          },
-          "autoConfigUrl": {
-            "type": "string",
-            "optional": true,
-            "description": "A URL to use to configure the proxy."
-          },
-          "autoLogin": {
-            "type": "boolean",
-            "optional": true,
-            "description": "Do not prompt for authentication if password is saved."
-          },
-          "proxyDNS": {
-            "type": "boolean",
-            "optional": true,
-            "description": "Proxy DNS when using SOCKS v5."
-          }
-        }
       }
     ],
     "properties": {
       "allowPopupsForUserEvents": {
         "$ref": "types.Setting",
         "description": "Allows or disallows pop-up windows from opening in response to user events."
       },
       "cacheEnabled": {
@@ -148,20 +77,16 @@
       "openSearchResultsInNewTabs": {
         "$ref": "types.Setting",
         "description": "This boolean setting controls whether search results are opened in the current tab or in a new tab."
       },
       "openUrlbarResultsInNewTabs": {
         "$ref": "types.Setting",
         "description": "This boolean setting controls whether urlbar results are opened in the current tab or in a new tab."
       },
-      "proxyConfig": {
-        "$ref": "types.Setting",
-        "description": "Configures proxy settings. This setting's value is an object of type ProxyConfig."
-      },
       "webNotificationsDisabled": {
         "$ref": "types.Setting",
         "description": "Disables webAPI notifications."
       },
       "overrideDocumentColors": {
         "$ref": "types.Setting",
         "description": "This setting controls whether the user-chosen colors override the page's colors."
       },
--- a/toolkit/components/extensions/schemas/proxy.json
+++ b/toolkit/components/extensions/schemas/proxy.json
@@ -12,16 +12,95 @@
         }]
       }
     ]
   },
   {
     "namespace": "proxy",
     "description": "Use the browser.proxy API to register proxy scripts in Firefox. Proxy scripts in Firefox are proxy auto-config files with extra contextual information and support for additional return types.",
     "permissions": ["proxy"],
+    "types": [
+      {
+        "id": "ProxyConfig",
+        "type": "object",
+        "description": "An object which describes proxy settings.",
+        "properties": {
+          "proxyType": {
+            "type": "string",
+            "optional": true,
+            "enum": [
+              "none",
+              "autoDetect",
+              "system",
+              "manual",
+              "autoConfig"
+            ],
+            "description": "The type of proxy to use."
+          },
+          "http": {
+            "type": "string",
+            "optional": true,
+            "description": "The address of the http proxy, can include a port."
+          },
+          "httpProxyAll":{
+            "type": "boolean",
+            "optional": true,
+            "description": "Use the http proxy server for all protocols."
+          },
+          "ftp": {
+            "type": "string",
+            "optional": true,
+            "description": "The address of the ftp proxy, can include a port."
+          },
+          "ssl": {
+            "type": "string",
+            "optional": true,
+            "description": "The address of the ssl proxy, can include a port."
+          },
+          "socks": {
+            "type": "string",
+            "optional": true,
+            "description": "The address of the socks proxy, can include a port."
+          },
+          "socksVersion": {
+            "type": "integer",
+            "optional": true,
+            "description": "The version of the socks proxy.",
+            "minimum": 4,
+            "maximum": 5
+          },
+          "passthrough": {
+            "type": "string",
+            "optional": true,
+            "description": "A list of hosts which should not be proxied."
+          },
+          "autoConfigUrl": {
+            "type": "string",
+            "optional": true,
+            "description": "A URL to use to configure the proxy."
+          },
+          "autoLogin": {
+            "type": "boolean",
+            "optional": true,
+            "description": "Do not prompt for authentication if password is saved."
+          },
+          "proxyDNS": {
+            "type": "boolean",
+            "optional": true,
+            "description": "Proxy DNS when using SOCKS v5."
+          }
+        }
+      }
+    ],
+    "properties": {
+      "settings": {
+        "$ref": "types.Setting",
+        "description": "Configures proxy settings. This setting's value is an object of type ProxyConfig."
+      }
+    },
     "functions": [
       {
         "name": "register",
         "type": "function",
         "description": "Registers the proxy script for the extension.",
         "async": true,
         "parameters": [
           {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings.js
@@ -13,46 +13,30 @@ const {
   promiseStartupManager,
 } = AddonTestUtils;
 
 AddonTestUtils.init(this);
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
 add_task(async function test_browser_settings() {
-  const proxySvc = Ci.nsIProtocolProxyService;
   const PERM_DENY_ACTION = Services.perms.DENY_ACTION;
   const PERM_UNKNOWN_ACTION = Services.perms.UNKNOWN_ACTION;
 
   // Create an object to hold the values to which we will initialize the prefs.
   const PREFS = {
     "browser.cache.disk.enable": true,
     "browser.cache.memory.enable": true,
     "dom.popup_allowed_events": Preferences.get("dom.popup_allowed_events"),
     "image.animation_mode": "none",
     "permissions.default.desktop-notification": PERM_UNKNOWN_ACTION,
     "ui.context_menus.after_mouseup": false,
     "browser.tabs.closeTabByDblclick": false,
     "browser.tabs.loadBookmarksInTabs": false,
     "browser.search.openintab": false,
-    "network.proxy.type": proxySvc.PROXYCONFIG_SYSTEM,
-    "network.proxy.http": "",
-    "network.proxy.http_port": 0,
-    "network.proxy.share_proxy_settings": false,
-    "network.proxy.ftp": "",
-    "network.proxy.ftp_port": 0,
-    "network.proxy.ssl": "",
-    "network.proxy.ssl_port": 0,
-    "network.proxy.socks": "",
-    "network.proxy.socks_port": 0,
-    "network.proxy.socks_version": 5,
-    "network.proxy.socks_remote_dns": false,
-    "network.proxy.no_proxies_on": "localhost, 127.0.0.1",
-    "network.proxy.autoconfig_url": "",
-    "signon.autologin.proxy": false,
     "browser.tabs.insertRelatedAfterCurrent": true,
     "browser.tabs.insertAfterCurrent": false,
     "browser.display.document_color_use": 1,
     "browser.display.use_document_fonts": 1,
   };
 
   async function background() {
     browser.test.onMessage.addListener(async (msg, apiName, value) => {
@@ -230,185 +214,16 @@ add_task(async function test_browser_set
 
   await testSetting(
     "useDocumentFonts", false,
     {"browser.display.use_document_fonts": 0});
   await testSetting(
     "useDocumentFonts", true,
     {"browser.display.use_document_fonts": 1});
 
-  async function testProxy(config, expectedPrefs, expectedConfig = config) {
-    // proxyConfig is not supported on Android.
-    if (AppConstants.platform === "android") {
-      return Promise.resolve();
-    }
-
-    let proxyConfig = {
-      proxyType: "none",
-      autoConfigUrl: "",
-      autoLogin: false,
-      proxyDNS: false,
-      httpProxyAll: false,
-      socksVersion: 5,
-      passthrough: "localhost, 127.0.0.1",
-      http: "",
-      ftp: "",
-      ssl: "",
-      socks: "",
-    };
-    return testSetting(
-      "proxyConfig", config, expectedPrefs, Object.assign(proxyConfig, expectedConfig)
-    );
-  }
-
-  await testProxy(
-    {proxyType: "none"},
-    {"network.proxy.type": proxySvc.PROXYCONFIG_DIRECT},
-  );
-
-  await testProxy(
-    {
-      proxyType: "autoDetect",
-      autoLogin: true,
-      proxyDNS: true,
-    },
-    {
-      "network.proxy.type": proxySvc.PROXYCONFIG_WPAD,
-      "signon.autologin.proxy": true,
-      "network.proxy.socks_remote_dns": true,
-    },
-  );
-
-  await testProxy(
-    {
-      proxyType: "system",
-      autoLogin: false,
-      proxyDNS: false,
-    },
-    {
-      "network.proxy.type": proxySvc.PROXYCONFIG_SYSTEM,
-      "signon.autologin.proxy": false,
-      "network.proxy.socks_remote_dns": false,
-    },
-  );
-
-  await testProxy(
-    {
-      proxyType: "autoConfig",
-      autoConfigUrl: "http://mozilla.org",
-    },
-    {
-      "network.proxy.type": proxySvc.PROXYCONFIG_PAC,
-      "network.proxy.autoconfig_url": "http://mozilla.org",
-    },
-  );
-
-  await testProxy(
-    {
-      proxyType: "manual",
-      http: "http://www.mozilla.org",
-      autoConfigUrl: "",
-    },
-    {
-      "network.proxy.type": proxySvc.PROXYCONFIG_MANUAL,
-      "network.proxy.http": "www.mozilla.org",
-      "network.proxy.http_port": 0,
-      "network.proxy.autoconfig_url": "",
-    },
-    {
-      proxyType: "manual",
-      http: "www.mozilla.org",
-      autoConfigUrl: "",
-    }
-  );
-
-  // When using proxyAll, we expect all proxies to be set to
-  // be the same as http.
-  await testProxy(
-    {
-      proxyType: "manual",
-      http: "http://www.mozilla.org:8080",
-      ftp: "http://www.mozilla.org:1234",
-      httpProxyAll: true,
-    },
-    {
-      "network.proxy.type": proxySvc.PROXYCONFIG_MANUAL,
-      "network.proxy.http": "www.mozilla.org",
-      "network.proxy.http_port": 8080,
-      "network.proxy.ftp": "www.mozilla.org",
-      "network.proxy.ftp_port": 8080,
-      "network.proxy.ssl": "www.mozilla.org",
-      "network.proxy.ssl_port": 8080,
-      "network.proxy.socks": "www.mozilla.org",
-      "network.proxy.socks_port": 8080,
-      "network.proxy.share_proxy_settings": true,
-    },
-    {
-      proxyType: "manual",
-      http: "www.mozilla.org:8080",
-      ftp: "www.mozilla.org:8080",
-      ssl: "www.mozilla.org:8080",
-      socks: "www.mozilla.org:8080",
-      httpProxyAll: true,
-    }
-  );
-
-  await testProxy(
-    {
-      proxyType: "manual",
-      http: "www.mozilla.org:8080",
-      httpProxyAll: false,
-      ftp: "www.mozilla.org:8081",
-      ssl: "www.mozilla.org:8082",
-      socks: "mozilla.org:8083",
-      socksVersion: 4,
-      passthrough: ".mozilla.org",
-    },
-    {
-      "network.proxy.type": proxySvc.PROXYCONFIG_MANUAL,
-      "network.proxy.http": "www.mozilla.org",
-      "network.proxy.http_port": 8080,
-      "network.proxy.share_proxy_settings": false,
-      "network.proxy.ftp": "www.mozilla.org",
-      "network.proxy.ftp_port": 8081,
-      "network.proxy.ssl": "www.mozilla.org",
-      "network.proxy.ssl_port": 8082,
-      "network.proxy.socks": "mozilla.org",
-      "network.proxy.socks_port": 8083,
-      "network.proxy.socks_version": 4,
-      "network.proxy.no_proxies_on": ".mozilla.org",
-    }
-  );
-
-  // Test resetting values.
-  await testProxy(
-    {
-      proxyType: "none",
-      http: "",
-      ftp: "",
-      ssl: "",
-      socks: "",
-      socksVersion: 5,
-      passthrough: "",
-    },
-    {
-      "network.proxy.type": proxySvc.PROXYCONFIG_DIRECT,
-      "network.proxy.http": "",
-      "network.proxy.http_port": 0,
-      "network.proxy.ftp": "",
-      "network.proxy.ftp_port": 0,
-      "network.proxy.ssl": "",
-      "network.proxy.ssl_port": 0,
-      "network.proxy.socks": "",
-      "network.proxy.socks_port": 0,
-      "network.proxy.socks_version": 5,
-      "network.proxy.no_proxies_on": "",
-    }
-  );
-
   await extension.unload();
   await promiseShutdownManager();
 });
 
 add_task(async function test_bad_value() {
   async function background() {
     await browser.test.assertRejects(
       browser.browserSettings.contextMenuShowEvent.set({value: "bad"}),
@@ -470,84 +285,8 @@ add_task(async function test_bad_value_a
       permissions: ["browserSettings"],
     },
   });
 
   await extension.startup();
   await extension.awaitMessage("done");
   await extension.unload();
 });
-
-add_task(async function test_bad_value_proxy_config() {
-  let background = AppConstants.platform === "android" ?
-    async () => {
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "none",
-        }}),
-        /proxyConfig is not supported on android/,
-        "proxyConfig.set rejects on Android.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.get({}),
-        /android is not a supported platform for the proxyConfig setting/,
-        "proxyConfig.get rejects on Android.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.clear({}),
-        /android is not a supported platform for the proxyConfig setting/,
-        "proxyConfig.clear rejects on Android.");
-
-      browser.test.sendMessage("done");
-    } :
-    async () => {
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "abc",
-        }}),
-        /abc is not a valid value for proxyType/,
-        "proxyConfig.set rejects with an invalid proxyType value.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "autoConfig",
-        }}),
-        /undefined is not a valid value for autoConfigUrl/,
-        "proxyConfig.set for type autoConfig rejects with an empty autoConfigUrl value.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "autoConfig",
-          autoConfigUrl: "abc",
-        }}),
-        /abc is not a valid value for autoConfigUrl/,
-        "proxyConfig.set rejects with an invalid autoConfigUrl value.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "manual",
-          socksVersion: "abc",
-        }}),
-        /abc is not a valid value for socksVersion/,
-        "proxyConfig.set rejects with an invalid socksVersion value.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "manual",
-          socksVersion: 3,
-        }}),
-        /3 is not a valid value for socksVersion/,
-        "proxyConfig.set rejects with an invalid socksVersion value.");
-
-      browser.test.sendMessage("done");
-    };
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background,
-    manifest: {
-      permissions: ["browserSettings"],
-    },
-  });
-
-  await extension.startup();
-  await extension.awaitMessage("done");
-  await extension.unload();
-});
copy from toolkit/components/extensions/test/xpcshell/test_ext_browserSettings.js
copy to toolkit/components/extensions/test/xpcshell/test_ext_proxy_config.js
--- a/toolkit/components/extensions/test/xpcshell/test_ext_browserSettings.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_config.js
@@ -1,10 +1,8 @@
-/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 ChromeUtils.defineModuleGetter(this, "ExtensionPreferencesManager",
                                "resource://gre/modules/ExtensionPreferencesManager.jsm");
 ChromeUtils.defineModuleGetter(this, "Preferences",
                                "resource://gre/modules/Preferences.jsm");
 
 const {
@@ -14,54 +12,39 @@ const {
 } = AddonTestUtils;
 
 AddonTestUtils.init(this);
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
 add_task(async function test_browser_settings() {
   const proxySvc = Ci.nsIProtocolProxyService;
-  const PERM_DENY_ACTION = Services.perms.DENY_ACTION;
-  const PERM_UNKNOWN_ACTION = Services.perms.UNKNOWN_ACTION;
 
   // Create an object to hold the values to which we will initialize the prefs.
   const PREFS = {
-    "browser.cache.disk.enable": true,
-    "browser.cache.memory.enable": true,
-    "dom.popup_allowed_events": Preferences.get("dom.popup_allowed_events"),
-    "image.animation_mode": "none",
-    "permissions.default.desktop-notification": PERM_UNKNOWN_ACTION,
-    "ui.context_menus.after_mouseup": false,
-    "browser.tabs.closeTabByDblclick": false,
-    "browser.tabs.loadBookmarksInTabs": false,
-    "browser.search.openintab": false,
     "network.proxy.type": proxySvc.PROXYCONFIG_SYSTEM,
     "network.proxy.http": "",
     "network.proxy.http_port": 0,
     "network.proxy.share_proxy_settings": false,
     "network.proxy.ftp": "",
     "network.proxy.ftp_port": 0,
     "network.proxy.ssl": "",
     "network.proxy.ssl_port": 0,
     "network.proxy.socks": "",
     "network.proxy.socks_port": 0,
     "network.proxy.socks_version": 5,
     "network.proxy.socks_remote_dns": false,
     "network.proxy.no_proxies_on": "localhost, 127.0.0.1",
     "network.proxy.autoconfig_url": "",
     "signon.autologin.proxy": false,
-    "browser.tabs.insertRelatedAfterCurrent": true,
-    "browser.tabs.insertAfterCurrent": false,
-    "browser.display.document_color_use": 1,
-    "browser.display.use_document_fonts": 1,
   };
 
   async function background() {
-    browser.test.onMessage.addListener(async (msg, apiName, value) => {
-      let apiObj = browser.browserSettings[apiName];
+    browser.test.onMessage.addListener(async (msg, value) => {
+      let apiObj = browser.proxy.settings;
       let result = await apiObj.set({value});
       if (msg === "set") {
         browser.test.assertTrue(result, "set returns true.");
         browser.test.sendMessage("settingData", await apiObj.get({}));
       } else {
         browser.test.assertFalse(result, "set returns false for a no-op.");
         browser.test.sendMessage("no-op set");
       }
@@ -78,170 +61,38 @@ add_task(async function test_browser_set
     for (let pref in PREFS) {
       Preferences.reset(pref);
     }
   });
 
   let extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
-      permissions: ["browserSettings"],
+      permissions: ["proxy"],
     },
     useAddonManager: "temporary",
   });
 
   await promiseStartupManager();
   await extension.startup();
 
-  async function testSetting(setting, value, expected, expectedValue = value) {
-    extension.sendMessage("set", setting, value);
+  async function testSetting(value, expected, expectedValue = value) {
+    extension.sendMessage("set", value);
     let data = await extension.awaitMessage("settingData");
     deepEqual(data.value, expectedValue,
-              `The ${setting} setting has the expected value.`);
+              `The setting has the expected value.`);
     equal(data.levelOfControl, "controlled_by_this_extension",
-          `The ${setting} setting has the expected levelOfControl.`);
-    for (let pref in expected) {
-      equal(Preferences.get(pref), expected[pref], `${pref} set correctly for ${value}`);
-    }
-  }
-
-  async function testNoOpSetting(setting, value, expected) {
-    extension.sendMessage("setNoOp", setting, value);
-    await extension.awaitMessage("no-op set");
+          `The setting has the expected levelOfControl.`);
     for (let pref in expected) {
       equal(Preferences.get(pref), expected[pref], `${pref} set correctly for ${value}`);
     }
   }
 
-  await testSetting(
-    "cacheEnabled", false,
-    {
-      "browser.cache.disk.enable": false,
-      "browser.cache.memory.enable": false,
-    });
-  await testSetting(
-    "cacheEnabled", true,
-    {
-      "browser.cache.disk.enable": true,
-      "browser.cache.memory.enable": true,
-    });
-
-  await testSetting(
-    "allowPopupsForUserEvents", false,
-    {"dom.popup_allowed_events": ""});
-  await testSetting(
-    "allowPopupsForUserEvents", true,
-    {"dom.popup_allowed_events": PREFS["dom.popup_allowed_events"]});
-
-  for (let value of ["normal", "none", "once"]) {
-    await testSetting(
-      "imageAnimationBehavior", value,
-      {"image.animation_mode": value});
-  }
-
-  await testSetting(
-    "webNotificationsDisabled", true,
-    {"permissions.default.desktop-notification": PERM_DENY_ACTION});
-  await testSetting(
-    "webNotificationsDisabled", false,
-    {
-      // This pref is not defaulted on Android.
-      "permissions.default.desktop-notification":
-        AppConstants.MOZ_BUILD_APP !== "browser" ? undefined : PERM_UNKNOWN_ACTION,
-    });
-
-  // This setting is a no-op on Android.
-  if (AppConstants.platform === "android") {
-    await testNoOpSetting("contextMenuShowEvent", "mouseup",
-                          {"ui.context_menus.after_mouseup": false});
-  } else {
-    await testSetting(
-      "contextMenuShowEvent", "mouseup",
-      {"ui.context_menus.after_mouseup": true});
-  }
-
-  // "mousedown" is also a no-op on Windows.
-  if (["android", "win"].includes(AppConstants.platform)) {
-    await testNoOpSetting(
-      "contextMenuShowEvent", "mousedown",
-      {"ui.context_menus.after_mouseup": AppConstants.platform === "win"});
-  } else {
-    await testSetting(
-      "contextMenuShowEvent", "mousedown",
-      {"ui.context_menus.after_mouseup": false});
-  }
-
-  if (AppConstants.platform !== "android") {
-    await testSetting(
-      "closeTabsByDoubleClick", true,
-      {"browser.tabs.closeTabByDblclick": true});
-    await testSetting(
-      "closeTabsByDoubleClick", false,
-      {"browser.tabs.closeTabByDblclick": false});
-  }
-
-  await testSetting(
-    "newTabPosition", "afterCurrent",
-    {
-      "browser.tabs.insertRelatedAfterCurrent": false,
-      "browser.tabs.insertAfterCurrent": true,
-    });
-  await testSetting(
-    "newTabPosition", "atEnd",
-    {
-      "browser.tabs.insertRelatedAfterCurrent": false,
-      "browser.tabs.insertAfterCurrent": false,
-    });
-  await testSetting(
-    "newTabPosition", "relatedAfterCurrent",
-    {
-      "browser.tabs.insertRelatedAfterCurrent": true,
-      "browser.tabs.insertAfterCurrent": false,
-    });
-
-  await testSetting(
-    "openBookmarksInNewTabs", true,
-    {"browser.tabs.loadBookmarksInTabs": true});
-  await testSetting(
-    "openBookmarksInNewTabs", false,
-    {"browser.tabs.loadBookmarksInTabs": false});
-
-  await testSetting(
-    "openSearchResultsInNewTabs", true,
-    {"browser.search.openintab": true});
-  await testSetting(
-    "openSearchResultsInNewTabs", false,
-    {"browser.search.openintab": false});
-
-  await testSetting(
-    "openUrlbarResultsInNewTabs", true,
-    {"browser.urlbar.openintab": true});
-  await testSetting(
-    "openUrlbarResultsInNewTabs", false,
-    {"browser.urlbar.openintab": false});
-
-  await testSetting(
-    "overrideDocumentColors", "high-contrast-only",
-    {"browser.display.document_color_use": 0});
-  await testSetting(
-    "overrideDocumentColors", "never",
-    {"browser.display.document_color_use": 1});
-  await testSetting(
-    "overrideDocumentColors", "always",
-    {"browser.display.document_color_use": 2});
-
-  await testSetting(
-    "useDocumentFonts", false,
-    {"browser.display.use_document_fonts": 0});
-  await testSetting(
-    "useDocumentFonts", true,
-    {"browser.display.use_document_fonts": 1});
-
   async function testProxy(config, expectedPrefs, expectedConfig = config) {
-    // proxyConfig is not supported on Android.
+    // proxy.settings is not supported on Android.
     if (AppConstants.platform === "android") {
       return Promise.resolve();
     }
 
     let proxyConfig = {
       proxyType: "none",
       autoConfigUrl: "",
       autoLogin: false,
@@ -250,17 +101,17 @@ add_task(async function test_browser_set
       socksVersion: 5,
       passthrough: "localhost, 127.0.0.1",
       http: "",
       ftp: "",
       ssl: "",
       socks: "",
     };
     return testSetting(
-      "proxyConfig", config, expectedPrefs, Object.assign(proxyConfig, expectedConfig)
+      config, expectedPrefs, Object.assign(proxyConfig, expectedConfig)
     );
   }
 
   await testProxy(
     {proxyType: "none"},
     {"network.proxy.type": proxySvc.PROXYCONFIG_DIRECT},
   );
 
@@ -403,151 +254,83 @@ add_task(async function test_browser_set
       "network.proxy.no_proxies_on": "",
     }
   );
 
   await extension.unload();
   await promiseShutdownManager();
 });
 
-add_task(async function test_bad_value() {
-  async function background() {
-    await browser.test.assertRejects(
-      browser.browserSettings.contextMenuShowEvent.set({value: "bad"}),
-      /bad is not a valid value for contextMenuShowEvent/,
-      "contextMenuShowEvent.set rejects with an invalid value.");
+add_task(async function test_bad_value_proxy_config() {
+  let background = AppConstants.platform === "android" ?
+    async () => {
+      await browser.test.assertRejects(
+        browser.proxy.settings.set({value: {
+          proxyType: "none",
+        }}),
+        /proxy.settings is not supported on android/,
+        "proxy.settings.set rejects on Android.");
+
+      await browser.test.assertRejects(
+        browser.proxy.settings.get({}),
+        /proxy.settings is not supported on android/,
+        "proxy.settings.get rejects on Android.");
+
+      await browser.test.assertRejects(
+        browser.proxy.settings.clear({}),
+        /proxy.settings is not supported on android/,
+        "proxy.settings.clear rejects on Android.");
+
+      browser.test.sendMessage("done");
+    } :
+    async () => {
+      await browser.test.assertRejects(
+        browser.proxy.settings.set({value: {
+          proxyType: "abc",
+        }}),
+        /abc is not a valid value for proxyType/,
+        "proxy.settings.set rejects with an invalid proxyType value.");
 
-    await browser.test.assertRejects(
-      browser.browserSettings.overrideDocumentColors.set({value: 2}),
-      /2 is not a valid value for overrideDocumentColors/,
-      "overrideDocumentColors.set rejects with an invalid value.");
+      await browser.test.assertRejects(
+        browser.proxy.settings.set({value: {
+          proxyType: "autoConfig",
+        }}),
+        /undefined is not a valid value for autoConfigUrl/,
+        "proxy.settings.set for type autoConfig rejects with an empty autoConfigUrl value.");
+
+      await browser.test.assertRejects(
+        browser.proxy.settings.set({value: {
+          proxyType: "autoConfig",
+          autoConfigUrl: "abc",
+        }}),
+        /abc is not a valid value for autoConfigUrl/,
+        "proxy.settings.set rejects with an invalid autoConfigUrl value.");
 
-    await browser.test.assertRejects(
-      browser.browserSettings.overrideDocumentColors.set({value: "bad"}),
-      /bad is not a valid value for overrideDocumentColors/,
-      "overrideDocumentColors.set rejects with an invalid value.");
+      await browser.test.assertRejects(
+        browser.proxy.settings.set({value: {
+          proxyType: "manual",
+          socksVersion: "abc",
+        }}),
+        /abc is not a valid value for socksVersion/,
+        "proxy.settings.set rejects with an invalid socksVersion value.");
 
-    browser.test.sendMessage("done");
-  }
+      await browser.test.assertRejects(
+        browser.proxy.settings.set({value: {
+          proxyType: "manual",
+          socksVersion: 3,
+        }}),
+        /3 is not a valid value for socksVersion/,
+        "proxy.settings.set rejects with an invalid socksVersion value.");
+
+      browser.test.sendMessage("done");
+    };
 
   let extension = ExtensionTestUtils.loadExtension({
     background,
     manifest: {
-      permissions: ["browserSettings"],
-    },
-  });
-
-  await extension.startup();
-  await extension.awaitMessage("done");
-  await extension.unload();
-});
-
-add_task(async function test_bad_value_android() {
-  if (AppConstants.platform !== "android") {
-    return;
-  }
-
-  async function background() {
-    await browser.test.assertRejects(
-      browser.browserSettings.closeTabsByDoubleClick.set({value: true}),
-      /android is not a supported platform for the closeTabsByDoubleClick setting/,
-      "closeTabsByDoubleClick.set rejects on Android.");
-
-    await browser.test.assertRejects(
-      browser.browserSettings.closeTabsByDoubleClick.get({}),
-      /android is not a supported platform for the closeTabsByDoubleClick setting/,
-      "closeTabsByDoubleClick.get rejects on Android.");
-
-    await browser.test.assertRejects(
-      browser.browserSettings.closeTabsByDoubleClick.clear({}),
-      /android is not a supported platform for the closeTabsByDoubleClick setting/,
-      "closeTabsByDoubleClick.clear rejects on Android.");
-
-    browser.test.sendMessage("done");
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background,
-    manifest: {
-      permissions: ["browserSettings"],
+      permissions: ["proxy"],
     },
   });
 
   await extension.startup();
   await extension.awaitMessage("done");
   await extension.unload();
 });
-
-add_task(async function test_bad_value_proxy_config() {
-  let background = AppConstants.platform === "android" ?
-    async () => {
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "none",
-        }}),
-        /proxyConfig is not supported on android/,
-        "proxyConfig.set rejects on Android.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.get({}),
-        /android is not a supported platform for the proxyConfig setting/,
-        "proxyConfig.get rejects on Android.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.clear({}),
-        /android is not a supported platform for the proxyConfig setting/,
-        "proxyConfig.clear rejects on Android.");
-
-      browser.test.sendMessage("done");
-    } :
-    async () => {
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "abc",
-        }}),
-        /abc is not a valid value for proxyType/,
-        "proxyConfig.set rejects with an invalid proxyType value.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "autoConfig",
-        }}),
-        /undefined is not a valid value for autoConfigUrl/,
-        "proxyConfig.set for type autoConfig rejects with an empty autoConfigUrl value.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "autoConfig",
-          autoConfigUrl: "abc",
-        }}),
-        /abc is not a valid value for autoConfigUrl/,
-        "proxyConfig.set rejects with an invalid autoConfigUrl value.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "manual",
-          socksVersion: "abc",
-        }}),
-        /abc is not a valid value for socksVersion/,
-        "proxyConfig.set rejects with an invalid socksVersion value.");
-
-      await browser.test.assertRejects(
-        browser.browserSettings.proxyConfig.set({value: {
-          proxyType: "manual",
-          socksVersion: 3,
-        }}),
-        /3 is not a valid value for socksVersion/,
-        "proxyConfig.set rejects with an invalid socksVersion value.");
-
-      browser.test.sendMessage("done");
-    };
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background,
-    manifest: {
-      permissions: ["browserSettings"],
-    },
-  });
-
-  await extension.startup();
-  await extension.awaitMessage("done");
-  await extension.unload();
-});
--- a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_settings.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_settings.js
@@ -39,29 +39,29 @@ add_task(async function test_proxy_setti
     browser.webRequest.onCompleted.addListener(details => {
       browser.test.notifyPass("proxytest");
     }, {urls: ["http://example.com/*"]});
     browser.webRequest.onErrorOccurred.addListener(details => {
       browser.test.notifyFail("proxytest");
     }, {urls: ["http://example.com/*"]});
 
     // Wait for the settings before testing a request.
-    await browser.browserSettings.proxyConfig.set({value: {
+    await browser.proxy.settings.set({value: {
       proxyType: "manual",
       http: `${host}:${port}`,
     }});
     browser.test.sendMessage("ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
+      applications: {gecko: {id: "proxy.settings@mochi.test"}},
       permissions: [
         "proxy",
         "webRequest",
-        "browserSettings",
         "<all_urls>",
       ],
     },
     useAddonManager: "temporary",
     background: `(${background})("${proxy.identity.primaryHost}", ${proxy.identity.primaryPort})`,
   });
 
   await promiseStartupManager();
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -61,16 +61,17 @@ skip-if = (os == "win" && !debug) #Bug 1
 [test_ext_onmessage_removelistener.js]
 skip-if = true # This test no longer tests what it is meant to test.
 [test_ext_permission_xhr.js]
 [test_ext_persistent_events.js]
 [test_ext_privacy.js]
 [test_ext_privacy_disable.js]
 [test_ext_privacy_update.js]
 [test_ext_proxy_auth.js]
+[test_ext_proxy_config.js]
 [test_ext_proxy_onauthrequired.js]
 [test_ext_proxy_settings.js]
 skip-if = os == "android" # proxy settings are not supported on android
 [test_ext_proxy_socks.js]
 [test_ext_proxy_speculative.js]
 [test_ext_redirects.js]
 [test_ext_runtime_connect_no_receiver.js]
 [test_ext_runtime_getBrowserInfo.js]