Bug 1322235: Part 6 - Replace AddonPolicyService with a stub implementation in ExtensionPolicyService. r?mixedpuppy,zombie draft
authorKris Maglione <maglione.k@gmail.com>
Tue, 23 May 2017 22:11:40 -0700
changeset 584189 56249f0a50073a8110d77ec8ba2348435583e6e0
parent 584188 4d660303f98a642d3af3759e5b952989161053a3
child 584190 44feff566cb0d5667cfba52d152032cee9fe9992
push id60645
push usermaglione.k@gmail.com
push dateThu, 25 May 2017 00:12:26 +0000
reviewersmixedpuppy, zombie
bugs1322235
milestone55.0a1
Bug 1322235: Part 6 - Replace AddonPolicyService with a stub implementation in ExtensionPolicyService. r?mixedpuppy,zombie This replaces the JS policy service stubs with a pure C++ version which directly makes policy decisions based on active WebExtensionPolicy objects. This is the first step in a larger refactoring, which will remove the ExtensionManagement module entirely, and replace the current add-on policy service with direct, non-virtual access to native WebExtensionPolicy objects. It will also be followed by related changes to migrate the content script and extension page matching to native code, based on the existing MatchPattern and WebExtensionPolicy bindings. MozReview-Commit-ID: 2MpbmXZGiPZ
toolkit/components/build/nsToolkitCompsCID.h
toolkit/components/build/nsToolkitCompsModule.cpp
toolkit/components/extensions/.eslintrc.js
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionChild.jsm
toolkit/components/extensions/ExtensionManagement.jsm
toolkit/components/extensions/ExtensionPolicyService.cpp
toolkit/components/extensions/ExtensionPolicyService.h
toolkit/components/extensions/extension-process-script.js
toolkit/components/extensions/test/xpcshell/test_csp_custom_policies.js
toolkit/components/extensions/test/xpcshell/test_locale_converter.js
toolkit/components/utils/simpleServices.js
toolkit/components/utils/utils.manifest
--- a/toolkit/components/build/nsToolkitCompsCID.h
+++ b/toolkit/components/build/nsToolkitCompsCID.h
@@ -181,8 +181,14 @@
 #define NS_APPLICATION_REPUTATION_SERVICE_CID \
 { 0x8576c950, 0xf4a2, 0x11e2, { 0xb7, 0x78, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
 
 #define NS_ADDONCONTENTPOLICY_CID \
 { 0xc26a8241, 0xecf4, 0x4aed, { 0x9f, 0x3c, 0xf1, 0xf5, 0xc7, 0x13, 0xb9, 0xa5 } }
 
 #define NS_ADDON_PATH_SERVICE_CID \
 { 0xa39f39d0, 0xdfb6, 0x11e3, { 0x8b, 0x68, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
+
+#define NS_ADDON_POLICY_SERVICE_CID \
+{ 0x562de129, 0x8338, 0x482c, { 0xbb, 0x96, 0xa1, 0xff, 0x09, 0xee, 0x53, 0xcc } }
+
+#define NS_ADDON_POLICY_SERVICE_CONTRACTID \
+  "@mozilla.org/addons/policy-service;1"
--- a/toolkit/components/build/nsToolkitCompsModule.cpp
+++ b/toolkit/components/build/nsToolkitCompsModule.cpp
@@ -32,16 +32,17 @@
 #include "nsUrlClassifierPrefixSet.h"
 
 #include "nsBrowserStatusFilter.h"
 #include "mozilla/FinalizationWitnessService.h"
 #include "mozilla/NativeOSFileInternals.h"
 #include "mozilla/AddonContentPolicy.h"
 #include "mozilla/AddonManagerStartup.h"
 #include "mozilla/AddonPathService.h"
+#include "mozilla/ExtensionPolicyService.h"
 
 #if defined(XP_WIN)
 #include "NativeFileWatcherWin.h"
 #else
 #include "NativeFileWatcherNotSupported.h"
 #endif // (XP_WIN)
 
 #include "nsWebRequestListener.h"
@@ -121,16 +122,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateP
 #endif
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(FinalizationWitnessService, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(NativeOSFileInternalsService)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NativeFileWatcherService, Init)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(AddonContentPolicy)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonPathService, AddonPathService::GetInstance)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonManagerStartup, AddonManagerStartup::GetInstance)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ExtensionPolicyService, ExtensionPolicyService::GetInstance)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebRequestListener)
 
 NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
 #if defined(MOZ_HAS_PERFSTATS)
 NS_DEFINE_NAMED_CID(NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID);
 #endif // defined (MOZ_HAS_PERFSTATS)
 
@@ -156,16 +158,17 @@ NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILT
 #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
 NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
 #endif
 NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NATIVE_OSFILE_INTERNALS_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_ADDONCONTENTPOLICY_CID);
 NS_DEFINE_NAMED_CID(NS_ADDON_PATH_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_ADDON_MANAGER_STARTUP_CID);
