Bug 1317697: Remove things from ExtensionUtils that don't belong there. r?mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 07 Apr 2017 13:39:06 -0700
changeset 562401 e92b0ff2be481690ae7642bb1dc2bd43b6381564
parent 562400 7ca9c01d26684f36dbfbed28f6eb1ca056fcf09b
child 624235 96b6f15adbff80c97e81b6d924ea9c175f39f8e7
push id54021
push usermaglione.k@gmail.com
push dateThu, 13 Apr 2017 22:30:15 +0000
reviewersmixedpuppy
bugs1317697
milestone55.0a1
Bug 1317697: Remove things from ExtensionUtils that don't belong there. r?mixedpuppy MozReview-Commit-ID: CTX0TckLqoV
browser/components/extensions/ext-bookmarks.js
browser/components/extensions/ext-browserAction.js
browser/components/extensions/ext-c-devtools-panels.js
browser/components/extensions/ext-c-omnibox.js
browser/components/extensions/ext-commands.js
browser/components/extensions/ext-contextMenus.js
browser/components/extensions/ext-devtools-network.js
browser/components/extensions/ext-history.js
browser/components/extensions/ext-omnibox.js
browser/components/extensions/ext-pageAction.js
browser/components/extensions/ext-sessions.js
browser/components/extensions/ext-tabs.js
browser/components/extensions/ext-utils.js
browser/components/extensions/ext-windows.js
mobile/android/components/extensions/ext-pageAction.js
mobile/android/components/extensions/ext-tabs.js
mobile/android/components/extensions/ext-utils.js
toolkit/components/extensions/.eslintrc.js
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionChild.jsm
toolkit/components/extensions/ExtensionCommon.jsm
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/ext-alarms.js
toolkit/components/extensions/ext-c-test.js
toolkit/components/extensions/ext-c-toolkit.js
toolkit/components/extensions/ext-cookies.js
toolkit/components/extensions/ext-downloads.js
toolkit/components/extensions/ext-i18n.js
toolkit/components/extensions/ext-idle.js
toolkit/components/extensions/ext-notifications.js
toolkit/components/extensions/ext-proxy.js
toolkit/components/extensions/ext-runtime.js
toolkit/components/extensions/ext-storage.js
toolkit/components/extensions/ext-toolkit.js
toolkit/components/extensions/ext-webNavigation.js
toolkit/components/extensions/ext-webRequest.js
toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
toolkit/components/extensions/test/xpcshell/test_ext_themes_supported_properties.js
--- a/browser/components/extensions/ext-bookmarks.js
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -1,16 +1,12 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
 let listenerCount = 0;
 
 function getTree(rootGuid, onlyChildren) {
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -15,17 +15,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "@mozilla.org/inspector/dom-utils;1",
                                    "inIDOMUtils");
 
 Cu.import("resource://devtools/shared/event-emitter.js");
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {
   IconDetails,
-  SingletonEventManager,
 } = ExtensionUtils;
 
 const POPUP_PRELOAD_TIMEOUT_MS = 200;
 
 var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 function isAncestorOrSelf(target, node) {
   for (; node; node = node.parentNode) {
--- a/browser/components/extensions/ext-c-devtools-panels.js
+++ b/browser/components/extensions/ext-c-devtools-panels.js
@@ -2,17 +2,16 @@
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                   "resource://devtools/shared/event-emitter.js");
 
 var {
   promiseDocumentLoaded,
-  SingletonEventManager,
 } = ExtensionUtils;
 
 /**
  * Represents an addon devtools panel in the child process.
  *
  * @param {DevtoolsExtensionContext}
  *   A devtools extension context running in a child process.
  * @param {object} panelOptions
--- a/browser/components/extensions/ext-c-omnibox.js
+++ b/browser/components/extensions/ext-c-omnibox.js
@@ -1,16 +1,12 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 this.omnibox = class extends ExtensionAPI {
   getAPI(context) {
     return {
       omnibox: {
         onInputChanged: new SingletonEventManager(context, "omnibox.onInputChanged", fire => {
           let listener = (text, id) => {
             fire.asyncWithoutClone(text, suggestions => {
               context.childManager.callParentFunctionNoReturn("omnibox_internal.addSuggestions", [
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -1,14 +1,13 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 var {
-  SingletonEventManager,
   PlatformInfo,
 } = ExtensionUtils;
 
 var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 this.commands = class extends ExtensionAPI {
   onManifestEntry(entryName) {
     let {extension} = this;
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -6,17 +6,16 @@ Cu.import("resource://gre/modules/MatchP
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
 var {
   ExtensionError,
   IconDetails,
-  SingletonEventManager,
 } = ExtensionUtils;
 
 const ACTION_MENU_TOP_LEVEL_LIMIT = 6;
 
 // Map[Extension -> Map[ID -> MenuItem]]
 // Note: we want to enumerate all the menu items so
 // this cannot be a weak map.
 var gContextMenuMap = new Map();
--- a/browser/components/extensions/ext-devtools-network.js
+++ b/browser/components/extensions/ext-devtools-network.js
@@ -1,16 +1,12 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 this.devtools_network = class extends ExtensionAPI {
   getAPI(context) {
     return {
       devtools: {
         network: {
           onNavigated: new SingletonEventManager(context, "devtools.onNavigated", fire => {
             let listener = (event, data) => {
               fire.async(data.url);
--- a/browser/components/extensions/ext-history.js
+++ b/browser/components/extensions/ext-history.js
@@ -4,17 +4,16 @@
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 
 var {
   normalizeTime,
-  SingletonEventManager,
 } = ExtensionUtils;
 
 let nsINavHistoryService = Ci.nsINavHistoryService;
 const TRANSITION_TO_TRANSITION_TYPES_MAP = new Map([
   ["link", nsINavHistoryService.TRANSITION_LINK],
   ["typed", nsINavHistoryService.TRANSITION_TYPED],
   ["auto_bookmark", nsINavHistoryService.TRANSITION_BOOKMARK],
   ["auto_subframe", nsINavHistoryService.TRANSITION_EMBED],
--- a/browser/components/extensions/ext-omnibox.js
+++ b/browser/components/extensions/ext-omnibox.js
@@ -1,17 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSearchHandler",
                                   "resource://gre/modules/ExtensionSearchHandler.jsm");
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
 
 this.omnibox = class extends ExtensionAPI {
   onManifestEntry(entryName) {
     let {extension} = this;
     let {manifest} = extension;
 
     let keyword = manifest.omnibox.keyword;
     try {
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -3,17 +3,16 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "PanelPopup",
                                   "resource:///modules/ExtensionPopups.jsm");
 
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {
-  SingletonEventManager,
   IconDetails,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> PageAction]
 let pageActionMap = new WeakMap();
 
 this.pageAction = class extends ExtensionAPI {
   static for(extension) {
--- a/browser/components/extensions/ext-sessions.js
+++ b/browser/components/extensions/ext-sessions.js
@@ -1,15 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 var {
   promiseObserved,
-  SingletonEventManager,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
 const SS_ON_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed";
 
 function getRecentlyClosed(maxResults, extension) {
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -10,20 +10,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 let tabListener = {
   tabReadyInitialized: false,
   tabReadyPromises: new WeakMap(),
   initializingTabs: new WeakSet(),
 
   initTabReady() {
     if (!this.tabReadyInitialized) {
       windowTracker.addListener("progress", this);
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -9,17 +9,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 Cu.import("resource://gre/modules/ExtensionTabs.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                    "@mozilla.org/content/style-sheet-service;1",
                                    "nsIStyleSheetService");
 
 var {
   ExtensionError,
-  SingletonEventManager,
   defineLazyGetter,
 } = ExtensionUtils;
 
 let tabTracker;
 let windowTracker;
 
 // This file provides some useful code for the |tabs| and |windows|
 // modules. All of the code is installed on |global|, which is a scope
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -6,17 +6,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 var {
-  SingletonEventManager,
   promiseObserved,
 } = ExtensionUtils;
 
 function onXULFrameLoaderCreated({target}) {
   target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
 }
 
 this.windows = class extends ExtensionAPI {
--- a/mobile/android/components/extensions/ext-pageAction.js
+++ b/mobile/android/components/extensions/ext-pageAction.js
@@ -11,17 +11,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 // Import the android PageActions module.
 XPCOMUtils.defineLazyModuleGetter(this, "PageActions",
                                   "resource://gre/modules/PageActions.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   IconDetails,
-  SingletonEventManager,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> PageAction]
 var pageActionMap = new WeakMap();
 
 function PageAction(options, extension) {
   this.id = null;
 
--- a/mobile/android/components/extensions/ext-tabs.js
+++ b/mobile/android/components/extensions/ext-tabs.js
@@ -10,20 +10,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 // This function is pretty tightly tied to Extension.jsm.
 // Its job is to fill in the |tab| property of the sender.
 function getSender(extension, target, sender) {
   let tabId;
   if ("tabId" in sender) {
     // The message came from a privileged extension page running in a tab. In
     // that case, it should include a tabId property (which is filled in by the
     // page-open listener below).
--- a/mobile/android/components/extensions/ext-utils.js
+++ b/mobile/android/components/extensions/ext-utils.js
@@ -10,17 +10,16 @@ Cu.import("resource://gre/modules/Extens
 /* globals EventDispatcher */
 Cu.import("resource://gre/modules/Messaging.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   DefaultWeakMap,
   ExtensionError,
-  SingletonEventManager,
   defineLazyGetter,
 } = ExtensionUtils;
 
 global.GlobalEventDispatcher = EventDispatcher.instance;
 
 const BrowserStatusFilter = Components.Constructor(
   "@mozilla.org/appshell/component/browser-status-filter;1", "nsIWebProgress",
   "addProgressListener");
--- a/toolkit/components/extensions/.eslintrc.js
+++ b/toolkit/components/extensions/.eslintrc.js
@@ -32,16 +32,17 @@ module.exports = {
     "isValidCookieStoreId": true,
     "NetUtil": true,
     "openOptionsPage": true,
     "require": false,
     "runSafe": true,
     "runSafeSync": true,
     "runSafeSyncWithoutClone": true,
     "Services": true,
+    "SingletonEventManager": true,
     "tabTracker": false,
     "XPCOMUtils": true,
   },
 
   "rules": {
     // Rules from the mozilla plugin
     "mozilla/balanced-listeners": "error",
     "mozilla/no-aArgs": "error",
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -31,16 +31,18 @@ Cu.import("resource://gre/modules/Servic
 XPCOMUtils.defineLazyPreferenceGetter(this, "processCount", "dom.ipc.processCount.extension");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionAPIs",
                                   "resource://gre/modules/ExtensionAPI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionCommon",
+                                  "resource://gre/modules/ExtensionCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPermissions",
                                   "resource://gre/modules/ExtensionPermissions.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
                                   "resource://gre/modules/ExtensionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestCommon",
                                   "resource://testing-common/ExtensionTestCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Locale",
                                   "resource://gre/modules/Locale.jsm");
@@ -79,24 +81,58 @@ var {
   GlobalManager,
   ParentAPIManager,
   apiManager: Management,
 } = ExtensionParent;
 
 const {
   classifyPermission,
   EventEmitter,
-  LocaleData,
   StartupCache,
   getUniqueId,
-  validateThemeManifest,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
 
+XPCOMUtils.defineLazyGetter(this, "LocaleData", () => ExtensionCommon.LocaleData);
+
+
+// The list of properties that themes are allowed to contain.
+XPCOMUtils.defineLazyGetter(this, "allowedThemeProperties", () => {
+  Cu.import("resource://gre/modules/ExtensionParent.jsm");
+  let propertiesInBaseManifest = ExtensionParent.baseManifestProperties;
+
+  // The properties found in the base manifest contain all of the properties that
+  // themes are allowed to have. However, the list also contains several properties
+  // that aren't allowed, so we need to filter them out first before the list can
+  // be used to validate themes.
+  return propertiesInBaseManifest.filter(prop => {
+    const propertiesToRemove = ["background", "content_scripts", "permissions"];
+    return !propertiesToRemove.includes(prop);
+  });
+});
+
+/**
+ * Validates a theme to ensure it only contains static resources.
+ *
+ * @param {Array<string>} manifestProperties The list of top-level keys found in the
+ *    the extension's manifest.
+ * @returns {Array<string>} A list of invalid properties or an empty list
+ *    if none are found.
+ */
+function validateThemeManifest(manifestProperties) {
+  let invalidProps = [];
+  for (let propName of manifestProperties) {
+    if (propName != "theme" && !allowedThemeProperties.includes(propName)) {
+      invalidProps.push(propName);
+    }
+  }
+  return invalidProps;
+}
+
 const LOGGER_ID_BASE = "addons.webextension.";
 const UUID_MAP_PREF = "extensions.webextensions.uuids";
 const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
 const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
 
 const COMMENT_REGEXP = new RegExp(String.raw`
     ^
     (
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -39,28 +39,28 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 Cu.import("resource://gre/modules/ExtensionCommon.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 const {
   DefaultMap,
   EventEmitter,
   LimitedSet,
-  LocaleData,
-  SingletonEventManager,
   SpreadArgs,
   defineLazyGetter,
   getMessageManager,
   getUniqueId,
   injectAPI,
 } = ExtensionUtils;
 
 const {
   LocalAPIImplementation,
+  LocaleData,
   SchemaAPIInterface,
+  SingletonEventManager,
 } = ExtensionCommon;
 
 /**
  * Abstraction for a Port object in the extension API.
  *
  * @param {BaseContext} context The context that owns this port.
  * @param {nsIMessageSender} senderMM The message manager to send messages to.
  * @param {Array<nsIMessageListenerManager>} receiverMMs Message managers to
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -13,18 +13,22 @@ const {classes: Cc, interfaces: Ci, util
 
 /* exported ExtensionCommon */
 
 this.EXPORTED_SYMBOLS = ["ExtensionCommon"];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Locale",
+                                  "resource://gre/modules/Locale.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+                                  "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
@@ -1113,15 +1117,327 @@ class SchemaAPIManager extends EventEmit
       if (Schemas.checkPermissions(api.namespace, {hasPermission})) {
         api = api.getAPI(context);
         deepCopy(obj, api);
       }
     }
   }
 }
 
+function LocaleData(data) {
+  this.defaultLocale = data.defaultLocale;
+  this.selectedLocale = data.selectedLocale;
+  this.locales = data.locales || new Map();
+  this.warnedMissingKeys = new Set();
+
+  // Map(locale-name -> Map(message-key -> localized-string))
+  //
+  // Contains a key for each loaded locale, each of which is a
+  // Map of message keys to their localized strings.
+  this.messages = data.messages || new Map();
+
+  if (data.builtinMessages) {
+    this.messages.set(this.BUILTIN, data.builtinMessages);
+  }
+}
+
+LocaleData.prototype = {
+  // Representation of the object to send to content processes. This
+  // should include anything the content process might need.
+  serialize() {
+    return {
+      defaultLocale: this.defaultLocale,
+      selectedLocale: this.selectedLocale,
+      messages: this.messages,
+      locales: this.locales,
+    };
+  },
+
+  BUILTIN: "@@BUILTIN_MESSAGES",
+
+  has(locale) {
+    return this.messages.has(locale);
+  },
+
+  // https://developer.chrome.com/extensions/i18n
+  localizeMessage(message, substitutions = [], options = {}) {
+    let defaultOptions = {
+      locale: this.selectedLocale,
+      defaultValue: "",
+      cloneScope: null,
+    };
+
+    options = Object.assign(defaultOptions, options);
+
+    let locales = new Set([this.BUILTIN, options.locale, this.defaultLocale]
+                          .filter(locale => this.messages.has(locale)));
+
+    // Message names are case-insensitive, so normalize them to lower-case.
+    message = message.toLowerCase();
+    for (let locale of locales) {
+      let messages = this.messages.get(locale);
+      if (messages.has(message)) {
+        let str = messages.get(message);
+
+        if (!Array.isArray(substitutions)) {
+          substitutions = [substitutions];
+        }
+
+        let replacer = (matched, index, dollarSigns) => {
+          if (index) {
+            // This is not quite Chrome-compatible. Chrome consumes any number
+            // of digits following the $, but only accepts 9 substitutions. We
+            // accept any number of substitutions.
+            index = parseInt(index, 10) - 1;
+            return index in substitutions ? substitutions[index] : "";
+          }
+          // For any series of contiguous `$`s, the first is dropped, and
+          // the rest remain in the output string.
+          return dollarSigns;
+        };
+        return str.replace(/\$(?:([1-9]\d*)|(\$+))/g, replacer);
+      }
+    }
+
+    // Check for certain pre-defined messages.
+    if (message == "@@ui_locale") {
+      return this.uiLocale;
+    } else if (message.startsWith("@@bidi_")) {
+      let registry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
+      let rtl = registry.isLocaleRTL("global");
+
+      if (message == "@@bidi_dir") {
+        return rtl ? "rtl" : "ltr";
+      } else if (message == "@@bidi_reversed_dir") {
+        return rtl ? "ltr" : "rtl";
+      } else if (message == "@@bidi_start_edge") {
+        return rtl ? "right" : "left";
+      } else if (message == "@@bidi_end_edge") {
+        return rtl ? "left" : "right";
+      }
+    }
+
+    if (!this.warnedMissingKeys.has(message)) {
+      let error = `Unknown localization message ${message}`;
+      if (options.cloneScope) {
+        error = new options.cloneScope.Error(error);
+      }
+      Cu.reportError(error);
+      this.warnedMissingKeys.add(message);
+    }
+    return options.defaultValue;
+  },
+
+  // Localize a string, replacing all |__MSG_(.*)__| tokens with the
+  // matching string from the current locale, as determined by
+  // |this.selectedLocale|.
+  //
+  // This may not be called before calling either |initLocale| or
+  // |initAllLocales|.
+  localize(str, locale = this.selectedLocale) {
+    if (!str) {
+      return str;
+    }
+
+    return str.replace(/__MSG_([A-Za-z0-9@_]+?)__/g, (matched, message) => {
+      return this.localizeMessage(message, [], {locale, defaultValue: matched});
+    });
+  },
+
+  // Validates the contents of a locale JSON file, normalizes the
+  // messages into a Map of message key -> localized string pairs.
+  addLocale(locale, messages, extension) {
+    let result = new Map();
+
+    // Chrome does not document the semantics of its localization
+    // system very well. It handles replacements by pre-processing
+    // messages, replacing |$[a-zA-Z0-9@_]+$| tokens with the value of their
+    // replacements. Later, it processes the resulting string for
+    // |$[0-9]| replacements.
+    //
+    // Again, it does not document this, but it accepts any number
+    // of sequential |$|s, and replaces them with that number minus
+    // 1. It also accepts |$| followed by any number of sequential
+    // digits, but refuses to process a localized string which
+    // provides more than 9 substitutions.
+    if (!instanceOf(messages, "Object")) {
+      extension.packagingError(`Invalid locale data for ${locale}`);
+      return result;
+    }
+
+    for (let key of Object.keys(messages)) {
+      let msg = messages[key];
+
+      if (!instanceOf(msg, "Object") || typeof(msg.message) != "string") {
+        extension.packagingError(`Invalid locale message data for ${locale}, message ${JSON.stringify(key)}`);
+        continue;
+      }
+
+      // Substitutions are case-insensitive, so normalize all of their names
+      // to lower-case.
+      let placeholders = new Map();
+      if (instanceOf(msg.placeholders, "Object")) {
+        for (let key of Object.keys(msg.placeholders)) {
+          placeholders.set(key.toLowerCase(), msg.placeholders[key]);
+        }
+      }
+
+      let replacer = (match, name) => {
+        let replacement = placeholders.get(name.toLowerCase());
+        if (instanceOf(replacement, "Object") && "content" in replacement) {
+          return replacement.content;
+        }
+        return "";
+      };
+
+      let value = msg.message.replace(/\$([A-Za-z0-9@_]+)\$/g, replacer);
+
+      // Message names are also case-insensitive, so normalize them to lower-case.
+      result.set(key.toLowerCase(), value);
+    }
+
+    this.messages.set(locale, result);
+    return result;
+  },
+
+  get acceptLanguages() {
+    let result = Preferences.get("intl.accept_languages", "", Ci.nsIPrefLocalizedString);
+    return result.split(/\s*,\s*/g);
+  },
+
+
+  get uiLocale() {
+    // Return the browser locale, but convert it to a Chrome-style
+    // locale code.
+    return Locale.getLocale().replace(/-/g, "_");
+  },
+};
+
+// This is a generic class for managing event listeners. Example usage:
+//
+// new SingletonEventManager(context, "api.subAPI", fire => {
+//   let listener = (...) => {
+//     // Fire any listeners registered with addListener.
+//     fire.async(arg1, arg2);
+//   };
+//   // Register the listener.
+//   SomehowRegisterListener(listener);
+//   return () => {
+//     // Return a way to unregister the listener.
+//     SomehowUnregisterListener(listener);
+//   };
+// }).api()
+//
+// The result is an object with addListener, removeListener, and
+// hasListener methods. |context| is an add-on scope (either an
+// ExtensionContext in the chrome process or ExtensionContext in a
+// content process). |name| is for debugging. |register| is a function
+// to register the listener. |register| should return an
+// unregister function that will unregister the listener.
+function SingletonEventManager(context, name, register) {
+  this.context = context;
+  this.name = name;
+  this.register = register;
+  this.unregister = new Map();
+}
+
+SingletonEventManager.prototype = {
+  addListener(callback, ...args) {
+    if (this.unregister.has(callback)) {
+      return;
+    }
+
+    let shouldFire = () => {
+      if (this.context.unloaded) {
+        dump(`${this.name} event fired after context unloaded.\n`);
+      } else if (!this.context.active) {
+        dump(`${this.name} event fired while context is inactive.\n`);
+      } else if (this.unregister.has(callback)) {
+        return true;
+      }
+      return false;
+    };
+
+    let fire = {
+      sync: (...args) => {
+        if (shouldFire()) {
+          return this.context.runSafe(callback, ...args);
+        }
+      },
+      async: (...args) => {
+        return Promise.resolve().then(() => {
+          if (shouldFire()) {
+            return this.context.runSafe(callback, ...args);
+          }
+        });
+      },
+      raw: (...args) => {
+        if (!shouldFire()) {
+          throw new Error("Called raw() on unloaded/inactive context");
+        }
+        return callback(...args);
+      },
+      asyncWithoutClone: (...args) => {
+        return Promise.resolve().then(() => {
+          if (shouldFire()) {
+            return this.context.runSafeWithoutClone(callback, ...args);
+          }
+        });
+      },
+    };
+
+
+    let unregister = this.register(fire, ...args);
+    this.unregister.set(callback, unregister);
+    this.context.callOnClose(this);
+  },
+
+  removeListener(callback) {
+    if (!this.unregister.has(callback)) {
+      return;
+    }
+
+    let unregister = this.unregister.get(callback);
+    this.unregister.delete(callback);
+    try {
+      unregister();
+    } catch (e) {
+      Cu.reportError(e);
+    }
+    if (this.unregister.size == 0) {
+      this.context.forgetOnClose(this);
+    }
+  },
+
+  hasListener(callback) {
+    return this.unregister.has(callback);
+  },
+
+  revoke() {
+    for (let callback of this.unregister.keys()) {
+      this.removeListener(callback);
+    }
+  },
+
+  close() {
+    this.revoke();
+  },
+
+  api() {
+    return {
+      addListener: (...args) => this.addListener(...args),
+      removeListener: (...args) => this.removeListener(...args),
+      hasListener: (...args) => this.hasListener(...args),
+      [Schemas.REVOKE]: () => this.revoke(),
+    };
+  },
+};
+
+
 const ExtensionCommon = {
   BaseContext,
   CanOfAPIs,
   LocalAPIImplementation,
+  LocaleData,
   SchemaAPIInterface,
   SchemaAPIManager,
+  SingletonEventManager,
 };
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -21,24 +21,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI",
                                   "resource://gre/modules/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "IndexedDB",
                                   "resource://gre/modules/IndexedDB.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
-                                  "resource:///modules/translation/LanguageDetector.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Locale",
-                                  "resource://gre/modules/Locale.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                    "@mozilla.org/content/style-sheet-service;1",
                                    "nsIStyleSheetService");
@@ -56,49 +50,16 @@ XPCOMUtils.defineLazyGetter(this, "conso
 
 let nextId = 0;
 XPCOMUtils.defineLazyGetter(this, "uniqueProcessID", () => Services.appinfo.uniqueProcessID);
 
 function getUniqueId() {
   return `${nextId++}-${uniqueProcessID}`;
 }
 
-// The list of properties that themes are allowed to contain.
-XPCOMUtils.defineLazyGetter(this, "gAllowedThemeProperties", () => {
-  Cu.import("resource://gre/modules/ExtensionParent.jsm");
-  let propertiesInBaseManifest = ExtensionParent.baseManifestProperties;
-
-  // The properties found in the base manifest contain all of the properties that
-  // themes are allowed to have. However, the list also contains several properties
-  // that aren't allowed, so we need to filter them out first before the list can
-  // be used to validate themes.
-  return propertiesInBaseManifest.filter(prop => {
-    const propertiesToRemove = ["background", "content_scripts", "permissions"];
-    return !propertiesToRemove.includes(prop);
-  });
-});
-
-/**
- * Validates a theme to ensure it only contains static resources.
- *
- * @param {Array<string>} manifestProperties The list of top-level keys found in the
- *    the extension's manifest.
- * @returns {Array<string>} A list of invalid properties or an empty list
- *    if none are found.
- */
-function validateThemeManifest(manifestProperties) {
-  let invalidProps = [];
-  for (let propName of manifestProperties) {
-    if (propName != "theme" && !gAllowedThemeProperties.includes(propName)) {
-      invalidProps.push(propName);
-    }
-  }
-  return invalidProps;
-}
-
 let StartupCache = {
   DB_NAME: "ExtensionStartupCache",
 
   SCHEMA_VERSION: 1,
 
   STORE_NAMES: Object.freeze(["locales", "manifests", "schemas"]),
 
   dbPromise: null,
@@ -571,325 +532,16 @@ class EventEmitter {
     let promises = Array.from(listeners, listener => {
       return runSafeSyncWithoutClone(listener, event, ...args);
     });
 
     return Promise.all(promises);
   }
 }
 
-function LocaleData(data) {
-  this.defaultLocale = data.defaultLocale;
-  this.selectedLocale = data.selectedLocale;
-  this.locales = data.locales || new Map();
-  this.warnedMissingKeys = new Set();
-
-  // Map(locale-name -> Map(message-key -> localized-string))
-  //
-  // Contains a key for each loaded locale, each of which is a
-  // Map of message keys to their localized strings.
-  this.messages = data.messages || new Map();
-
-  if (data.builtinMessages) {
-    this.messages.set(this.BUILTIN, data.builtinMessages);
-  }
-}
-
-
-LocaleData.prototype = {
-  // Representation of the object to send to content processes. This
-  // should include anything the content process might need.
-  serialize() {
-    return {
-      defaultLocale: this.defaultLocale,
-      selectedLocale: this.selectedLocale,
-      messages: this.messages,
-      locales: this.locales,
-    };
-  },
-
-  BUILTIN: "@@BUILTIN_MESSAGES",
-
-  has(locale) {
-    return this.messages.has(locale);
-  },
-
-  // https://developer.chrome.com/extensions/i18n
-  localizeMessage(message, substitutions = [], options = {}) {
-    let defaultOptions = {
-      locale: this.selectedLocale,
-      defaultValue: "",
-      cloneScope: null,
-    };
-
-    options = Object.assign(defaultOptions, options);
-
-    let locales = new Set([this.BUILTIN, options.locale, this.defaultLocale]
-                          .filter(locale => this.messages.has(locale)));
-
-    // Message names are case-insensitive, so normalize them to lower-case.
-    message = message.toLowerCase();
-    for (let locale of locales) {
-      let messages = this.messages.get(locale);
-      if (messages.has(message)) {
-        let str = messages.get(message);
-
-        if (!Array.isArray(substitutions)) {
-          substitutions = [substitutions];
-        }
-
-        let replacer = (matched, index, dollarSigns) => {
-          if (index) {
-            // This is not quite Chrome-compatible. Chrome consumes any number
-            // of digits following the $, but only accepts 9 substitutions. We
-            // accept any number of substitutions.
-            index = parseInt(index, 10) - 1;
-            return index in substitutions ? substitutions[index] : "";
-          }
-          // For any series of contiguous `$`s, the first is dropped, and
-          // the rest remain in the output string.
-          return dollarSigns;
-        };
-        return str.replace(/\$(?:([1-9]\d*)|(\$+))/g, replacer);
-      }
-    }
-
-    // Check for certain pre-defined messages.
-    if (message == "@@ui_locale") {
-      return this.uiLocale;
-    } else if (message.startsWith("@@bidi_")) {
-      let rtl = Services.locale.isAppLocaleRTL;
-
-      if (message == "@@bidi_dir") {
-        return rtl ? "rtl" : "ltr";
-      } else if (message == "@@bidi_reversed_dir") {
-        return rtl ? "ltr" : "rtl";
-      } else if (message == "@@bidi_start_edge") {
-        return rtl ? "right" : "left";
-      } else if (message == "@@bidi_end_edge") {
-        return rtl ? "left" : "right";
-      }
-    }
-
-    if (!this.warnedMissingKeys.has(message)) {
-      let error = `Unknown localization message ${message}`;
-      if (options.cloneScope) {
-        error = new options.cloneScope.Error(error);
-      }
-      Cu.reportError(error);
-      this.warnedMissingKeys.add(message);
-    }
-    return options.defaultValue;
-  },
-
-  // Localize a string, replacing all |__MSG_(.*)__| tokens with the
-  // matching string from the current locale, as determined by
-  // |this.selectedLocale|.
-  //
-  // This may not be called before calling either |initLocale| or
-  // |initAllLocales|.
-  localize(str, locale = this.selectedLocale) {
-    if (!str) {
-      return str;
-    }
-
-    return str.replace(/__MSG_([A-Za-z0-9@_]+?)__/g, (matched, message) => {
-      return this.localizeMessage(message, [], {locale, defaultValue: matched});
-    });
-  },
-
-  // Validates the contents of a locale JSON file, normalizes the
-  // messages into a Map of message key -> localized string pairs.
-  addLocale(locale, messages, extension) {
-    let result = new Map();
-
-    // Chrome does not document the semantics of its localization
-    // system very well. It handles replacements by pre-processing
-    // messages, replacing |$[a-zA-Z0-9@_]+$| tokens with the value of their
-    // replacements. Later, it processes the resulting string for
-    // |$[0-9]| replacements.
-    //
-    // Again, it does not document this, but it accepts any number
-    // of sequential |$|s, and replaces them with that number minus
-    // 1. It also accepts |$| followed by any number of sequential
-    // digits, but refuses to process a localized string which
-    // provides more than 9 substitutions.
-    if (!instanceOf(messages, "Object")) {
-      extension.packagingError(`Invalid locale data for ${locale}`);
-      return result;
-    }
-
-    for (let key of Object.keys(messages)) {
-      let msg = messages[key];
-
-      if (!instanceOf(msg, "Object") || typeof(msg.message) != "string") {
-        extension.packagingError(`Invalid locale message data for ${locale}, message ${JSON.stringify(key)}`);
-        continue;
-      }
-
-      // Substitutions are case-insensitive, so normalize all of their names
-      // to lower-case.
-      let placeholders = new Map();
-      if (instanceOf(msg.placeholders, "Object")) {
-        for (let key of Object.keys(msg.placeholders)) {
-          placeholders.set(key.toLowerCase(), msg.placeholders[key]);
-        }
-      }
-
-      let replacer = (match, name) => {
-        let replacement = placeholders.get(name.toLowerCase());
-        if (instanceOf(replacement, "Object") && "content" in replacement) {
-          return replacement.content;
-        }
-        return "";
-      };
-
-      let value = msg.message.replace(/\$([A-Za-z0-9@_]+)\$/g, replacer);
-
-      // Message names are also case-insensitive, so normalize them to lower-case.
-      result.set(key.toLowerCase(), value);
-    }
-
-    this.messages.set(locale, result);
-    return result;
-  },
-
-  get acceptLanguages() {
-    let result = Preferences.get("intl.accept_languages", "", Ci.nsIPrefLocalizedString);
-    return result.split(/\s*,\s*/g);
-  },
-
-
-  get uiLocale() {
-    // Return the browser locale, but convert it to a Chrome-style
-    // locale code.
-    return Locale.getLocale().replace(/-/g, "_");
-  },
-};
-
-// This is a generic class for managing event listeners. Example usage:
-//
-// new SingletonEventManager(context, "api.subAPI", fire => {
-//   let listener = (...) => {
-//     // Fire any listeners registered with addListener.
-//     fire.async(arg1, arg2);
-//   };
-//   // Register the listener.
-//   SomehowRegisterListener(listener);
-//   return () => {
-//     // Return a way to unregister the listener.
-//     SomehowUnregisterListener(listener);
-//   };
-// }).api()
-//
-// The result is an object with addListener, removeListener, and
-// hasListener methods. |context| is an add-on scope (either an
-// ExtensionContext in the chrome process or ExtensionContext in a
-// content process). |name| is for debugging. |register| is a function
-// to register the listener. |register| should return an
-// unregister function that will unregister the listener.
-function SingletonEventManager(context, name, register) {
-  this.context = context;
-  this.name = name;
-  this.register = register;
-  this.unregister = new Map();
-}
-
-SingletonEventManager.prototype = {
-  addListener(callback, ...args) {
-    if (this.unregister.has(callback)) {
-      return;
-    }
-
-    let shouldFire = () => {
-      if (this.context.unloaded) {
-        dump(`${this.name} event fired after context unloaded.\n`);
-      } else if (!this.context.active) {
-        dump(`${this.name} event fired while context is inactive.\n`);
-      } else if (this.unregister.has(callback)) {
-        return true;
-      }
-      return false;
-    };
-
-    let fire = {
-      sync: (...args) => {
-        if (shouldFire()) {
-          return this.context.runSafe(callback, ...args);
-        }
-      },
-      async: (...args) => {
-        return Promise.resolve().then(() => {
-          if (shouldFire()) {
-            return this.context.runSafe(callback, ...args);
-          }
-        });
-      },
-      raw: (...args) => {
-        if (!shouldFire()) {
-          throw new Error("Called raw() on unloaded/inactive context");
-        }
-        return callback(...args);
-      },
-      asyncWithoutClone: (...args) => {
-        return Promise.resolve().then(() => {
-          if (shouldFire()) {
-            return this.context.runSafeWithoutClone(callback, ...args);
-          }
-        });
-      },
-    };
-
-
-    let unregister = this.register(fire, ...args);
-    this.unregister.set(callback, unregister);
-    this.context.callOnClose(this);
-  },
-
-  removeListener(callback) {
-    if (!this.unregister.has(callback)) {
-      return;
-    }
-
-    let unregister = this.unregister.get(callback);
-    this.unregister.delete(callback);
-    try {
-      unregister();
-    } catch (e) {
-      Cu.reportError(e);
-    }
-    if (this.unregister.size == 0) {
-      this.context.forgetOnClose(this);
-    }
-  },
-
-  hasListener(callback) {
-    return this.unregister.has(callback);
-  },
-
-  revoke() {
-    for (let callback of this.unregister.keys()) {
-      this.removeListener(callback);
-    }
-  },
-
-  close() {
-    this.revoke();
-  },
-
-  api() {
-    return {
-      addListener: (...args) => this.addListener(...args),
-      removeListener: (...args) => this.removeListener(...args),
-      hasListener: (...args) => this.hasListener(...args),
-      [Schemas.REVOKE]: () => this.revoke(),
-    };
-  },
-};
-
 // Simple API for event listeners where events never fire.
 function ignoreEvent(context, name) {
   return {
     addListener: function(callback) {
       let id = context.extension.id;
       let frame = Components.stack.caller;
       let msg = `In add-on ${id}, attempting to use listener "${name}", which is unimplemented.`;
       let scriptError = Cc["@mozilla.org/scripterror;1"]
@@ -1075,28 +727,16 @@ function PlatformInfo() {
       } else if (arch == "x86_64") {
         arch = "x86-64";
       }
       return arch;
     })(),
   });
 }
 
-function detectLanguage(text) {
-  return LanguageDetector.detectLanguage(text).then(result => ({
-    isReliable: result.confident,
-    languages: result.languages.map(lang => {
-      return {
-        language: lang.languageCode,
-        percentage: lang.percent,
-      };
-    }),
-  }));
-}
-
 /**
  * Convert any of several different representations of a date/time to a Date object.
  * Accepts several formats:
  * a Date object, an ISO8601 string, or a number of milliseconds since the epoch as
  * either a number or a string.
  *
  * @param {Date|string|number} date
  *      The date to convert.
@@ -1106,17 +746,17 @@ function detectLanguage(text) {
 function normalizeTime(date) {
   // Of all the formats we accept the "number of milliseconds since the epoch as a string"
   // is an outlier, everything else can just be passed directly to the Date constructor.
   return new Date((typeof date == "string" && /^\d+$/.test(date))
                         ? parseInt(date, 10) : date);
 }
 
 const stylesheetMap = new DefaultMap(url => {
-  let uri = NetUtil.newURI(url);
+  let uri = Services.io.newURI(url);
   return styleSheetService.preloadSheet(uri, styleSheetService.AGENT_SHEET);
 });
 
 /**
  * Defines a lazy getter for the given property on the given object. The
  * first time the property is accessed, the return value of the getter
  * is defined on the current `this` object with the given property name.
  * Importantly, this means that a lazy getter defined on an object
@@ -1374,17 +1014,16 @@ function classifyPermission(perm) {
     return {api: match[2]};
   }
   return {permission: perm};
 }
 
 this.ExtensionUtils = {
   classifyPermission,
   defineLazyGetter,
-  detectLanguage,
   extend,
   findPathInObject,
   flushJarCache,
   getConsole,
   getInnerWindowID,
   getMessageManager,
   getUniqueId,
   filterStack,
@@ -1397,23 +1036,20 @@ this.ExtensionUtils = {
   promiseDocumentReady,
   promiseEvent,
   promiseObserved,
   runSafe,
   runSafeSync,
   runSafeSyncWithoutClone,
   runSafeWithoutClone,
   stylesheetMap,
-  validateThemeManifest,
   DefaultMap,
   DefaultWeakMap,
   EventEmitter,
   ExtensionError,
   IconDetails,
   LimitedSet,
-  LocaleData,
   MessageManagerProxy,
-  SingletonEventManager,
   SpreadArgs,
   StartupCache,
 };
 
 XPCOMUtils.defineLazyGetter(this.ExtensionUtils, "PlatformInfo", PlatformInfo);
--- a/toolkit/components/extensions/ext-alarms.js
+++ b/toolkit/components/extensions/ext-alarms.js
@@ -1,14 +1,10 @@
 "use strict";
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 // WeakMap[Extension -> Map[name -> Alarm]]
 let alarmsMap = new WeakMap();
 
 // WeakMap[Extension -> Set[callback]]
 let alarmCallbacksMap = new WeakMap();
 
 // Manages an alarm created by the extension (alarms API).
 function Alarm(extension, name, alarmInfo) {
--- a/toolkit/components/extensions/ext-c-test.js
+++ b/toolkit/components/extensions/ext-c-test.js
@@ -1,14 +1,10 @@
 "use strict";
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 /**
  * Checks whether the given error matches the given expectations.
  *
  * @param {*} error
  *        The error to check.
  * @param {string|RegExp|function|null} expectedError
  *        The expectation to check against. If this parameter is:
  *
--- a/toolkit/components/extensions/ext-c-toolkit.js
+++ b/toolkit/components/extensions/ext-c-toolkit.js
@@ -1,10 +1,14 @@
 "use strict";
 
+Cu.import("resource://gre/modules/ExtensionCommon.jsm");
+
+global.SingletonEventManager = ExtensionCommon.SingletonEventManager;
+
 global.initializeBackgroundPage = (contentWindow) => {
   // Override the `alert()` method inside background windows;
   // we alias it to console.log().
   // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1203394
   let alertDisplayedWarning = false;
   let alertOverwrite = text => {
     if (!alertDisplayedWarning) {
       require("devtools/client/framework/devtools-browser");
--- a/toolkit/components/extensions/ext-cookies.js
+++ b/toolkit/components/extensions/ext-cookies.js
@@ -2,20 +2,16 @@
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                   "resource://gre/modules/ContextualIdentityService.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
 /* globals DEFAULT_STORE, PRIVATE_STORE */
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 function convert({cookie, isPrivate}) {
   let result = {
     name: cookie.name,
     value: cookie.value,
     domain: cookie.host,
     hostOnly: !cookie.isDomain,
     path: cookie.path,
     secure: cookie.isSecure,
--- a/toolkit/components/extensions/ext-downloads.js
+++ b/toolkit/components/extensions/ext-downloads.js
@@ -11,17 +11,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                   "resource://devtools/shared/event-emitter.js");
 
 var {
   ignoreEvent,
   normalizeTime,
-  SingletonEventManager,
   PlatformInfo,
 } = ExtensionUtils;
 
 const DOWNLOAD_ITEM_FIELDS = ["id", "url", "referrer", "filename", "incognito",
                               "danger", "mime", "startTime", "endTime",
                               "estimatedEndTime", "state",
                               "paused", "canResume", "error",
                               "bytesReceived", "totalBytes",
--- a/toolkit/components/extensions/ext-i18n.js
+++ b/toolkit/components/extensions/ext-i18n.js
@@ -1,13 +1,13 @@
 "use strict";
 
-var {
-  detectLanguage,
-} = ExtensionUtils;
+XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
+                                  "resource:///modules/translation/LanguageDetector.jsm");
+
 
 this.i18n = class extends ExtensionAPI {
   getAPI(context) {
     let {extension} = context;
     return {
       i18n: {
         getMessage: function(messageName, substitutions) {
           return extension.localizeMessage(messageName, substitutions, {cloneScope: context.cloneScope});
@@ -18,14 +18,22 @@ this.i18n = class extends ExtensionAPI {
           return Promise.resolve(result);
         },
 
         getUILanguage: function() {
           return extension.localeData.uiLocale;
         },
 
         detectLanguage: function(text) {
-          return detectLanguage(text);
+          return LanguageDetector.detectLanguage(text).then(result => ({
+            isReliable: result.confident,
+            languages: result.languages.map(lang => {
+              return {
+                language: lang.languageCode,
+                percentage: lang.percent,
+              };
+            }),
+          }));
         },
       },
     };
   }
 };
--- a/toolkit/components/extensions/ext-idle.js
+++ b/toolkit/components/extensions/ext-idle.js
@@ -1,18 +1,15 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                   "resource://devtools/shared/event-emitter.js");
 XPCOMUtils.defineLazyServiceGetter(this, "idleService",
                                    "@mozilla.org/widget/idleservice;1",
                                    "nsIIdleService");
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
 
 // WeakMap[Extension -> Object]
 let observersMap = new WeakMap();
 
 function getObserverInfo(extension, context) {
   let observerInfo = observersMap.get(extension);
   if (!observerInfo) {
     observerInfo = {
--- a/toolkit/components/extensions/ext-notifications.js
+++ b/toolkit/components/extensions/ext-notifications.js
@@ -1,15 +1,14 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                   "resource://devtools/shared/event-emitter.js");
 
 var {
-  SingletonEventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Map[id -> Notification]]
 let notificationsMap = new WeakMap();
 
 // Manages a notification popup (notifications API) created by the extension.
 function Notification(extension, id, options) {
--- a/toolkit/components/extensions/ext-proxy.js
+++ b/toolkit/components/extensions/ext-proxy.js
@@ -5,20 +5,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "ProxyScriptContext",
                                   "resource://gre/modules/ProxyScriptContext.jsm");
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 // WeakMap[Extension -> ProxyScriptContext]
 let proxyScriptContextMap = new WeakMap();
 
 this.proxy = class extends ExtensionAPI {
   onShutdown() {
     let {extension} = this;
 
     let proxyScriptContext = proxyScriptContextMap.get(extension);
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -4,20 +4,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 this.runtime = class extends ExtensionAPI {
   getAPI(context) {
     let {extension} = context;
     return {
       runtime: {
         onStartup: new SingletonEventManager(context, "runtime.onStartup", fire => {
           if (context.incognito) {
             // This event should not fire if we are operating in a private profile.
--- a/toolkit/components/extensions/ext-storage.js
+++ b/toolkit/components/extensions/ext-storage.js
@@ -4,17 +4,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/ExtensionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "extensionStorageSync",
                                   "resource://gre/modules/ExtensionStorageSync.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                   "resource://gre/modules/AddonManager.jsm");
 
 var {
   ExtensionError,
-  SingletonEventManager,
 } = ExtensionUtils;
 
 function enforceNoTemporaryAddon(extensionId) {
   const EXCEPTION_MESSAGE =
         "The storage API will not work with a temporary addon ID. " +
         "Please add an explicit addon ID to your manifest. " +
         "For more information see https://bugzil.la/1323228.";
   if (AddonManagerPrivate.isTemporaryInstallID(extensionId)) {
--- a/toolkit/components/extensions/ext-toolkit.js
+++ b/toolkit/components/extensions/ext-toolkit.js
@@ -1,13 +1,17 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                   "resource://gre/modules/ContextualIdentityService.jsm");
 
+Cu.import("resource://gre/modules/ExtensionCommon.jsm");
+
+global.SingletonEventManager = ExtensionCommon.SingletonEventManager;
+
 /* globals DEFAULT_STORE, PRIVATE_STORE, CONTAINER_STORE */
 
 global.DEFAULT_STORE = "firefox-default";
 global.PRIVATE_STORE = "firefox-private";
 global.CONTAINER_STORE = "firefox-container-";
 
 global.getCookieStoreIdForTab = function(data, tab) {
   if (data.incognito) {
--- a/toolkit/components/extensions/ext-webNavigation.js
+++ b/toolkit/components/extensions/ext-webNavigation.js
@@ -2,20 +2,16 @@
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MatchURLFilters",
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebNavigation",
                                   "resource://gre/modules/WebNavigation.jsm");
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 const defaultTransitionTypes = {
   topFrame: "link",
   subFrame: "auto_subframe",
 };
 
 const frameTransitions = {
   anyFrame: {
     qualifiers: ["server_redirect", "client_redirect", "forward_back"],
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -1,44 +1,39 @@
 "use strict";
 
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
+                                  "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                   "resource://gre/modules/MatchPattern.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
                                   "resource://gre/modules/WebRequest.jsm");
 
-Cu.import("resource://gre/modules/ExtensionManagement.jsm");
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 // EventManager-like class specifically for WebRequest. Inherits from
 // SingletonEventManager. Takes care of converting |details| parameter
 // when invoking listeners.
 function WebRequestEventManager(context, eventName) {
   let name = `webRequest.${eventName}`;
   let register = (fire, filter, info) => {
     let listener = data => {
       // Prevent listening in on requests originating from system principal to
       // prevent tinkering with OCSP, app and addon updates, etc.
       if (data.isSystemPrincipal) {
         return;
       }
 
       // Check hosts permissions for both the resource being requested,
       const hosts = context.extension.whiteListedHosts;
-      if (!hosts.matchesIgnoringPath(NetUtil.newURI(data.url))) {
+      if (!hosts.matchesIgnoringPath(Services.io.newURI(data.url))) {
         return;
       }
       // and the origin that is loading the resource.
       const origin = data.documentUrl;
       const own = origin && origin.startsWith(context.extension.getURL());
-      if (origin && !own && !hosts.matchesIgnoringPath(NetUtil.newURI(origin))) {
+      if (origin && !own && !hosts.matchesIgnoringPath(Services.io.newURI(origin))) {
         return;
       }
 
       let browserData = {tabId: -1, windowId: -1};
       if (data.browser) {
         browserData = tabTracker.getBrowserData(data.browser);
       }
       if (filter.tabId != null && browserData.tabId != filter.tabId) {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
@@ -1,25 +1,21 @@
 "use strict";
 
 const global = this;
 
 Cu.import("resource://gre/modules/Timer.jsm");
 
 Cu.import("resource://gre/modules/ExtensionCommon.jsm");
-Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   BaseContext,
+  SingletonEventManager,
 } = ExtensionCommon;
 
-var {
-  SingletonEventManager,
-} = ExtensionUtils;
-
 class StubContext extends BaseContext {
   constructor() {
     let fakeExtension = {id: "test@web.extension"};
     super("testEnv", fakeExtension);
     this.sandbox = Cu.Sandbox(global);
   }
 
   get cloneScope() {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_themes_supported_properties.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_themes_supported_properties.js
@@ -1,18 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/Schemas.jsm");
 
 const {
   validateThemeManifest,
-} = ExtensionUtils;
+} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
 const BASE_SCHEMA_URL = "chrome://extensions/content/schemas/manifest.json";
 const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas";
 
 const baseManifestProperties = [
   "manifest_version",
   "minimum_chrome_version",
   "applications",