Bug 1419102 - Implement the Enterprise Policies feature to provide enterprise users with easier control and setup of deployments of Firefox. r=Mossop
authorFelipe Gomes <felipc@gmail.com>
Mon, 22 Jan 2018 17:01:48 -0200
changeset 400181 2dcf8a5409c24530f207fb65406c1d113ce0e5aa
parent 400180 132539103be0931373d54713cf47e8e7f53aff4b
child 400182 e981314d055fcb8955b90999c69f45281a81cbee
child 400278 c4ebc8c28a33b785dfbfa533810517cc707d1ad0
push id99110
push userfelipc@gmail.com
push dateMon, 22 Jan 2018 19:02:24 +0000
treeherdermozilla-inbound@2dcf8a5409c2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMossop
bugs1419102
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1419102 - Implement the Enterprise Policies feature to provide enterprise users with easier control and setup of deployments of Firefox. r=Mossop This feature is currently disabled behind a pref, but this pref will be removed when we're green to release it. MozReview-Commit-ID: 3ZH2UJVdtC0
.eslintignore
browser/components/enterprisepolicies/EnterprisePolicies.js
browser/components/enterprisepolicies/EnterprisePolicies.manifest
browser/components/enterprisepolicies/EnterprisePoliciesContent.js
browser/components/enterprisepolicies/Policies.jsm
browser/components/enterprisepolicies/PoliciesValidator.jsm
browser/components/enterprisepolicies/helpers/moz.build
browser/components/enterprisepolicies/helpers/sample.json
browser/components/enterprisepolicies/moz.build
browser/components/enterprisepolicies/schemas/configuration.json
browser/components/enterprisepolicies/schemas/moz.build
browser/components/enterprisepolicies/schemas/policies-schema.json
browser/components/enterprisepolicies/schemas/schema.jsm
browser/components/enterprisepolicies/tests/browser/.eslintrc.js
browser/components/enterprisepolicies/tests/browser/browser.ini
browser/components/enterprisepolicies/tests/browser/browser_policies_broken_json.js
browser/components/enterprisepolicies/tests/browser/browser_policies_simple_policies.js
browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js
browser/components/enterprisepolicies/tests/browser/config_broken_json.json
browser/components/enterprisepolicies/tests/browser/config_simple_policies.json
browser/components/enterprisepolicies/tests/browser/head.js
browser/components/enterprisepolicies/tests/moz.build
browser/components/moz.build
browser/installer/package-manifest.in
toolkit/components/enterprisepolicies/moz.build
toolkit/components/enterprisepolicies/nsIEnterprisePolicies.idl
toolkit/components/moz.build
toolkit/modules/Services.jsm
toolkit/modules/tests/xpcshell/test_Services.js
toolkit/xre/nsXREDirProvider.cpp
--- a/.eslintignore
+++ b/.eslintignore
@@ -72,16 +72,20 @@ browser/branding/**/firefox-branding.js
 # Gzipped test file.
 browser/base/content/test/general/gZipOfflineChild.html
 browser/base/content/test/urlbar/file_blank_but_not_blank.html
 # New tab is likely to be replaced soon.
 browser/base/content/newtab/**
 # Test files that are really json not js, and don't need to be linted.
 browser/components/sessionstore/test/unit/data/sessionstore_valid.js
 browser/components/sessionstore/test/unit/data/sessionstore_invalid.js
+# This file is split into two in order to keep it as a valid json file
+# for documentation purposes (policies.json) but to be accessed by the
+# code as a .jsm (schema.jsm)
+browser/components/enterprisepolicies/schemas/schema.jsm
 # generated & special files in cld2
 browser/components/translation/cld2/**
 # Screenshots and Follow-on search are imported as a system add-on and have
 # their own lint rules currently.
 browser/extensions/followonsearch/**
 browser/extensions/screenshots/**
 browser/extensions/pdfjs/content/build**
 browser/extensions/pdfjs/content/web**
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/EnterprisePolicies.js
@@ -0,0 +1,358 @@
+/* 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/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  NetUtil: "resource://gre/modules/NetUtil.jsm",
+  Policies: "resource:///modules/policies/Policies.jsm",
+  PoliciesValidator: "resource:///modules/policies/PoliciesValidator.jsm",
+});
+
+// This is the file that will be searched for in the
+// ${InstallDir}/distribution folder.
+const POLICIES_FILENAME = "policies.json";
+
+// For easy testing, modify the helpers/sample.json file,
+// and set PREF_ALTERNATE_PATH in firefox.js as:
+// /your/repo/browser/components/enterprisepolicies/helpers/sample.json
+const PREF_ALTERNATE_PATH     = "browser.policies.alternatePath";
+
+// This pref is meant to be temporary: it will only be used while we're
+// testing this feature without rolling it out officially. When the
+// policy engine is released, this pref should be removed.
+const PREF_ENABLED            = "browser.policies.enabled";
+const PREF_LOGLEVEL           = "browser.policies.loglevel";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+  return new ConsoleAPI({
+    prefix: "Enterprise Policies",
+    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+    // messages during development. See LOG_LEVELS in Console.jsm for details.
+    maxLogLevel: "error",
+    maxLogLevelPref: PREF_LOGLEVEL,
+  });
+});
+
+// ==== Start XPCOM Boilerplate ==== \\
+
+// Factory object
+const EnterprisePoliciesFactory = {
+  _instance: null,
+  createInstance: function BGSF_createInstance(outer, iid) {
+    if (outer != null)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return this._instance == null ?
+      this._instance = new EnterprisePoliciesManager() : this._instance;
+  }
+};
+
+// ==== End XPCOM Boilerplate ==== //
+
+// Constructor
+function EnterprisePoliciesManager() {
+  Services.obs.addObserver(this, "profile-after-change", true);
+  Services.obs.addObserver(this, "final-ui-startup", true);
+  Services.obs.addObserver(this, "sessionstore-windows-restored", true);
+  Services.obs.addObserver(this, "EnterprisePolicies:Restart", true);
+}
+
+EnterprisePoliciesManager.prototype = {
+  // for XPCOM
+  classID:          Components.ID("{ea4e1414-779b-458b-9d1f-d18e8efbc145}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIEnterprisePolicies]),
+
+  // redefine the default factory for XPCOMUtils
+  _xpcom_factory: EnterprisePoliciesFactory,
+
+  _initialize() {
+    if (!Services.prefs.getBoolPref(PREF_ENABLED, false)) {
+      this.status = Ci.nsIEnterprisePolicies.INACTIVE;
+      return;
+    }
+
+    this._file = new JSONFileReader(getConfigurationFile());
+    this._file.readData();
+
+    if (!this._file.exists) {
+      this.status = Ci.nsIEnterprisePolicies.INACTIVE;
+      return;
+    }
+
+    if (this._file.failed) {
+      this.status = Ci.nsIEnterprisePolicies.FAILED;
+      return;
+    }
+
+    this.status = Ci.nsIEnterprisePolicies.ACTIVE;
+    this._activatePolicies();
+  },
+
+  _activatePolicies() {
+    let { schema } = Cu.import("resource:///modules/policies/schema.jsm", {});
+    let json = this._file.json;
+
+    for (let policyName of Object.keys(json.policies)) {
+      let policySchema = schema.properties[policyName];
+      let policyParameters = json.policies[policyName];
+
+      if (!policySchema) {
+        log.error(`Unknown policy: ${policyName}`);
+        continue;
+      }
+
+      let [parametersAreValid, parsedParameters] =
+        PoliciesValidator.validateAndParseParameters(policyParameters,
+                                                     policySchema);
+
+      if (!parametersAreValid) {
+        log.error(`Invalid parameters specified for ${policyName}.`);
+        continue;
+      }
+
+      let policyImpl = Policies[policyName];
+
+      for (let timing of Object.keys(this._callbacks)) {
+        let policyCallback = policyImpl["on" + timing];
+        if (policyCallback) {
+          this._schedulePolicyCallback(
+            timing,
+            policyCallback.bind(null,
+                                this, /* the EnterprisePoliciesManager */
+                                parsedParameters));
+        }
+      }
+    }
+  },
+
+  _callbacks: {
+    BeforeAddons: [],
+    ProfileAfterChange: [],
+    BeforeUIStartup: [],
+    AllWindowsRestored: [],
+  },
+
+  _schedulePolicyCallback(timing, callback) {
+    this._callbacks[timing].push(callback);
+  },
+
+  _runPoliciesCallbacks(timing) {
+    let callbacks = this._callbacks[timing];
+    while (callbacks.length > 0) {
+      let callback = callbacks.shift();
+      try {
+        callback();
+      } catch (ex) {
+        log.error("Error running ", callback, `for ${timing}:`, ex);
+      }
+    }
+  },
+
+  async _restart() {
+    if (!Cu.isInAutomation) {
+      return;
+    }
+
+    DisallowedFeatures = {};
+
+    this._status = Ci.nsIEnterprisePolicies.UNINITIALIZED;
+    for (let timing of Object.keys(this._callbacks)) {
+      this._callbacks[timing] = [];
+    }
+    delete Services.ppmm.initialProcessData.policies;
+    Services.ppmm.broadcastAsyncMessage("EnterprisePolicies:Restart", null);
+
+    let { PromiseUtils } = Cu.import("resource://gre/modules/PromiseUtils.jsm",
+                                     {});
+
+    // Simulate the startup process. This step-by-step is a bit ugly but it
+    // tries to emulate the same behavior as of a normal startup.
+
+    await PromiseUtils.idleDispatch(() => {
+      this.observe(null, "policies-startup", null);
+    });
+
+    await PromiseUtils.idleDispatch(() => {
+      this.observe(null, "profile-after-change", null);
+    });
+
+    await PromiseUtils.idleDispatch(() => {
+      this.observe(null, "final-ui-startup", null);
+    });
+
+    await PromiseUtils.idleDispatch(() => {
+      this.observe(null, "sessionstore-windows-restored", null);
+    });
+  },
+
+  // nsIObserver implementation
+  observe: function BG_observe(subject, topic, data) {
+    switch (topic) {
+      case "policies-startup":
+        this._initialize();
+        this._runPoliciesCallbacks("BeforeAddons");
+        break;
+
+      case "profile-after-change":
+        // Before the first set of policy callbacks runs, we must
+        // initialize the service.
+        this._runPoliciesCallbacks("ProfileAfterChange");
+        break;
+
+      case "final-ui-startup":
+        this._runPoliciesCallbacks("BeforeUIStartup");
+        break;
+
+      case "sessionstore-windows-restored":
+        this._runPoliciesCallbacks("AllWindowsRestored");
+
+        // After the last set of policy callbacks ran, notify the test observer.
+        Services.obs.notifyObservers(null,
+                                     "EnterprisePolicies:AllPoliciesApplied");
+        break;
+
+      case "EnterprisePolicies:Restart":
+        this._restart().then(null, Cu.reportError);
+        break;
+    }
+  },
+
+  disallowFeature(feature, neededOnContentProcess = false) {
+    DisallowedFeatures[feature] = true;
+
+    // NOTE: For optimization purposes, only features marked as needed
+    // on content process will be passed onto the child processes.
+    if (neededOnContentProcess) {
+      Services.ppmm.initialProcessData.policies
+                                      .disallowedFeatures.push(feature);
+
+      if (Services.ppmm.childCount > 1) {
+        // If there has been a content process already initialized, let's
+        // broadcast the newly disallowed feature.
+        Services.ppmm.broadcastAsyncMessage(
+          "EnterprisePolicies:DisallowFeature", {feature}
+        );
+      }
+    }
+  },
+
+  // ------------------------------
+  // public nsIEnterprisePolicies members
+  // ------------------------------
+
+  _status: Ci.nsIEnterprisePolicies.UNINITIALIZED,
+
+  set status(val) {
+    this._status = val;
+    if (val != Ci.nsIEnterprisePolicies.INACTIVE) {
+      Services.ppmm.initialProcessData.policies = {
+        status: val,
+        disallowedFeatures: [],
+      };
+    }
+    return val;
+  },
+
+  get status() {
+    return this._status;
+  },
+
+  isAllowed: function BG_sanitize(feature) {
+    return !(feature in DisallowedFeatures);
+  },
+};
+
+let DisallowedFeatures = {};
+
+function JSONFileReader(file) {
+  this._file = file;
+  this._data = {
+    exists: null,
+    failed: false,
+    json: null,
+  };
+}
+
+JSONFileReader.prototype = {
+  get exists() {
+    if (this._data.exists === null) {
+      this.readData();
+    }
+
+    return this._data.exists;
+  },
+
+  get failed() {
+    return this._data.failed;
+  },
+
+  get json() {
+    if (this._data.failed) {
+      return null;
+    }
+
+    if (this._data.json === null) {
+      this.readData();
+    }
+
+    return this._data.json;
+  },
+
+  readData() {
+    try {
+      let data = Cu.readUTF8File(this._file);
+      if (data) {
+        this._data.exists = true;
+        this._data.json = JSON.parse(data);
+      } else {
+        this._data.exists = false;
+      }
+    } catch (ex) {
+      if (ex instanceof Components.Exception &&
+          ex.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
+        this._data.exists = false;
+      } else if (ex instanceof SyntaxError) {
+        log.error("Error parsing JSON file");
+        this._data.failed = true;
+      } else {
+        log.error("Error reading file");
+        this._data.failed = true;
+      }
+    }
+  }
+};
+
+function getConfigurationFile() {
+  let configFile = Services.dirsvc.get("XREAppDist", Ci.nsIFile);
+  configFile.append(POLICIES_FILENAME);
+
+  let prefType = Services.prefs.getPrefType(PREF_ALTERNATE_PATH);
+
+  if ((prefType == Services.prefs.PREF_STRING) && !configFile.exists()) {
+    // We only want to use the alternate file path if the file on the install
+    // folder doesn't exist. Otherwise it'd be possible for a user to override
+    // the admin-provided policies by changing the user-controlled prefs.
+    // This pref is only meant for tests, so it's fine to use this extra
+    // synchronous configFile.exists() above.
+    configFile = Cc["@mozilla.org/file/local;1"]
+                   .createInstance(Ci.nsIFile);
+    let alternatePath = Services.prefs.getStringPref(PREF_ALTERNATE_PATH);
+    configFile.initWithPath(alternatePath);
+  }
+
+  return configFile;
+}
+
+var components = [EnterprisePoliciesManager];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/EnterprisePolicies.manifest
@@ -0,0 +1,5 @@
+component {ea4e1414-779b-458b-9d1f-d18e8efbc145} EnterprisePolicies.js process=main
+contract @mozilla.org/browser/enterprisepolicies;1 {ea4e1414-779b-458b-9d1f-d18e8efbc145} process=main
+
+component {dc6358f8-d167-4566-bf5b-4350b5e6a7a2} EnterprisePoliciesContent.js process=content
+contract @mozilla.org/browser/enterprisepolicies;1 {dc6358f8-d167-4566-bf5b-4350b5e6a7a2} process=content
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/EnterprisePoliciesContent.js
@@ -0,0 +1,91 @@
+/* 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/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_LOGLEVEL           = "browser.policies.loglevel";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+  return new ConsoleAPI({
+    prefix: "Enterprise Policies Child",
+    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+    // messages during development. See LOG_LEVELS in Console.jsm for details.
+    maxLogLevel: "error",
+    maxLogLevelPref: PREF_LOGLEVEL,
+  });
+});
+
+
+// ==== Start XPCOM Boilerplate ==== \\
+
+// Factory object
+const EnterprisePoliciesFactory = {
+  _instance: null,
+  createInstance: function BGSF_createInstance(outer, iid) {
+    if (outer != null)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return this._instance == null ?
+      this._instance = new EnterprisePoliciesManagerContent() : this._instance;
+  }
+};
+
+// ==== End XPCOM Boilerplate ==== //
+
+
+function EnterprisePoliciesManagerContent() {
+  let policies = Services.cpmm.initialProcessData.policies;
+  if (policies) {
+    this._status = policies.status;
+    // make a copy of the array so that we can keep adding to it
+    // in a way that is not confusing.
+    this._disallowedFeatures = policies.disallowedFeatures.slice();
+  }
+
+  Services.cpmm.addMessageListener("EnterprisePolicies:DisallowFeature", this);
+  Services.cpmm.addMessageListener("EnterprisePolicies:Restart", this);
+}
+
+EnterprisePoliciesManagerContent.prototype = {
+  // for XPCOM
+  classID:          Components.ID("{dc6358f8-d167-4566-bf5b-4350b5e6a7a2}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
+                                         Ci.nsIEnterprisePolicies]),
+
+  // redefine the default factory for XPCOMUtils
+  _xpcom_factory: EnterprisePoliciesFactory,
+
+  _status: Ci.nsIEnterprisePolicies.INACTIVE,
+
+  _disallowedFeatures: [],
+
+  receiveMessage({name, data}) {
+    switch (name) {
+      case "EnterprisePolicies:DisallowFeature":
+        this._disallowedFeatures.push(data.feature);
+        break;
+
+      case "EnterprisePolicies:Restart":
+        this._disallowedFeatures = [];
+        break;
+    }
+  },
+
+  get status() {
+    return this._status;
+  },
+
+  isAllowed(feature) {
+    return !this._disallowedFeatures.includes(feature);
+  }
+};
+
+var components = [EnterprisePoliciesManagerContent];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -0,0 +1,38 @@
+/* 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/. */
+
+"use strict";
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_LOGLEVEL           = "browser.policies.loglevel";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+  return new ConsoleAPI({
+    prefix: "Policies.jsm",
+    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+    // messages during development. See LOG_LEVELS in Console.jsm for details.
+    maxLogLevel: "error",
+    maxLogLevelPref: PREF_LOGLEVEL,
+  });
+});
+
+this.EXPORTED_SYMBOLS = ["Policies"];
+
+this.Policies = {
+  "block_about_config": {
+    onBeforeUIStartup(manager, param) {
+      if (param == true) {
+        manager.disallowFeature("about:config", true);
+      }
+    }
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/PoliciesValidator.jsm
@@ -0,0 +1,148 @@
+/* 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/. */
+
+"use strict";
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_LOGLEVEL           = "browser.policies.loglevel";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+  return new ConsoleAPI({
+    prefix: "PoliciesValidator.jsm",
+    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+    // messages during development. See LOG_LEVELS in Console.jsm for details.
+    maxLogLevel: "error",
+    maxLogLevelPref: PREF_LOGLEVEL,
+  });
+});
+
+this.EXPORTED_SYMBOLS = ["PoliciesValidator"];
+
+this.PoliciesValidator = {
+  validateAndParseParameters(param, properties) {
+    return validateAndParseParamRecursive(param, properties);
+  }
+};
+
+function validateAndParseParamRecursive(param, properties) {
+  if (properties.enum) {
+    if (properties.enum.includes(param)) {
+      return [true, param];
+    }
+    return [false, null];
+  }
+
+  log.debug(`checking @${param}@ for type ${properties.type}`);
+  switch (properties.type) {
+    case "boolean":
+    case "number":
+    case "integer":
+    case "string":
+    case "URL":
+    case "origin":
+      return validateAndParseSimpleParam(param, properties.type);
+
+    case "array":
+      if (!Array.isArray(param)) {
+        log.error("Array expected but not received");
+        return [false, null];
+      }
+
+      let parsedArray = [];
+      for (let item of param) {
+        log.debug(`in array, checking @${item}@ for type ${properties.items.type}`);
+        let [valid, parsedValue] = validateAndParseParamRecursive(item, properties.items);
+        if (!valid) {
+          return [false, null];
+        }
+
+        parsedArray.push(parsedValue);
+      }
+
+      return [true, parsedArray];
+
+    case "object": {
+      if (typeof(param) != "object") {
+        log.error("Object expected but not received");
+        return [false, null];
+      }
+
+      let parsedObj = {};
+      for (let property of Object.keys(properties.properties)) {
+        log.debug(`in object, for property ${property} checking @${param[property]}@ for type ${properties.properties[property].type}`);
+        let [valid, parsedValue] = validateAndParseParamRecursive(param[property], properties.properties[property]);
+        if (!valid) {
+          return [false, null];
+        }
+
+        parsedObj[property] = parsedValue;
+      }
+
+      return [true, parsedObj];
+    }
+  }
+
+  return [false, null];
+}
+
+function validateAndParseSimpleParam(param, type) {
+  let valid = false;
+  let parsedParam = param;
+
+  switch (type) {
+    case "boolean":
+    case "number":
+    case "string":
+      valid = (typeof(param) == type);
+      break;
+
+    // integer is an alias to "number" that some JSON schema tools use
+    case "integer":
+      valid = (typeof(param) == "number");
+      break;
+
+    case "origin":
+      if (typeof(param) != "string") {
+        break;
+      }
+
+      try {
+        parsedParam = Services.io.newURI(param);
+
+        let pathQueryRef = parsedParam.pathQueryRef;
+        // Make sure that "origin" types won't accept full URLs.
+        if (pathQueryRef != "/" && pathQueryRef != "") {
+          valid = false;
+        } else {
+          valid = true;
+        }
+      } catch (ex) {
+        valid = false;
+      }
+      break;
+
+    case "URL":
+      if (typeof(param) != "string") {
+        break;
+      }
+
+      try {
+        parsedParam = Services.io.newURI(param);
+        valid = true;
+      } catch (ex) {
+        valid = false;
+      }
+      break;
+  }
+
+  return [valid, parsedParam];
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/helpers/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Firefox", "Enterprise Policies")
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/helpers/sample.json
@@ -0,0 +1,18 @@
+{
+  "policies": {
+    "block_about_config": true,
+    "dont_check_default_browser": true,
+
+    "flash_plugin": {
+      "allow": [
+        "https://www.example.com"
+      ],
+
+      "block": [
+        "https://www.example.org"
+      ]
+    },
+
+    "block_about_profiles": true
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Firefox", "Enterprise Policies")
+
+DIRS += [
+    'helpers',
+    'schemas',
+]
+
+TEST_DIRS += [
+	'tests'
+]
+
+EXTRA_COMPONENTS += [
+    'EnterprisePolicies.js',
+    'EnterprisePolicies.manifest',
+    'EnterprisePoliciesContent.js',
+]
+
+EXTRA_JS_MODULES.policies += [
+    'Policies.jsm',
+    'PoliciesValidator.jsm',
+]
+
+FINAL_LIBRARY = 'browsercomps'
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/schemas/configuration.json
@@ -0,0 +1,10 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "type": "object",
+  "properties": {
+    "policies": {
+      "$ref": "policies.json"
+    }
+  },
+  "required": ["policies"]
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/schemas/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Firefox", "Enterprise Policies")
+
+EXTRA_PP_JS_MODULES.policies += [
+    'schema.jsm',
+]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -0,0 +1,13 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "type": "object",
+  "properties": {
+     "block_about_config": {
+      "description": "Blocks access to the about:config page.",
+      "first_available": "60.0",
+
+      "type": "boolean",
+      "enum": [true]
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/schemas/schema.jsm
@@ -0,0 +1,10 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["schema"];
+
+this.schema =
+#include policies-schema.json
copy from browser/components/newtab/tests/browser/.eslintrc.js
copy to browser/components/enterprisepolicies/tests/browser/.eslintrc.js
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+prefs =
+  browser.policies.enabled=true
+support-files =
+  head.js
+  config_simple_policies.json
+  config_broken_json.json
+
+[browser_policies_broken_json.js]
+[browser_policies_simple_policies.js]
+[browser_policies_validate_and_parse_API.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_broken_json.js
@@ -0,0 +1,15 @@
+/* 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/. */
+
+"use strict";
+
+add_task(async function test_clean_slate() {
+  await startWithCleanSlate();
+});
+
+add_task(async function test_broken_json() {
+  await setupPolicyEngineWithJson("config_broken_json.json");
+
+  is(Services.policies.status, Ci.nsIEnterprisePolicies.FAILED, "Engine was correctly set to the error state");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_policies.js
@@ -0,0 +1,101 @@
+/* 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/. */
+
+"use strict";
+
+add_task(async function test_clean_slate() {
+  await startWithCleanSlate();
+});
+
+add_task(async function test_simple_policies() {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    // Initialize the service in the content process, in case it hasn't
+    // already started.
+    Services.policies;
+  });
+
+  let { Policies } = Cu.import("resource:///modules/policies/Policies.jsm", {});
+
+  let policy0Ran = false, policy1Ran = false, policy2Ran = false, policy3Ran = false;
+
+  // Implement functions to handle the four simple policies that will be added
+  // to the schema.
+  Policies.simple_policy0 = {
+    onProfileAfterChange(manager, param) {
+      is(param, true, "Param matches what was passed in config file");
+      policy0Ran = true;
+    }
+  };
+
+  Policies.simple_policy1 = {
+    onProfileAfterChange(manager, param) {
+      is(param, true, "Param matches what was passed in config file");
+      manager.disallowFeature("feature1", /* needed in content process */ true);
+      policy1Ran = true;
+    }
+  };
+
+  Policies.simple_policy2 = {
+    onBeforeUIStartup(manager, param) {
+      is(param, true, "Param matches what was passed in config file");
+      manager.disallowFeature("feature2", /* needed in content process */ false);
+      policy2Ran = true;
+    }
+  };
+
+  Policies.simple_policy3 = {
+    onAllWindowsRestored(manager, param) {
+      is(param, false, "Param matches what was passed in config file");
+      policy3Ran = true;
+    }
+  };
+
+  await setupPolicyEngineWithJson(
+    "config_simple_policies.json",
+    /* custom schema */
+    {
+      properties: {
+        "simple_policy0": {
+          "type": "boolean"
+        },
+
+        "simple_policy1": {
+          "type": "boolean"
+        },
+
+        "simple_policy2": {
+          "type": "boolean"
+        },
+
+        "simple_policy3": {
+          "type": "boolean"
+        }
+
+      }
+    }
+  );
+
+  is(Services.policies.status, Ci.nsIEnterprisePolicies.ACTIVE, "Engine is active");
+  is(Services.policies.isAllowed("feature1"), false, "Dummy feature was disallowed");
+  is(Services.policies.isAllowed("feature2"), false, "Dummy feature was disallowed");
+
+  ok(policy0Ran, "Policy 0 ran correctly through BeforeAddons");
+  ok(policy1Ran, "Policy 1 ran correctly through onProfileAfterChange");
+  ok(policy2Ran, "Policy 2 ran correctly through onBeforeUIStartup");
+  ok(policy3Ran, "Policy 3 ran correctly through onAllWindowsRestored");
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+      is(Services.policies.isAllowed("feature1"), false, "Correctly disallowed in the content process");
+      // Feature 2 wasn't explictly marked as needed in the content process, so it is not marked
+      // as disallowed there.
+      is(Services.policies.isAllowed("feature2"), true, "Correctly missing in the content process");
+    }
+  });
+
+  delete Policies.simple_policy0;
+  delete Policies.simple_policy1;
+  delete Policies.simple_policy2;
+  delete Policies.simple_policy3;
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js
@@ -0,0 +1,231 @@
+/* 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/. */
+
+"use strict";
+
+/* This file will test the parameters parsing and validation directly through
+   the PoliciesValidator API.
+ */
+
+const { PoliciesValidator } = Cu.import("resource:///modules/policies/PoliciesValidator.jsm", {});
+
+add_task(async function test_boolean_values() {
+  let schema = {
+    type: "boolean"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(true, schema);
+  ok(valid && parsed === true, "Parsed boolean value correctly");
+
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(false, schema);
+  ok(valid && parsed === false, "Parsed boolean value correctly");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters("0", schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters("true", schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+});
+
+add_task(async function test_number_values() {
+  let schema = {
+    type: "number"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(1, schema);
+  ok(valid && parsed === 1, "Parsed number value correctly");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+});
+
+add_task(async function test_integer_values() {
+  // Integer is an alias for number
+  let schema = {
+    type: "integer"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(1, schema);
+  ok(valid && parsed == 1, "Parsed integer value correctly");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+});
+
+add_task(async function test_string_values() {
+  let schema = {
+    type: "string"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters("foobar", schema);
+  ok(valid && parsed == "foobar", "Parsed string value correctly");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters(1, schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "No type coercion");
+  ok(!PoliciesValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+});
+
+add_task(async function test_URL_values() {
+  let schema = {
+    type: "URL"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters("https://www.example.com/foo#bar", schema);
+  ok(valid, "URL is valid");
+  ok(parsed instanceof Ci.nsIURI, "parsed is a nsIURI");
+  is(parsed.prePath, "https://www.example.com", "prePath is correct");
+  is(parsed.pathQueryRef, "/foo#bar", "pathQueryRef is correct");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters("www.example.com", schema)[0], "Scheme is required for URL");
+  ok(!PoliciesValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid URL");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+});
+
+add_task(async function test_origin_values() {
+  // Origin is a URL that doesn't contain a path/query string (i.e., it's only scheme + host + port)
+  let schema = {
+    type: "origin"
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters("https://www.example.com", schema);
+  ok(valid, "Origin is valid");
+  ok(parsed instanceof Ci.nsIURI, "parsed is a nsIURI");
+  is(parsed.prePath, "https://www.example.com", "prePath is correct");
+  is(parsed.pathQueryRef, "/", "pathQueryRef is corect");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters("https://www.example.com/foobar", schema)[0], "Origin cannot contain a path part");
+  ok(!PoliciesValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid origin");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+});
+
+add_task(async function test_array_values() {
+  // The types inside an array object must all be the same
+  let schema = {
+    type: "array",
+    items: {
+      type: "number"
+    }
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters([1, 2, 3], schema);
+  ok(valid, "Array is valid");
+  ok(Array.isArray(parsed), "parsed is an array");
+  is(parsed.length, 3, "array is correct");
+
+  // An empty array is also valid
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters([], schema);
+  ok(valid, "Array is valid");
+  ok(Array.isArray(parsed), "parsed is an array");
+  is(parsed.length, 0, "array is correct");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters([1, true, 3], schema)[0], "Mixed types");
+  ok(!PoliciesValidator.validateAndParseParameters(2, schema)[0], "Type is correct but not in an array");
+  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Object is not an array");
+});
+
+add_task(async function test_object_values() {
+  let schema = {
+    type: "object",
+    properties: {
+      url: {
+        type: "URL"
+      },
+      title: {
+        type: "string"
+      }
+    }
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(
+    {
+      url: "https://www.example.com/foo#bar",
+      title: "Foo",
+      alias: "Bar"
+    },
+    schema);
+
+  ok(valid, "Object is valid");
+  ok(typeof(parsed) == "object", "parsed in an object");
+  ok(parsed.url instanceof Ci.nsIURI, "types inside the object are also parsed");
+  is(parsed.url.spec, "https://www.example.com/foo#bar", "URL was correctly parsed");
+  is(parsed.title, "Foo", "title was correctly parsed");
+  is(parsed.alias, undefined, "property not described in the schema is not present in the parsed object");
+
+  // Invalid values:
+  ok(!PoliciesValidator.validateAndParseParameters(
+    {
+      url: "https://www.example.com/foo#bar",
+      title: 3,
+    },
+    schema)[0], "Mismatched type for title");
+
+  ok(!PoliciesValidator.validateAndParseParameters(
+    {
+      url: "www.example.com",
+      title: 3,
+    },
+    schema)[0], "Invalid URL inside the object");
+});
+
+add_task(async function test_array_of_objects() {
+  // This schema is used, for example, for bookmarks
+  let schema = {
+    type: "array",
+    items: {
+      type: "object",
+      properties: {
+        url: {
+          type: "URL",
+        },
+        title: {
+          type: "string"
+        }
+      }
+    }
+  };
+
+  let valid, parsed;
+  [valid, parsed] = PoliciesValidator.validateAndParseParameters(
+    [{
+      url: "https://www.example.com/bookmark1",
+      title: "Foo",
+    },
+    {
+      url: "https://www.example.com/bookmark2",
+      title: "Bar",
+    }],
+    schema);
+
+  ok(valid, "Array is valid");
+  is(parsed.length, 2, "Correct number of items");
+
+  ok(typeof(parsed[0]) == "object" && typeof(parsed[1]) == "object", "Correct objects inside array");
+
+  is(parsed[0].url.spec, "https://www.example.com/bookmark1", "Correct URL for bookmark 1");
+  is(parsed[1].url.spec, "https://www.example.com/bookmark2", "Correct URL for bookmark 2");
+
+  is(parsed[0].title, "Foo", "Correct title for bookmark 1");
+  is(parsed[1].title, "Bar", "Correct title for bookmark 2");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/config_broken_json.json
@@ -0,0 +1,3 @@
+{
+  "policies
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/config_simple_policies.json
@@ -0,0 +1,8 @@
+{
+  "policies": {
+    "simple_policy0": true,
+    "simple_policy1": true,
+    "simple_policy2": true,
+    "simple_policy3": false
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/head.js
@@ -0,0 +1,34 @@
+/* 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/. */
+
+"use strict";
+
+async function setupPolicyEngineWithJson(jsonName, customSchema) {
+  let filePath = getTestFilePath(jsonName ? jsonName : "non-existing-file.json");
+  Services.prefs.setStringPref("browser.policies.alternatePath", filePath);
+
+  let resolve = null;
+  let promise = new Promise((r) => resolve = r);
+
+  Services.obs.addObserver(function observer() {
+    Services.obs.removeObserver(observer, "EnterprisePolicies:AllPoliciesApplied");
+    resolve();
+  }, "EnterprisePolicies:AllPoliciesApplied");
+
+  // Clear any previously used custom schema
+  Cu.unload("resource:///modules/policies/schema.jsm");
+
+  if (customSchema) {
+    let schemaModule = Cu.import("resource:///modules/policies/schema.jsm", {});
+    schemaModule.schema = customSchema;
+  }
+
+  Services.obs.notifyObservers(null, "EnterprisePolicies:Restart");
+  return promise;
+}
+
+async function startWithCleanSlate() {
+  await setupPolicyEngineWithJson("");
+  is(Services.policies.status, Ci.nsIEnterprisePolicies.INACTIVE, "Engine is inactive");
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Firefox", "General")
+
+BROWSER_CHROME_MANIFESTS += [
+    'browser/browser.ini'
+]
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -33,16 +33,17 @@ with Files('controlcenter/**'):
 
 
 DIRS += [
     'about',
     'contextualidentity',
     'customizableui',
     'dirprovider',
     'downloads',
+    'enterprisepolicies',
     'extensions',
     'feeds',
     'migration',
     'newtab',
     'originattributes',
     'places',
     'preferences',
     'privatebrowsing',
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -224,16 +224,17 @@
 @RESPATH@/components/dom_workers.xpt
 @RESPATH@/components/dom_xbl.xpt
 @RESPATH@/components/dom_xhr.xpt
 @RESPATH@/components/dom_xpath.xpt
 @RESPATH@/components/dom_xul.xpt
 @RESPATH@/components/dom_presentation.xpt
 @RESPATH@/components/downloads.xpt
 @RESPATH@/components/editor.xpt
+@RESPATH@/components/enterprisepolicies.xpt
 @RESPATH@/components/extensions.xpt
 @RESPATH@/components/exthandler.xpt
 @RESPATH@/components/fastfind.xpt
 @RESPATH@/components/feeds.xpt
 @RESPATH@/components/gfx.xpt
 @RESPATH@/components/html5.xpt
 @RESPATH@/components/htmlparser.xpt
 @RESPATH@/components/imglib2.xpt
@@ -374,16 +375,19 @@
 @RESPATH@/browser/components/aboutdevtools.manifest
 @RESPATH@/browser/components/aboutdevtoolstoolbox-registration.js
 @RESPATH@/browser/components/aboutdevtoolstoolbox.manifest
 @RESPATH@/browser/components/Experiments.manifest
 @RESPATH@/browser/components/ExperimentsService.js
 @RESPATH@/browser/components/browser-newtab.xpt
 @RESPATH@/browser/components/aboutNewTabService.js
 @RESPATH@/browser/components/NewTabComponents.manifest
+@RESPATH@/browser/components/EnterprisePolicies.js
+@RESPATH@/browser/components/EnterprisePoliciesContent.js
+@RESPATH@/browser/components/EnterprisePolicies.manifest
 @RESPATH@/components/Downloads.manifest
 @RESPATH@/components/DownloadLegacy.js
 @RESPATH@/components/thumbnails.xpt
 @RESPATH@/components/PageThumbsComponents.manifest
 @RESPATH@/components/crashmonitor.manifest
 @RESPATH@/components/nsCrashMonitor.js
 @RESPATH@/components/toolkitsearch.manifest
 @RESPATH@/components/nsSearchService.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/enterprisepolicies/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Firefox", "Enterprise Policies")
+
+XPIDL_SOURCES += [
+    'nsIEnterprisePolicies.idl',
+]
+
+XPIDL_MODULE = 'enterprisepolicies'
new file mode 100644
--- /dev/null
+++ b/toolkit/components/enterprisepolicies/nsIEnterprisePolicies.idl
@@ -0,0 +1,18 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(6a568972-cc91-4bf5-963e-3768f3319b8a)]
+interface nsIEnterprisePolicies : nsISupports
+{
+  const unsigned short UNINITIALIZED = 0;
+  const unsigned short INACTIVE      = 1;
+  const unsigned short ACTIVE        = 2;
+  const unsigned short FAILED        = 3;
+
+  readonly attribute short status;
+
+  bool isAllowed(in ACString feature);
+};
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -25,16 +25,17 @@ DIRS += [
     'cloudstorage',
     'commandlines',
     'contentprefs',
     'contextualidentity',
     'crashes',
     'crashmonitor',
     'diskspacewatcher',
     'downloads',
+    'enterprisepolicies',
     'extensions',
     'filewatcher',
     'finalizationwitness',
     'find',
     'jsdownloads',
     'jsoncpp/src/lib_json',
     'lz4',
     'mediasniffer',
--- a/toolkit/modules/Services.jsm
+++ b/toolkit/modules/Services.jsm
@@ -117,12 +117,15 @@ if (AppConstants.platform == "android") 
   initTable.androidBridge = ["@mozilla.org/android/bridge;1", "nsIAndroidBridge"];
 }
 if (AppConstants.MOZ_GECKO_PROFILER) {
   initTable.profiler = ["@mozilla.org/tools/profiler;1", "nsIProfiler"];
 }
 if (AppConstants.MOZ_TOOLKIT_SEARCH) {
   initTable.search = ["@mozilla.org/browser/search-service;1", "nsIBrowserSearchService"];
 }
+if (AppConstants.MOZ_BUILD_APP == "browser") {
+  initTable.policies = ["@mozilla.org/browser/enterprisepolicies;1", "nsIEnterprisePolicies"];
+}
 
 XPCOMUtils.defineLazyServiceGetters(Services, initTable);
 
 initTable = undefined;
--- a/toolkit/modules/tests/xpcshell/test_Services.js
+++ b/toolkit/modules/tests/xpcshell/test_Services.js
@@ -65,16 +65,20 @@ function run_test() {
   checkService("wm", Ci.nsIWindowMediator);
   checkService("ww", Ci.nsIWindowWatcher);
   if ("nsIBrowserSearchService" in Ci) {
     checkService("search", Ci.nsIBrowserSearchService);
   }
   if ("nsIAndroidBridge" in Ci) {
     checkService("androidBridge", Ci.nsIAndroidBridge);
   }
+  if ("@mozilla.org/browser/enterprisepolicies;1" in Cc) {
+    checkService("policies", Ci.nsIEnterprisePolicies);
+  }
+
 
   // In xpcshell tests, the "@mozilla.org/xre/app-info;1" component implements
   // only the nsIXULRuntime interface, but not nsIXULAppInfo.  To test the
   // service getter for the latter interface, load mock app-info.
   let tmp = {};
   Cu.import("resource://testing-common/AppInfo.jsm", tmp);
   tmp.updateAppInfo();
 
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -1004,16 +1004,22 @@ nsXREDirProvider::DoStartup()
         appStartup->RestartInSafeMode(nsIAppStartup::eForceQuit);
         return NS_OK;
       }
     }
 
     static const char16_t kStartup[] = {'s','t','a','r','t','u','p','\0'};
     obsSvc->NotifyObservers(nullptr, "profile-do-change", kStartup);
 
+    // Initialize the Enterprise Policies service
+    nsCOMPtr<nsIObserver> policies(do_GetService("@mozilla.org/browser/enterprisepolicies;1"));
+    if (policies) {
+      policies->Observe(nullptr, "policies-startup", nullptr);
+    }
+
     // Init the Extension Manager
     nsCOMPtr<nsIObserver> em = do_GetService("@mozilla.org/addons/integration;1");
     if (em) {
       em->Observe(nullptr, "addons-startup", nullptr);
     } else {
       NS_WARNING("Failed to create Addons Manager.");
     }