+NS_DEFINE_NAMED_CID(NS_ADDON_POLICY_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NATIVE_FILEWATCHER_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_WEBREQUESTLISTENER_CID);
 
 static const Module::CIDEntry kToolkitCIDs[] = {
   { &kNS_TOOLKIT_APPSTARTUP_CID, false, nullptr, nsAppStartupConstructor },
 #if defined(MOZ_HAS_TERMINATOR)
   { &kNS_TOOLKIT_TERMINATOR_CID, false, nullptr, nsTerminatorConstructor },
 #endif
@@ -191,16 +194,17 @@ static const Module::CIDEntry kToolkitCI
 #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
   { &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor },
 #endif
   { &kFINALIZATIONWITNESSSERVICE_CID, false, nullptr, FinalizationWitnessServiceConstructor },
   { &kNATIVE_OSFILE_INTERNALS_SERVICE_CID, false, nullptr, NativeOSFileInternalsServiceConstructor },
   { &kNS_ADDONCONTENTPOLICY_CID, false, nullptr, AddonContentPolicyConstructor },
   { &kNS_ADDON_PATH_SERVICE_CID, false, nullptr, AddonPathServiceConstructor },
   { &kNS_ADDON_MANAGER_STARTUP_CID, false, nullptr, AddonManagerStartupConstructor },
+  { &kNS_ADDON_POLICY_SERVICE_CID, false, nullptr, ExtensionPolicyServiceConstructor },
   { &kNATIVE_FILEWATCHER_SERVICE_CID, false, nullptr, NativeFileWatcherServiceConstructor },
   { &kNS_WEBREQUESTLISTENER_CID, false, nullptr, nsWebRequestListenerConstructor },
   { nullptr }
 };
 
 static const Module::ContractIDEntry kToolkitContracts[] = {
   { NS_APPSTARTUP_CONTRACTID, &kNS_TOOLKIT_APPSTARTUP_CID },
 #if defined(MOZ_HAS_TERMINATOR)
@@ -229,16 +233,17 @@ static const Module::ContractIDEntry kTo
 #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
   { NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
 #endif
   { FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
   { NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID, &kNATIVE_OSFILE_INTERNALS_SERVICE_CID },
   { NS_ADDONCONTENTPOLICY_CONTRACTID, &kNS_ADDONCONTENTPOLICY_CID },
   { NS_ADDONPATHSERVICE_CONTRACTID, &kNS_ADDON_PATH_SERVICE_CID },
   { NS_ADDONMANAGERSTARTUP_CONTRACTID, &kNS_ADDON_MANAGER_STARTUP_CID },
+  { NS_ADDON_POLICY_SERVICE_CONTRACTID, &kNS_ADDON_POLICY_SERVICE_CID },
   { NATIVE_FILEWATCHER_SERVICE_CONTRACTID, &kNATIVE_FILEWATCHER_SERVICE_CID },
   { NS_WEBREQUESTLISTENER_CONTRACTID, &kNS_WEBREQUESTLISTENER_CID },
   { nullptr }
 };
 
 static const mozilla::Module::CategoryEntry kToolkitCategories[] = {
   { "content-policy", NS_ADDONCONTENTPOLICY_CONTRACTID, NS_ADDONCONTENTPOLICY_CONTRACTID },
   { nullptr }
--- a/toolkit/components/extensions/.eslintrc.js
+++ b/toolkit/components/extensions/.eslintrc.js
@@ -8,16 +8,17 @@ module.exports = {
     "Cr": true,
     "Cu": true,
     "TextDecoder": false,
     "TextEncoder": false,
 
     "MatchGlob": false,
     "MatchPattern": true,
     "MatchPatternSet": false,
+    "WebExtensionPolicy": false,
     // Specific to WebExtensions:
     "AppConstants": true,
     "Extension": true,
     "ExtensionAPI": true,
     "ExtensionManagement": true,
     "ExtensionUtils": true,
     "extensions": true,
     "getContainerForCookieStoreId": true,
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -752,16 +752,19 @@ this.Extension = class extends Extension
       }
 
       let origins = permissions.origins.map(
         origin => new MatchPattern(origin, {ignorePath: true}).pattern);
 
       this.whiteListedHosts = new MatchPatternSet(
         this.whiteListedHosts.patterns
             .filter(host => !origins.includes(host.pattern)));
+
+      this.policy.permissions = Array.from(this.permissions);
+      this.policy.allowedOrigins = this.whiteListedHosts;
     });
     /* eslint-enable mozilla/balanced-listeners */
   }
 
   static generateXPI(data) {
     return ExtensionTestCommon.generateXPI(data);
   }
 
@@ -1018,17 +1021,17 @@ this.Extension = class extends Extension
       Management.emit("ready", this);
       this.emit("ready");
     } catch (e) {
       dump(`Extension error: ${e.message} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`);
       Cu.reportError(e);
 
       if (this.started) {
         this.started = false;
-        ExtensionManagement.shutdownExtension(this.uuid);
+        ExtensionManagement.shutdownExtension(this);
       }
 
       this.cleanupGeneratedFile();
 
       throw e;
     }
 
     this.startupPromise = null;
@@ -1075,17 +1078,17 @@ this.Extension = class extends Extension
     }
 
     let data = Services.ppmm.initialProcessData;
     data["Extension:Extensions"] = data["Extension:Extensions"].filter(e => e.id !== this.id);
 
     Services.ppmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this);
 
     if (!this.manifest) {
-      ExtensionManagement.shutdownExtension(this.uuid);
+      ExtensionManagement.shutdownExtension(this);
 
       this.cleanupGeneratedFile();
       return;
     }
 
     GlobalManager.uninit(this);
 
     for (let obj of this.onShutdown) {
@@ -1100,17 +1103,17 @@ this.Extension = class extends Extension
 
     Management.emit("shutdown", this);
     this.emit("shutdown");
 
     Services.ppmm.broadcastAsyncMessage("Extension:Shutdown", {id: this.id});
 
     MessageChannel.abortResponses({extensionId: this.id});
 
-    ExtensionManagement.shutdownExtension(this.uuid);
+    ExtensionManagement.shutdownExtension(this);
 
     return this.cleanupGeneratedFile();
   }
 
   observe(subject, topic, data) {
     if (topic === "xpcom-shutdown") {
       this.cleanupGeneratedFile();
     }
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -522,16 +522,21 @@ class BrowserExtensionContent extends Ev
       if (permissions.origins.length > 0) {
         let origins = permissions.origins.map(
           origin => new MatchPattern(origin, {ignorePath: true}).pattern);
 
         this.whiteListedHosts = new MatchPatternSet(
           this.whiteListedHosts.patterns
               .filter(host => !origins.includes(host.pattern)));
       }
+
+      if (this.policy) {
+        this.policy.permissions = Array.from(this.permissions);
+        this.policy.allowedOrigins = this.whiteListedHosts;
+      }
     });
     /* eslint-enable mozilla/balanced-listeners */
 
     ExtensionManager.extensions.set(this.id, this);
   }
 
   shutdown() {
     ExtensionManager.extensions.delete(this.id);
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -11,31 +11,25 @@ const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
                                   "resource:///modules/E10SUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
-                                  "resource://gre/modules/ExtensionUtils.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionUtils.getConsole());
 
 XPCOMUtils.defineLazyGetter(this, "UUIDMap", () => {
   let {UUIDMap} = Cu.import("resource://gre/modules/Extension.jsm", {});
   return UUIDMap;
 });
 
 const {appinfo} = Services;
 const isParentProcess = appinfo.processType === appinfo.PROCESS_TYPE_DEFAULT;
 
-var ExtensionManagement;
-
 /*
  * This file should be kept short and simple since it's loaded even
  * when no extensions are running.
  */
 
 var APIs = {
   apis: new Map(),
 
@@ -60,147 +54,61 @@ function getURLForExtension(id, path = "
   let uuid = UUIDMap.get(id, false);
   if (!uuid) {
     Cu.reportError(`Called getURLForExtension on unmapped extension ${id}`);
     return null;
   }
   return `moz-extension://${uuid}/${path}`;
 }
 
-// This object manages various platform-level issues related to
-// moz-extension:// URIs. It lives here so that it can be used in both
-// the parent and child processes.
-//
-// moz-extension URIs have the form moz-extension://uuid/path. Each
-// extension has its own UUID, unique to the machine it's installed
-// on. This is easier and more secure than using the extension ID,
-// since it makes it slightly harder to fingerprint for extensions if
-// each user uses different URIs for the extension.
-var Service = {
-  initialized: false,
-
-  // Map[uuid -> extension].
-  // extension can be an Extension (parent process) or BrowserExtensionContent (child process).
-  uuidMap: new Map(),
-
-  init() {
-    let aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService);
-    aps = aps.wrappedJSObject;
-    this.aps = aps;
-    aps.setExtensionURILoadCallback(this.extensionURILoadableByAnyone.bind(this));
-    aps.setExtensionURIToAddonIdCallback(this.extensionURIToAddonID.bind(this));
-  },
-
-  // Called when a new extension is loaded.
-  startupExtension(uuid, uri, extension) {
-    if (!this.initialized) {
-      this.initialized = true;
-      this.init();
-    }
-
-    // Create the moz-extension://uuid mapping.
-    let handler = Services.io.getProtocolHandler("moz-extension");
-    handler.QueryInterface(Ci.nsISubstitutingProtocolHandler);
-    handler.setSubstitution(uuid, uri);
-
-    this.uuidMap.set(uuid, extension);
-    this.aps.setAddonHasPermissionCallback(extension.id, extension.hasPermission.bind(extension));
-    this.aps.setAddonLoadURICallback(extension.id, this.checkAddonMayLoad.bind(this, extension));
-    this.aps.setAddonLocalizeCallback(extension.id, extension.localize.bind(extension));
-    this.aps.setAddonCSP(extension.id, extension.manifest.content_security_policy);
-    this.aps.setBackgroundPageUrlCallback(uuid, this.generateBackgroundPageUrl.bind(this, extension));
-  },
-
-  // Called when an extension is unloaded.
-  shutdownExtension(uuid) {
-    let extension = this.uuidMap.get(uuid);
-    if (!extension) {
-      return;
-    }
-    this.uuidMap.delete(uuid);
-    this.aps.setAddonHasPermissionCallback(extension.id, null);
-    this.aps.setAddonLoadURICallback(extension.id, null);
-    this.aps.setAddonLocalizeCallback(extension.id, null);
-    this.aps.setAddonCSP(extension.id, null);
-    this.aps.setBackgroundPageUrlCallback(uuid, null);
-
-    let handler = Services.io.getProtocolHandler("moz-extension");
-    handler.QueryInterface(Ci.nsISubstitutingProtocolHandler);
-    handler.setSubstitution(uuid, null);
-  },
-
-  // Return true if the given URI can be loaded from arbitrary web
-  // content. The manifest.json |web_accessible_resources| directive
-  // determines this.
-  extensionURILoadableByAnyone(uri) {
-    let uuid = uri.host;
-    let extension = this.uuidMap.get(uuid);
-    if (!extension || !extension.webAccessibleResources) {
-      return false;
-    }
-
-    let path = uri.QueryInterface(Ci.nsIURL).filePath;
-    return extension.webAccessibleResources.some(res => res.matches(path));
-  },
-
-  // Checks whether a given extension can load this URI (typically via
-  // an XML HTTP request). The manifest.json |permissions| directive
-  // determines this.
-  checkAddonMayLoad(extension, uri, explicit = false) {
-    return extension.whiteListedHosts.matchesIgnoringPath(uri, explicit);
-  },
-
-  generateBackgroundPageUrl(extension) {
-    let background_scripts = (extension.manifest.background &&
-                              extension.manifest.background.scripts);
-
-    if (!background_scripts) {
-      return;
-    }
-
-    let html = "<!DOCTYPE html>\n<html>\n<body>\n";
-    for (let script of background_scripts) {
-      script = script.replace(/"/g, "&quot;");
-      html += `<script src="${script}"></script>\n`;
-    }
-    html += "</body>\n</html>\n";
-
-    return "data:text/html;charset=utf-8," + encodeURIComponent(html);
-  },
-
-  // Finds the add-on ID associated with a given moz-extension:// URI.
-  // This is used to set the addonId on the for the nsIPrincipal
-  // attached to the URI.
-  extensionURIToAddonID(uri) {
-    let uuid = uri.host;
-    let extension = this.uuidMap.get(uuid);
-    return extension ? extension.id : undefined;
-  },
-};
-
 let cacheInvalidated = 0;
 function onCacheInvalidate() {
   cacheInvalidated++;
 }
 Services.obs.addObserver(onCacheInvalidate, "startupcache-invalidate");
 
-ExtensionManagement = {
+var ExtensionManagement = {
   get cacheInvalidated() {
     return cacheInvalidated;
   },
 
   get isExtensionProcess() {
     if (this.useRemoteWebExtensions) {
       return appinfo.remoteType === E10SUtils.EXTENSION_REMOTE_TYPE;
     }
     return isParentProcess;
   },
 
-  startupExtension: Service.startupExtension.bind(Service),
-  shutdownExtension: Service.shutdownExtension.bind(Service),
+  // Called when a new extension is loaded.
+  startupExtension(uuid, uri, extension) {
+    let policy = new WebExtensionPolicy({
+      id: extension.id,
+      mozExtensionHostname: uuid,
+      baseURL: uri.spec,
+
+      permissions: Array.from(extension.permissions || []),
+      allowedOrigins: extension.whiteListedHosts,
+      webAccessibleResources: extension.webAccessibleResources || [],
+
+      contentSecurityPolicy: extension.manifest.content_security_policy,
+
+      localizeCallback: extension.localize.bind(extension),
+
+      backgroundScripts: (extension.manifest.background &&
+                          extension.manifest.background.scripts),
+    });
+
+    extension.policy = policy;
+    policy.active = true;
+  },
+
+  // Called when an extension is unloaded.
+  shutdownExtension(extension) {
+    extension.policy.active = false;
+  },
 
   registerAPI: APIs.register.bind(APIs),
   unregisterAPI: APIs.unregister.bind(APIs),
 
   getURLForExtension,
 
   APIs,
 };
--- a/toolkit/components/extensions/ExtensionPolicyService.cpp
+++ b/toolkit/components/extensions/ExtensionPolicyService.cpp
@@ -1,16 +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 "mozilla/ExtensionPolicyService.h"
 #include "mozilla/extensions/WebExtensionPolicy.h"
+
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
+#include "nsEscape.h"
 #include "nsGkAtoms.h"
 
 namespace mozilla {
 
 using namespace extensions;
 
 #define DEFAULT_BASE_CSP \
     "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " \
@@ -104,18 +106,115 @@ ExtensionPolicyService::DefaultCSP(nsASt
   nsresult rv;
 
   rv = Preferences::GetString("extensions.webextensions.default-content-security-policy", &aDefaultCSP);
   if (NS_FAILED(rv)) {
     aDefaultCSP.AssignLiteral(DEFAULT_DEFAULT_CSP);
   }
 }
 
+
+/*****************************************************************************
+ * nsIAddonPolicyService
+ *****************************************************************************/
+
+nsresult
+ExtensionPolicyService::GetBaseCSP(nsAString& aBaseCSP)
+{
+  BaseCSP(aBaseCSP);
+  return NS_OK;
+}
+
+nsresult
+ExtensionPolicyService::GetDefaultCSP(nsAString& aDefaultCSP)
+{
+  DefaultCSP(aDefaultCSP);
+  return NS_OK;
+}
+
+nsresult
+ExtensionPolicyService::GetAddonCSP(const nsAString& aAddonId,
+                                    nsAString& aResult)
+{
+  if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
+    policy->GetContentSecurityPolicy(aResult);
+    return NS_OK;
+  }
+  return NS_ERROR_INVALID_ARG;
+}
+
+nsresult
+ExtensionPolicyService::GetGeneratedBackgroundPageUrl(const nsACString& aHostname,
+                                                      nsACString& aResult)
+{
+  if (WebExtensionPolicy* policy = GetByHost(aHostname)) {
+    nsAutoCString url("data:text/html,");
+
+    nsCString html = policy->BackgroundPageHTML();
+    nsAutoCString escaped;
+
+    url.Append(NS_EscapeURL(html, esc_Minimal, escaped));
+
+    aResult = url;
+    return NS_OK;
+  }
+  return NS_ERROR_INVALID_ARG;
+}
+
+nsresult
+ExtensionPolicyService::AddonHasPermission(const nsAString& aAddonId,
+                                           const nsAString& aPerm,
+                                           bool* aResult)
+{
+  if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
+    *aResult = policy->HasPermission(aPerm);
+    return NS_OK;
+  }
+  return NS_ERROR_INVALID_ARG;
+}
+
+nsresult
+ExtensionPolicyService::AddonMayLoadURI(const nsAString& aAddonId,
+                                        nsIURI* aURI,
+                                        bool aExplicit,
+                                        bool* aResult)
+{
+  if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
+    *aResult = policy->CanAccessURI(aURI, aExplicit);
+    return NS_OK;
+  }
+  return NS_ERROR_INVALID_ARG;
+}
+
+nsresult
+ExtensionPolicyService::ExtensionURILoadableByAnyone(nsIURI* aURI, bool* aResult)
+{
+  URLInfo url(aURI);
+  if (WebExtensionPolicy* policy = GetByURL(url)) {
+    *aResult = policy->IsPathWebAccessible(url.Path());
+    return NS_OK;
+  }
+  return NS_ERROR_INVALID_ARG;
+}
+
+nsresult
+ExtensionPolicyService::ExtensionURIToAddonId(nsIURI* aURI, nsAString& aResult)
+{
+  if (WebExtensionPolicy* policy = GetByURL(aURI)) {
+    policy->GetId(aResult);
+  } else {
+    aResult.SetIsVoid(true);
+  }
+  return NS_OK;
+}
+
+
 NS_IMPL_CYCLE_COLLECTION(ExtensionPolicyService, mExtensions, mExtensionHosts)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPolicyService)
+  NS_INTERFACE_MAP_ENTRY(nsIAddonPolicyService)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionPolicyService)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionPolicyService)
 
 } // namespace mozilla
--- a/toolkit/components/extensions/ExtensionPolicyService.h
+++ b/toolkit/components/extensions/ExtensionPolicyService.h
@@ -5,33 +5,41 @@
 
 #ifndef mozilla_ExtensionPolicyService_h
 #define mozilla_ExtensionPolicyService_h
 
 #include "mozilla/extensions/WebExtensionPolicy.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsHashKeys.h"
+#include "nsIAddonPolicyService.h"
 #include "nsIAtom.h"
 #include "nsISupports.h"
 #include "nsPointerHashKeys.h"
 #include "nsRefPtrHashtable.h"
 
 namespace mozilla {
 
 using extensions::WebExtensionPolicy;
 
-class ExtensionPolicyService final : public nsISupports
+class ExtensionPolicyService final : public nsIAddonPolicyService
 {
 public:
   NS_DECL_CYCLE_COLLECTION_CLASS(ExtensionPolicyService)
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_NSIADDONPOLICYSERVICE
 
   static ExtensionPolicyService& GetSingleton();
 
+  static already_AddRefed<ExtensionPolicyService> GetInstance()
+  {
+    RefPtr<ExtensionPolicyService> service = &GetSingleton();
+    return service.forget();
+  }
+
   WebExtensionPolicy*
   GetByID(const nsIAtom* aAddonId)
   {
     return mExtensions.GetWeak(aAddonId);
   }
 
   WebExtensionPolicy* GetByID(const nsAString& aAddonId)
   {
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -552,17 +552,17 @@ class StubExtension {
     if (isContentProcess) {
       let uri = Services.io.newURI(this.data.resourceURL);
       ExtensionManagement.startupExtension(this.uuid, uri, this);
     }
   }
 
   shutdown() {
     if (isContentProcess) {
-      ExtensionManagement.shutdownExtension(this.uuid);
+      ExtensionManagement.shutdownExtension(this);
     }
     if (this._realExtension) {
       this._realExtension.shutdown();
     }
   }
 
   // Lazily create the real extension object when needed.
   get realExtension() {
--- a/toolkit/components/extensions/test/xpcshell/test_csp_custom_policies.js
+++ b/toolkit/components/extensions/test/xpcshell/test_csp_custom_policies.js
@@ -2,37 +2,55 @@
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 
 const ADDON_ID = "test@web.extension";
 
 const aps = Cc["@mozilla.org/addons/policy-service;1"]
-  .getService(Ci.nsIAddonPolicyService).wrappedJSObject;
+  .getService(Ci.nsIAddonPolicyService);
+
+let policy = null;
+
+function setAddonCSP(csp) {
+  if (policy) {
+    policy.active = false;
+  }
+
+  policy = new WebExtensionPolicy({
+    id: ADDON_ID,
+    mozExtensionHostname: ADDON_ID,
+    baseURL: "file:///",
+
+    allowedOrigins: new MatchPatternSet([]),
+    localizeCallback() {},
+
+    contentSecurityPolicy: csp,
+  });
+
+  policy.active = true;
+}
 
 do_register_cleanup(() => {
-  aps.setAddonCSP(ADDON_ID, null);
+  policy.active = false;
 });
 
 add_task(function* test_addon_csp() {
   equal(aps.baseCSP, Preferences.get("extensions.webextensions.base-content-security-policy"),
         "Expected base CSP value");
 
   equal(aps.defaultCSP, Preferences.get("extensions.webextensions.default-content-security-policy"),
         "Expected default CSP value");
 
-  equal(aps.getAddonCSP(ADDON_ID), aps.defaultCSP,
-        "CSP for unknown add-on ID should be the default CSP");
-
 
   const CUSTOM_POLICY = "script-src: 'self' https://xpcshell.test.custom.csp; object-src: 'none'";
 
-  aps.setAddonCSP(ADDON_ID, CUSTOM_POLICY);
+  setAddonCSP(CUSTOM_POLICY);
 
   equal(aps.getAddonCSP(ADDON_ID), CUSTOM_POLICY, "CSP should point to add-on's custom policy");
 
 
-  aps.setAddonCSP(ADDON_ID, null);
+  setAddonCSP(null);
 
   equal(aps.getAddonCSP(ADDON_ID), aps.defaultCSP,
         "CSP should revert to default when set to null");
 });
--- a/toolkit/components/extensions/test/xpcshell/test_locale_converter.js
+++ b/toolkit/components/extensions/test/xpcshell/test_locale_converter.js
@@ -18,32 +18,32 @@ function StringStream(string) {
   stream.data = string;
   return stream;
 }
 
 
 // Initialize the policy service with a stub localizer for our
 // add-on ID.
 add_task(async function init() {
-  const aps = Cc["@mozilla.org/addons/policy-service;1"]
-    .getService(Ci.nsIAddonPolicyService).wrappedJSObject;
+  let policy = new WebExtensionPolicy({
+    id: ADDON_ID,
+    mozExtensionHostname: UUID,
+    baseURL: "file:///",
 
-  let oldCallback = aps.setExtensionURIToAddonIdCallback(uri => {
-    if (uri.host == UUID) {
-      return ADDON_ID;
-    }
+    allowedOrigins: new MatchPatternSet([]),
+
+    localizeCallback(string) {
+      return string.replace(/__MSG_(.*?)__/g, "<localized-$1>");
+    },
   });
 
-  aps.setAddonLocalizeCallback(ADDON_ID, string => {
-    return string.replace(/__MSG_(.*?)__/g, "<localized-$1>");
-  });
+  policy.active = true;
 
   do_register_cleanup(() => {
-    aps.setExtensionURIToAddonIdCallback(oldCallback);
-    aps.setAddonLocalizeCallback(ADDON_ID, null);
+    policy.active = false;
   });
 });
 
 
 // Test that the synchronous converter works as expected with a
 // simple string.
 add_task(async function testSynchronousConvert() {
   let stream = StringStream("Foo __MSG_xxx__ bar __MSG_yyy__ baz");
--- a/toolkit/components/utils/simpleServices.js
+++ b/toolkit/components/utils/simpleServices.js
@@ -12,224 +12,33 @@
 
 "use strict";
 
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
+/* globals WebExtensionPolicy */
+
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
-function AddonPolicyService() {
-  this.wrappedJSObject = this;
-  this.cspStrings = new Map();
-  this.backgroundPageUrlCallbacks = new Map();
-  this.checkHasPermissionCallbacks = new Map();
-  this.mayLoadURICallbacks = new Map();
-  this.localizeCallbacks = new Map();
-
-  XPCOMUtils.defineLazyPreferenceGetter(
-    this, "baseCSP", "extensions.webextensions.base-content-security-policy",
-    "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " +
-    "object-src 'self' https://* moz-extension: blob: filesystem:;");
-
-  XPCOMUtils.defineLazyPreferenceGetter(
-    this, "defaultCSP", "extensions.webextensions.default-content-security-policy",
-    "script-src 'self'; object-src 'self';");
-}
-
-AddonPolicyService.prototype = {
-  classID: Components.ID("{89560ed3-72e3-498d-a0e8-ffe50334d7c5}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonPolicyService]),
-
-  /**
-   * Returns the content security policy which applies to documents belonging
-   * to the extension with the given ID. This may be either a custom policy,
-   * if one was supplied, or the default policy if one was not.
-   */
-  getAddonCSP(aAddonId) {
-    let csp = this.cspStrings.get(aAddonId);
-    return csp || this.defaultCSP;
-  },
-
-  /**
-   * Returns the generated background page as a data-URI, if any. If the addon
-   * does not have an auto-generated background page, an empty string is
-   * returned.
-   */
-  getGeneratedBackgroundPageUrl(aAddonId) {
-    let cb = this.backgroundPageUrlCallbacks.get(aAddonId);
-    return cb && cb(aAddonId) || "";
-  },
-
-  /*
-   * Invokes a callback (if any) associated with the addon to determine whether
-   * the addon is granted the |aPerm| API permission.
-   *
-   * @see nsIAddonPolicyService.addonHasPermission
-   */
-  addonHasPermission(aAddonId, aPerm) {
-    let cb = this.checkHasPermissionCallbacks.get(aAddonId);
-    return cb ? cb(aPerm) : false;
-  },
-
-  /*
-   * Invokes a callback (if any) associated with the addon to determine whether
-   * unprivileged code running within the addon is allowed to perform loads from
-   * the given URI.
-   *
-   * @see nsIAddonPolicyService.addonMayLoadURI
-   */
-  addonMayLoadURI(aAddonId, aURI, aExplicit = false) {
-    let cb = this.mayLoadURICallbacks.get(aAddonId);
-    return cb ? cb(aURI, aExplicit) : false;
-  },
-
-  /*
-   * Invokes a callback (if any) associated with the addon to loclaize a
-   * resource belonging to that add-on.
-   */
-  localizeAddonString(aAddonId, aString) {
-    let cb = this.localizeCallbacks.get(aAddonId);
-    return cb ? cb(aString) : aString;
-  },
-
-  /*
-   * Invokes a callback (if any) to determine if an extension URI should be
-   * web-accessible.
-   *
-   * @see nsIAddonPolicyService.extensionURILoadableByAnyone
-   */
-  extensionURILoadableByAnyone(aURI) {
-    if (aURI.scheme != "moz-extension") {
-      throw new TypeError("non-extension URI passed");
-    }
-
-    let cb = this.extensionURILoadCallback;
-    return cb ? cb(aURI) : false;
-  },
-
-  /*
-   * Maps an extension URI to an addon ID.
-   *
-   * @see nsIAddonPolicyService.extensionURIToAddonId
-   */
-  extensionURIToAddonId(aURI) {
-    if (aURI.scheme != "moz-extension") {
-      throw new TypeError("non-extension URI passed");
-    }
-
-    let cb = this.extensionURIToAddonIdCallback;
-    if (!cb) {
-      throw new Error("no callback set to map extension URIs to addon Ids");
-    }
-    return cb(aURI);
-  },
-
-  /*
-   * Sets the callbacks used in addonHasPermission above. Not accessible over
-   * XPCOM - callers should use .wrappedJSObject on the service to call it
-   * directly.
-   */
-  setAddonHasPermissionCallback(aAddonId, aCallback) {
-    if (aCallback) {
-      this.checkHasPermissionCallbacks.set(aAddonId, aCallback);
-    } else {
-      this.checkHasPermissionCallbacks.delete(aAddonId);
-    }
-  },
-
-  /*
-   * Sets the callbacks used in addonMayLoadURI above. Not accessible over
-   * XPCOM - callers should use .wrappedJSObject on the service to call it
-   * directly.
-   */
-  setAddonLoadURICallback(aAddonId, aCallback) {
-    if (aCallback) {
-      this.mayLoadURICallbacks.set(aAddonId, aCallback);
-    } else {
-      this.mayLoadURICallbacks.delete(aAddonId);
-    }
-  },
-
-  /*
-   * Sets the custom CSP string to be used for the add-on. Not accessible over
-   * XPCOM - callers should use .wrappedJSObject on the service to call it
-   * directly.
-   */
-  setAddonCSP(aAddonId, aCSPString) {
-    if (aCSPString) {
-      this.cspStrings.set(aAddonId, aCSPString);
-    } else {
-      this.cspStrings.delete(aAddonId);
-    }
-  },
-
-  /**
-   * Set the callback that generates a data-URL for the background page.
-   */
-  setBackgroundPageUrlCallback(aAddonId, aCallback) {
-    if (aCallback) {
-      this.backgroundPageUrlCallbacks.set(aAddonId, aCallback);
-    } else {
-      this.backgroundPageUrlCallbacks.delete(aAddonId);
-    }
-  },
-
-  /*
-   * Sets the callbacks used by the stream converter service to localize
-   * add-on resources.
-   */
-  setAddonLocalizeCallback(aAddonId, aCallback) {
-    if (aCallback) {
-      this.localizeCallbacks.set(aAddonId, aCallback);
-    } else {
-      this.localizeCallbacks.delete(aAddonId);
-    }
-  },
-
-  /*
-   * Sets the callback used in extensionURILoadableByAnyone above. Not
-   * accessible over XPCOM - callers should use .wrappedJSObject on the
-   * service to call it directly.
-   */
-  setExtensionURILoadCallback(aCallback) {
-    var old = this.extensionURILoadCallback;
-    this.extensionURILoadCallback = aCallback;
-    return old;
-  },
-
-  /*
-   * Sets the callback used in extensionURIToAddonId above. Not accessible over
-   * XPCOM - callers should use .wrappedJSObject on the service to call it
-   * directly.
-   */
-  setExtensionURIToAddonIdCallback(aCallback) {
-    var old = this.extensionURIToAddonIdCallback;
-    this.extensionURIToAddonIdCallback = aCallback;
-    return old;
-  }
-};
-
 /*
  * This class provides a stream filter for locale messages in CSS files served
  * by the moz-extension: protocol handler.
  *
  * See SubstituteChannel in netwerk/protocol/res/ExtensionProtocolHandler.cpp
  * for usage.
  */
 function AddonLocalizationConverter() {
-  this.aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService)
-    .wrappedJSObject;
 }
 
 AddonLocalizationConverter.prototype = {
   classID: Components.ID("{ded150e3-c92e-4077-a396-0dba9953e39f}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamConverter]),
 
   FROM_TYPE: "application/vnd.mozilla.webext.unlocalized",
   TO_TYPE: "text/css",
@@ -241,71 +50,70 @@ AddonLocalizationConverter.prototype = {
     }
     if (aToType != this.TO_TYPE) {
       throw Components.Exception("Invalid aToType value", Cr.NS_ERROR_INVALID_ARG,
                                  Components.stack.caller.caller);
     }
   },
 
   // aContext must be a nsIURI object for a valid moz-extension: URL.
-  getAddonId(aContext) {
+  getAddon(aContext) {
     // In this case, we want the add-on ID even if the URL is web accessible,
     // so check the root rather than the exact path.
     let uri = Services.io.newURI("/", null, aContext);
 
-    let id = this.aps.extensionURIToAddonId(uri);
-    if (id == undefined) {
+    let addon = WebExtensionPolicy.getByURI(uri);
+    if (!addon) {
       throw new Components.Exception("Invalid context", Cr.NS_ERROR_INVALID_ARG);
     }
-    return id;
+    return addon;
   },
 
-  convertToStream(aAddonId, aString) {
+  convertToStream(aAddon, aString) {
     let stream = Cc["@mozilla.org/io/string-input-stream;1"]
       .createInstance(Ci.nsIStringInputStream);
 
-    stream.data = this.aps.localizeAddonString(aAddonId, aString);
+    stream.data = aAddon.localize(aString);
     return stream;
   },
 
   convert(aStream, aFromType, aToType, aContext) {
     this.checkTypes(aFromType, aToType);
-    let addonId = this.getAddonId(aContext);
+    let addon = this.getAddon(aContext);
 
     let string = (
       aStream.available() ?
       NetUtil.readInputStreamToString(aStream, aStream.available()) : ""
     );
-    return this.convertToStream(addonId, string);
+    return this.convertToStream(addon, string);
   },
 
   asyncConvertData(aFromType, aToType, aListener, aContext) {
     this.checkTypes(aFromType, aToType);
-    this.addonId = this.getAddonId(aContext);
+    this.addon = this.getAddon(aContext);
     this.listener = aListener;
   },
 
   onStartRequest(aRequest, aContext) {
     this.parts = [];
   },
 
   onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) {
     this.parts.push(NetUtil.readInputStreamToString(aInputStream, aCount));
   },
 
   onStopRequest(aRequest, aContext, aStatusCode) {
     try {
       this.listener.onStartRequest(aRequest, null);
       if (Components.isSuccessCode(aStatusCode)) {
         let string = this.parts.join("");
-        let stream = this.convertToStream(this.addonId, string);
+        let stream = this.convertToStream(this.addon, string);
 
         this.listener.onDataAvailable(aRequest, null, stream, 0, stream.data.length);
       }
     } catch (e) {
       aStatusCode = e.result || Cr.NS_ERROR_FAILURE;
     }
     this.listener.onStopRequest(aRequest, null, aStatusCode);
   },
 };
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonPolicyService,
-                                                     AddonLocalizationConverter]);
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonLocalizationConverter]);
--- a/toolkit/components/utils/utils.manifest
+++ b/toolkit/components/utils/utils.manifest
@@ -1,6 +1,4 @@
 component {dfd07380-6083-11e4-9803-0800200c9a66} simpleServices.js
 contract @mozilla.org/addons/remote-tag-service;1 {dfd07380-6083-11e4-9803-0800200c9a66}
-component {89560ed3-72e3-498d-a0e8-ffe50334d7c5} simpleServices.js
-contract @mozilla.org/addons/policy-service;1 {89560ed3-72e3-498d-a0e8-ffe50334d7c5}
 component {ded150e3-c92e-4077-a396-0dba9953e39f} simpleServices.js
 contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.webext.unlocalized&to=text/css {ded150e3-c92e-4077-a396-0dba9953e39f}