merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 08 Aug 2017 13:18:14 +0200
changeset 373358 a921bfb8a2cf3db4d9edebe9b35799a3f9d035da
parent 373316 8c7788eda00e4cb4445638bdb7500009cd4cbe59 (current diff)
parent 373357 49af461825cb9c4c510be20ff5633851e30181a9 (diff)
child 373378 08266a98cd9b5d6d1d071d9d5c31e1e4eabaf608
child 373387 123c0a0b55e431dfdb56a600e0b9ff9e1767e250
push id32299
push usercbook@mozilla.com
push dateTue, 08 Aug 2017 11:18:41 +0000
treeherdermozilla-central@a921bfb8a2cf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone57.0a1
first release with
nightly linux32
a921bfb8a2cf / 57.0a1 / 20170808114032 / files
nightly linux64
a921bfb8a2cf / 57.0a1 / 20170808114032 / files
nightly mac
a921bfb8a2cf / 57.0a1 / 20170808114032 / files
nightly win32
a921bfb8a2cf / 57.0a1 / 20170808114032 / files
nightly win64
a921bfb8a2cf / 57.0a1 / 20170808114032 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
browser/components/extensions/ext-browserAction.js
browser/components/extensions/ext-menus.js
browser/components/extensions/ext-pageAction.js
browser/components/extensions/ext-utils.js
gfx/thebes/gfxFontconfigUtils.h
js/src/jsapi.h
js/src/jscompartment.cpp
js/src/jscompartment.h
toolkit/components/extensions/ExtensionTabs.jsm
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -230,18 +230,18 @@ if (!isDevtools) {
                       "passwords.js", "prefs.js", "tabs.js",
                       "extension-storage.js"]) {
     whitelist.add("resource://services-sync/engines/" + module);
   }
 
 }
 
 const gInterestingCategories = new Set([
-  "agent-style-sheets", "addon-provider-module", "webextension-scripts",
-  "webextension-schemas", "webextension-scripts-addon",
+  "agent-style-sheets", "addon-provider-module", "webextension-modules",
+  "webextension-scripts", "webextension-schemas", "webextension-scripts-addon",
   "webextension-scripts-content", "webextension-scripts-devtools"
 ]);
 
 var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]
                  .getService(Ci.nsIChromeRegistry);
 var gChromeMap = new Map();
 var gOverrideMap = new Map();
 var gReferencesFromCode = new Set();
@@ -498,23 +498,23 @@ add_task(async function checkAllTheFiles
   let libxul = await OS.File.read(libxulPath);
   findChromeUrlsFromArray(libxul, "chrome://");
   findChromeUrlsFromArray(libxul, "resource://");
   // Handle NS_LITERAL_STRING.
   let uint16 = new Uint16Array(libxul.buffer);
   findChromeUrlsFromArray(uint16, "chrome://");
   findChromeUrlsFromArray(uint16, "resource://");
 
-  const kCodeExtensions = [".xul", ".xml", ".xsl", ".js", ".jsm", ".html", ".xhtml"];
+  const kCodeExtensions = [".xul", ".xml", ".xsl", ".js", ".jsm", ".json", ".html", ".xhtml"];
 
   let appDir = Services.dirsvc.get("GreD", Ci.nsIFile);
   // This asynchronously produces a list of URLs (sadly, mostly sync on our
   // test infrastructure because it runs against jarfiles there, and
   // our zipreader APIs are all sync)
-  let uris = await generateURIsFromDirTree(appDir, [".css", ".manifest", ".json", ".jpg", ".png", ".gif", ".svg",  ".dtd", ".properties"].concat(kCodeExtensions));
+  let uris = await generateURIsFromDirTree(appDir, [".css", ".manifest", ".jpg", ".png", ".gif", ".svg",  ".dtd", ".properties"].concat(kCodeExtensions));
 
   // Parse and remove all manifests from the list.
   // NOTE that this must be done before filtering out devtools paths
   // so that all chrome paths can be recorded.
   let manifestPromises = [];
   uris = uris.filter(uri => {
     let path = uri.pathQueryRef;
     if (path.endsWith(".manifest")) {
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -1441,16 +1441,19 @@ var CustomizableUIInternal = {
       // By using this as the default, if a widget provides a full string rather
       // than a string ID for localization, we will fall back to that string
       // and return that.
       def = aDef || name;
     } else {
       name = aWidget.id + "." + aProp;
       def = aDef || "";
     }
+    if (aWidget.localized === false) {
+      return def;
+    }
     try {
       if (Array.isArray(aFormatArgs) && aFormatArgs.length) {
         return gWidgetsBundle.formatStringFromName(name, aFormatArgs,
           aFormatArgs.length) || def;
       }
       return gWidgetsBundle.GetStringFromName(name) || def;
     } catch (ex) {
       // If an empty string was explicitly passed, treat it as an actual
@@ -2313,16 +2316,17 @@ var CustomizableUIInternal = {
 
   // XXXunf Log some warnings here, when the data provided isn't up to scratch.
   normalizeWidget(aData, aSource) {
     let widget = {
       implementation: aData,
       source: aSource || CustomizableUI.SOURCE_EXTERNAL,
       instances: new Map(),
       currentArea: null,
+      localized: true,
       removable: true,
       overflows: true,
       defaultArea: null,
       shortcutId: null,
       tabSpecific: false,
       tooltiptext: null,
       showInPrivateBrowsing: true,
       _introducedInVersion: -1,
@@ -2348,17 +2352,18 @@ var CustomizableUIInternal = {
 
     const kOptStringProps = ["label", "tooltiptext", "shortcutId"];
     for (let prop of kOptStringProps) {
       if (typeof aData[prop] == "string") {
         widget[prop] = aData[prop];
       }
     }
 
-    const kOptBoolProps = ["removable", "showInPrivateBrowsing", "overflows", "tabSpecific"];
+    const kOptBoolProps = ["removable", "showInPrivateBrowsing", "overflows", "tabSpecific",
+                           "localized"];
     for (let prop of kOptBoolProps) {
       if (typeof aData[prop] == "boolean") {
         widget[prop] = aData[prop];
       }
     }
 
     // When we normalize builtin widgets, areas have not yet been registered:
     if (aData.defaultArea &&
@@ -3291,16 +3296,19 @@ this.CustomizableUI = {
    *                  shown may pass this method a Promise, which will
    *                  prevent the view from showing until it resolves.
    *                  Additionally, if the promise resolves to the exact
    *                  value `false`, the view will not be shown.
    * - onViewHiding(aEvt): Only useful for views; a function that will be
    *                  invoked when a user hides your view.
    * - tooltiptext:   string to use for the tooltip of the widget
    * - label:         string to use for the label of the widget
+   * - localized:     If true, or undefined, attempt to retrieve the
+   *                  widget's string properties from the customizable
+   *                  widgets string bundle.
    * - removable:     whether the widget is removable (optional, default: true)
    *                  NB: if you specify false here, you must provide a
    *                  defaultArea, too.
    * - overflows:     whether widget can overflow when in an overflowable
    *                  toolbar (optional, default: true)
    * - defaultArea:   default area to add the widget to
    *                  (optional, default: none; required if non-removable)
    * - shortcutId:    id of an element that has a shortcut for this widget
--- a/browser/components/extensions/ext-bookmarks.js
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -75,22 +75,26 @@ const convertBookmarks = result => {
     node.url = result.url.href; // Output is always URL object.
   } else {
     node.dateGroupModified = result.lastModified.getTime();
   }
 
   return node;
 };
 
-let observer = {
-  skipTags: true,
-  skipDescendantsOnItemRemoval: true,
+let observer = new class extends EventEmitter {
+  constructor() {
+    super();
 
-  onBeginUpdateBatch() {},
-  onEndUpdateBatch() {},
+    this.skipTags = true;
+    this.skipDescendantsOnItemRemoval = true;
+  }
+
+  onBeginUpdateBatch() {}
+  onEndUpdateBatch() {}
 
   onItemAdded(id, parentId, index, itemType, uri, title, dateAdded, guid, parentGuid, source) {
     if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
       return;
     }
 
     let bookmark = {
       id: guid,
@@ -102,33 +106,33 @@ let observer = {
 
     if (itemType == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
       bookmark.url = uri.spec;
     } else {
       bookmark.dateGroupModified = bookmark.dateAdded;
     }
 
     this.emit("created", bookmark);
-  },
+  }
 
-  onItemVisited() {},
+  onItemVisited() {}
 
   onItemMoved(id, oldParentId, oldIndex, newParentId, newIndex, itemType, guid, oldParentGuid, newParentGuid, source) {
     if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
       return;
     }
 
     let info = {
       parentId: newParentGuid,
       index: newIndex,
       oldParentId: oldParentGuid,
       oldIndex,
     };
     this.emit("moved", {guid, info});
-  },
+  }
 
   onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid, source) {
     if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
       return;
     }
 
     let node = {
       id: guid,
@@ -136,17 +140,17 @@ let observer = {
       index,
     };
 
     if (itemType == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
       node.url = uri.spec;
     }
 
     this.emit("removed", {guid, info: {parentId: parentGuid, index, node}});
-  },
+  }
 
   onItemChanged(id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid, oldVal, source) {
     if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
       return;
     }
 
     let info = {};
     if (prop == "title") {
@@ -154,19 +158,18 @@ let observer = {
     } else if (prop == "uri") {
       info.url = val;
     } else {
       // Not defined yet.
       return;
     }
 
     this.emit("changed", {guid, info});
-  },
-};
-EventEmitter.decorate(observer);
+  }
+}();
 
 const decrementListeners = () => {
   listenerCount -= 1;
   if (!listenerCount) {
     PlacesUtils.bookmarks.removeObserver(observer);
   }
 };
 
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.js
@@ -1,15 +1,32 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-// The ext-* files are imported into the same scopes.
-/* import-globals-from ext-utils.js */
+// This file provides some useful code for the |tabs| and |windows|
+// modules. All of the code is installed on |global|, which is a scope
+// shared among the different ext-*.js scripts.
+
+/* global EventEmitter:false, TabContext:false, WindowEventManager:false,
+          makeWidgetId:false, tabTracker:true, windowTracker:true */
+/* import-globals-from ../../../toolkit/components/extensions/ext-toolkit.js */
+
+/* globals TabBase, WindowBase, TabTrackerBase, WindowTrackerBase, TabManagerBase, WindowManagerBase */
 
-XPCOMUtils.defineLazyModuleGetter(global, "EventEmitter",
-                                  "resource://gre/modules/EventEmitter.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+var {
+  ExtensionError,
+  defineLazyGetter,
+} = ExtensionUtils;
+
+let tabTracker;
+let windowTracker;
 
 // This function is pretty tightly tied to Extension.jsm.
 // Its job is to fill in the |tab| property of the sender.
 const 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
@@ -71,169 +88,801 @@ global.openOptionsPage = (extension) => 
     return Promise.resolve();
   }
 
   let viewId = `addons://detail/${encodeURIComponent(extension.id)}/preferences`;
 
   return window.BrowserOpenAddonsMgr(viewId);
 };
 
-extensions.registerModules({
-  bookmarks: {
-    url: "chrome://browser/content/ext-bookmarks.js",
-    schema: "chrome://browser/content/schemas/bookmarks.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["bookmarks"],
-    ],
-  },
-  browserAction: {
-    url: "chrome://browser/content/ext-browserAction.js",
-    schema: "chrome://browser/content/schemas/browser_action.json",
-    scopes: ["addon_parent"],
-    manifest: ["browser_action"],
-    paths: [
-      ["browserAction"],
-    ],
-  },
-  browsingData: {
-    url: "chrome://browser/content/ext-browsingData.js",
-    schema: "chrome://browser/content/schemas/browsing_data.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["browsingData"],
-    ],
-  },
-  chrome_settings_overrides: {
-    url: "chrome://browser/content/ext-chrome-settings-overrides.js",
-    scopes: [],
-    schema: "chrome://browser/content/schemas/chrome_settings_overrides.json",
-    manifest: ["chrome_settings_overrides"],
-  },
-  commands: {
-    url: "chrome://browser/content/ext-commands.js",
-    schema: "chrome://browser/content/schemas/commands.json",
-    scopes: ["addon_parent"],
-    manifest: ["commands"],
-    paths: [
-      ["commands"],
-    ],
-  },
-  devtools: {
-    url: "chrome://browser/content/ext-devtools.js",
-    schema: "chrome://browser/content/schemas/devtools.json",
-    scopes: ["devtools_parent"],
-    manifest: ["devtools_page"],
-    paths: [
-      ["devtools"],
-    ],
-  },
-  devtools_inspectedWindow: {
-    url: "chrome://browser/content/ext-devtools-inspectedWindow.js",
-    schema: "chrome://browser/content/schemas/devtools_inspected_window.json",
-    scopes: ["devtools_parent"],
-    paths: [
-      ["devtools", "inspectedWindow"],
-    ],
-  },
-  devtools_network: {
-    url: "chrome://browser/content/ext-devtools-network.js",
-    schema: "chrome://browser/content/schemas/devtools_network.json",
-    scopes: ["devtools_parent"],
-    paths: [
-      ["devtools", "network"],
-    ],
-  },
-  devtools_panels: {
-    url: "chrome://browser/content/ext-devtools-panels.js",
-    schema: "chrome://browser/content/schemas/devtools_panels.json",
-    scopes: ["devtools_parent"],
-    paths: [
-      ["devtools", "panels"],
-    ],
-  },
-  history: {
-    url: "chrome://browser/content/ext-history.js",
-    schema: "chrome://browser/content/schemas/history.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["history"],
-    ],
-  },
-  // This module supports the "menus" and "contextMenus" namespaces,
-  // and because of permissions, the module name must differ from both.
-  menusInternal: {
-    url: "chrome://browser/content/ext-menus.js",
-    schema: "chrome://browser/content/schemas/menus.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["menusInternal"],
-    ],
-  },
-  omnibox: {
-    url: "chrome://browser/content/ext-omnibox.js",
-    schema: "chrome://browser/content/schemas/omnibox.json",
-    scopes: ["addon_parent"],
-    manifest: ["omnibox"],
-    paths: [
-      ["omnibox"],
-    ],
-  },
-  pageAction: {
-    url: "chrome://browser/content/ext-pageAction.js",
-    schema: "chrome://browser/content/schemas/page_action.json",
-    scopes: ["addon_parent"],
-    manifest: ["page_action"],
-    paths: [
-      ["pageAction"],
-    ],
-  },
-  geckoProfiler: {
-    url: "chrome://browser/content/ext-geckoProfiler.js",
-    schema: "chrome://browser/content/schemas/geckoProfiler.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["geckoProfiler"],
-    ],
-  },
-  sessions: {
-    url: "chrome://browser/content/ext-sessions.js",
-    schema: "chrome://browser/content/schemas/sessions.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["sessions"],
-    ],
-  },
-  sidebarAction: {
-    url: "chrome://browser/content/ext-sidebarAction.js",
-    schema: "chrome://browser/content/schemas/sidebar_action.json",
-    scopes: ["addon_parent"],
-    manifest: ["sidebar_action"],
-    paths: [
-      ["sidebarAction"],
-    ],
-  },
-  tabs: {
-    url: "chrome://browser/content/ext-tabs.js",
-    schema: "chrome://browser/content/schemas/tabs.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["tabs"],
-    ],
-  },
-  urlOverrides: {
-    url: "chrome://browser/content/ext-url-overrides.js",
-    schema: "chrome://browser/content/schemas/url_overrides.json",
-    scopes: ["addon_parent"],
-    manifest: ["chrome_url_overrides"],
-    paths: [
-      ["urlOverrides"],
-    ],
-  },
-  windows: {
-    url: "chrome://browser/content/ext-windows.js",
-    schema: "chrome://browser/content/schemas/windows.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["windows"],
-    ],
-  },
+global.makeWidgetId = id => {
+  id = id.toLowerCase();
+  // FIXME: This allows for collisions.
+  return id.replace(/[^a-z0-9_-]/g, "_");
+};
+
+// Manages tab-specific context data, and dispatching tab select events
+// across all windows.
+global.TabContext = class extends EventEmitter {
+  constructor(getDefaults, extension) {
+    super();
+
+    this.extension = extension;
+    this.getDefaults = getDefaults;
+
+    this.tabData = new WeakMap();
+    this.lastLocation = new WeakMap();
+
+    windowTracker.addListener("progress", this);
+    windowTracker.addListener("TabSelect", this);
+  }
+
+  get(nativeTab) {
+    if (!this.tabData.has(nativeTab)) {
+      this.tabData.set(nativeTab, this.getDefaults(nativeTab));
+    }
+
+    return this.tabData.get(nativeTab);
+  }
+
+  clear(nativeTab) {
+    this.tabData.delete(nativeTab);
+  }
+
+  handleEvent(event) {
+    if (event.type == "TabSelect") {
+      let nativeTab = event.target;
+      this.emit("tab-select", nativeTab);
+      this.emit("location-change", nativeTab);
+    }
+  }
+
+  onStateChange(browser, webProgress, request, stateFlags, statusCode) {
+    let flags = Ci.nsIWebProgressListener;
+
+    if (!(~stateFlags & (flags.STATE_IS_WINDOW | flags.STATE_START) ||
+          this.lastLocation.has(browser))) {
+      this.lastLocation.set(browser, request.URI);
+    }
+  }
+
+  onLocationChange(browser, webProgress, request, locationURI, flags) {
+    let gBrowser = browser.ownerGlobal.gBrowser;
+    let lastLocation = this.lastLocation.get(browser);
+    if (browser === gBrowser.selectedBrowser &&
+        !(lastLocation && lastLocation.equalsExceptRef(browser.currentURI))) {
+      let nativeTab = gBrowser.getTabForBrowser(browser);
+      this.emit("location-change", nativeTab, true);
+    }
+    this.lastLocation.set(browser, browser.currentURI);
+  }
+
+  shutdown() {
+    windowTracker.removeListener("progress", this);
+    windowTracker.removeListener("TabSelect", this);
+  }
+};
+
+
+class WindowTracker extends WindowTrackerBase {
+  addProgressListener(window, listener) {
+    window.gBrowser.addTabsProgressListener(listener);
+  }
+
+  removeProgressListener(window, listener) {
+    window.gBrowser.removeTabsProgressListener(listener);
+  }
+}
+
+/**
+ * An event manager API provider which listens for a DOM event in any browser
+ * window, and calls the given listener function whenever an event is received.
+ * That listener function receives a `fire` object, which it can use to dispatch
+ * events to the extension, and a DOM event object.
+ *
+ * @param {BaseContext} context
+ *        The extension context which the event manager belongs to.
+ * @param {string} name
+ *        The API name of the event manager, e.g.,"runtime.onMessage".
+ * @param {string} event
+ *        The name of the DOM event to listen for.
+ * @param {function} listener
+ *        The listener function to call when a DOM event is received.
+ */
+global.WindowEventManager = class extends EventManager {
+  constructor(context, name, event, listener) {
+    super(context, name, fire => {
+      let listener2 = listener.bind(null, fire);
+
+      windowTracker.addListener(event, listener2);
+      return () => {
+        windowTracker.removeListener(event, listener2);
+      };
+    });
+  }
+};
+
+class TabTracker extends TabTrackerBase {
+  constructor() {
+    super();
+
+    this._tabs = new WeakMap();
+    this._tabIds = new Map();
+    this._nextId = 1;
+
+    this._handleTabDestroyed = this._handleTabDestroyed.bind(this);
+  }
+
+  init() {
+    if (this.initialized) {
+      return;
+    }
+    this.initialized = true;
+
+    this.adoptedTabs = new WeakMap();
+
+    this._handleWindowOpen = this._handleWindowOpen.bind(this);
+    this._handleWindowClose = this._handleWindowClose.bind(this);
+
+    windowTracker.addListener("TabClose", this);
+    windowTracker.addListener("TabOpen", this);
+    windowTracker.addListener("TabSelect", this);
+    windowTracker.addOpenListener(this._handleWindowOpen);
+    windowTracker.addCloseListener(this._handleWindowClose);
+
+    /* eslint-disable mozilla/balanced-listeners */
+    this.on("tab-detached", this._handleTabDestroyed);
+    this.on("tab-removed", this._handleTabDestroyed);
+    /* eslint-enable mozilla/balanced-listeners */
+  }
+
+  getId(nativeTab) {
+    if (this._tabs.has(nativeTab)) {
+      return this._tabs.get(nativeTab);
+    }
+
+    this.init();
+
+    let id = this._nextId++;
+    this.setId(nativeTab, id);
+    return id;
+  }
+
+  setId(nativeTab, id) {
+    this._tabs.set(nativeTab, id);
+    this._tabIds.set(id, nativeTab);
+  }
+
+  _handleTabDestroyed(event, {nativeTab}) {
+    let id = this._tabs.get(nativeTab);
+    if (id) {
+      this._tabs.delete(nativeTab);
+      if (this._tabIds.get(id) === nativeTab) {
+        this._tabIds.delete(id);
+      }
+    }
+  }
+
+  /**
+   * Returns the XUL <tab> element associated with the given tab ID. If no tab
+   * with the given ID exists, and no default value is provided, an error is
+   * raised, belonging to the scope of the given context.
+   *
+   * @param {integer} tabId
+   *        The ID of the tab to retrieve.
+   * @param {*} default_
+   *        The value to return if no tab exists with the given ID.
+   * @returns {Element<tab>}
+   *        A XUL <tab> element.
+   */
+  getTab(tabId, default_ = undefined) {
+    let nativeTab = this._tabIds.get(tabId);
+    if (nativeTab) {
+      return nativeTab;
+    }
+    if (default_ !== undefined) {
+      return default_;
+    }
+    throw new ExtensionError(`Invalid tab ID: ${tabId}`);
+  }
+
+  /**
+   * @param {Event} event
+   *        The DOM Event to handle.
+   * @private
+   */
+  handleEvent(event) {
+    let nativeTab = event.target;
+
+    switch (event.type) {
+      case "TabOpen":
+        let {adoptedTab} = event.detail;
+        if (adoptedTab) {
+          this.adoptedTabs.set(adoptedTab, event.target);
+
+          // This tab is being created to adopt a tab from a different window.
+          // Copy the ID from the old tab to the new.
+          this.setId(nativeTab, this.getId(adoptedTab));
+
+          adoptedTab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetFrameData", {
+            windowId: windowTracker.getId(nativeTab.ownerGlobal),
+          });
+        }
+
+        // Save the current tab, since the newly-created tab will likely be
+        // active by the time the promise below resolves and the event is
+        // dispatched.
+        let currentTab = nativeTab.ownerGlobal.gBrowser.selectedTab;
+
+        // We need to delay sending this event until the next tick, since the
+        // tab does not have its final index when the TabOpen event is dispatched.
+        Promise.resolve().then(() => {
+          if (event.detail.adoptedTab) {
+            this.emitAttached(event.originalTarget);
+          } else {
+            this.emitCreated(event.originalTarget, currentTab);
+          }
+        });
+        break;
+
+      case "TabClose":
+        let {adoptedBy} = event.detail;
+        if (adoptedBy) {
+          // This tab is being closed because it was adopted by a new window.
+          // Copy its ID to the new tab, in case it was created as the first tab
+          // of a new window, and did not have an `adoptedTab` detail when it was
+          // opened.
+          this.setId(adoptedBy, this.getId(nativeTab));
+
+          this.emitDetached(nativeTab, adoptedBy);
+        } else {
+          this.emitRemoved(nativeTab, false);
+        }
+        break;
+
+      case "TabSelect":
+        // Because we are delaying calling emitCreated above, we also need to
+        // delay sending this event because it shouldn't fire before onCreated.
+        Promise.resolve().then(() => {
+          this.emitActivated(nativeTab);
+        });
+        break;
+    }
+  }
+
+  /**
+   * A private method which is called whenever a new browser window is opened,
+   * and dispatches the necessary events for it.
+   *
+   * @param {DOMWindow} window
+   *        The window being opened.
+   * @private
+   */
+  _handleWindowOpen(window) {
+    if (window.arguments && window.arguments[0] instanceof window.XULElement) {
+      // If the first window argument is a XUL element, it means the
+      // window is about to adopt a tab from another window to replace its
+      // initial tab.
+      //
+      // Note that this event handler depends on running before the
+      // delayed startup code in browser.js, which is currently triggered
+      // by the first MozAfterPaint event. That code handles finally
+      // adopting the tab, and clears it from the arguments list in the
+      // process, so if we run later than it, we're too late.
+      let nativeTab = window.arguments[0];
+      let adoptedBy = window.gBrowser.tabs[0];
+
+      this.adoptedTabs.set(nativeTab, adoptedBy);
+      this.setId(adoptedBy, this.getId(nativeTab));
+
+      // We need to be sure to fire this event after the onDetached event
+      // for the original tab.
+      let listener = (event, details) => {
+        if (details.nativeTab === nativeTab) {
+          this.off("tab-detached", listener);
+
+          Promise.resolve().then(() => {
+            this.emitAttached(details.adoptedBy);
+          });
+        }
+      };
+
+      this.on("tab-detached", listener);
+    } else {
+      for (let nativeTab of window.gBrowser.tabs) {
+        this.emitCreated(nativeTab);
+      }
+
+      // emitActivated to trigger tab.onActivated/tab.onHighlighted for a newly opened window.
+      this.emitActivated(window.gBrowser.tabs[0]);
+    }
+  }
+
+  /**
+   * A private method which is called whenever a browser window is closed,
+   * and dispatches the necessary events for it.
+   *
+   * @param {DOMWindow} window
+   *        The window being closed.
+   * @private
+   */
+  _handleWindowClose(window) {
+    for (let nativeTab of window.gBrowser.tabs) {
+      if (this.adoptedTabs.has(nativeTab)) {
+        this.emitDetached(nativeTab, this.adoptedTabs.get(nativeTab));
+      } else {
+        this.emitRemoved(nativeTab, true);
+      }
+    }
+  }
+
+  /**
+   * Emits a "tab-activated" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element which has been activated.
+   * @private
+   */
+  emitActivated(nativeTab) {
+    this.emit("tab-activated", {
+      tabId: this.getId(nativeTab),
+      windowId: windowTracker.getId(nativeTab.ownerGlobal)});
+  }
+
+  /**
+   * Emits a "tab-attached" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element in the window to which the tab is being attached.
+   * @private
+   */
+  emitAttached(nativeTab) {
+    let newWindowId = windowTracker.getId(nativeTab.ownerGlobal);
+    let tabId = this.getId(nativeTab);
+
+    this.emit("tab-attached", {nativeTab, tabId, newWindowId, newPosition: nativeTab._tPos});
+  }
+
+  /**
+   * Emits a "tab-detached" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element in the window from which the tab is being detached.
+   * @param {NativeTab} adoptedBy
+   *        The tab element in the window to which detached tab is being moved,
+   *        and will adopt this tab's contents.
+   * @private
+   */
+  emitDetached(nativeTab, adoptedBy) {
+    let oldWindowId = windowTracker.getId(nativeTab.ownerGlobal);
+    let tabId = this.getId(nativeTab);
+
+    this.emit("tab-detached", {nativeTab, adoptedBy, tabId, oldWindowId, oldPosition: nativeTab._tPos});
+  }
+
+  /**
+   * Emits a "tab-created" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element which is being created.
+   * @param {NativeTab} [currentTab]
+   *        The tab element for the currently active tab.
+   * @private
+   */
+  emitCreated(nativeTab, currentTab) {
+    this.emit("tab-created", {nativeTab, currentTab});
+  }
+
+  /**
+   * Emits a "tab-removed" event for the given tab element.
+   *
+   * @param {NativeTab} nativeTab
+   *        The tab element which is being removed.
+   * @param {boolean} isWindowClosing
+   *        True if the tab is being removed because the browser window is
+   *        closing.
+   * @private
+   */
+  emitRemoved(nativeTab, isWindowClosing) {
+    let windowId = windowTracker.getId(nativeTab.ownerGlobal);
+    let tabId = this.getId(nativeTab);
+
+    // When addons run in-process, `window.close()` is synchronous. Most other
+    // addon-invoked calls are asynchronous since they go through a proxy
+    // context via the message manager. This includes event registrations such
+    // as `tabs.onRemoved.addListener`.
+    //
+    // So, even if `window.close()` were to be called (in-process) after calling
+    // `tabs.onRemoved.addListener`, then the tab would be closed before the
+    // event listener is registered. To make sure that the event listener is
+    // notified, we dispatch `tabs.onRemoved` asynchronously.
+    Services.tm.dispatchToMainThread(() => {
+      this.emit("tab-removed", {nativeTab, tabId, windowId, isWindowClosing});
+    });
+  }
+
+  getBrowserData(browser) {
+    if (browser.ownerGlobal.location.href === "about:addons") {
+      // When we're loaded into a <browser> inside about:addons, we need to go up
+      // one more level.
+      browser = browser.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIDocShell)
+                       .chromeEventHandler;
+    }
+
+    let result = {
+      tabId: -1,
+      windowId: -1,
+    };
+
+    let {gBrowser} = browser.ownerGlobal;
+    // Some non-browser windows have gBrowser but not
+    // getTabForBrowser!
+    if (gBrowser && gBrowser.getTabForBrowser) {
+      result.windowId = windowTracker.getId(browser.ownerGlobal);
+
+      let nativeTab = gBrowser.getTabForBrowser(browser);
+      if (nativeTab) {
+        result.tabId = this.getId(nativeTab);
+      }
+    }
+
+    return result;
+  }
+
+  get activeTab() {
+    let window = windowTracker.topWindow;
+    if (window && window.gBrowser) {
+      return window.gBrowser.selectedTab;
+    }
+    return null;
+  }
+}
+
+windowTracker = new WindowTracker();
+tabTracker = new TabTracker();
+
+Object.assign(global, {tabTracker, windowTracker});
+
+class Tab extends TabBase {
+  get _favIconUrl() {
+    return this.window.gBrowser.getIcon(this.nativeTab);
+  }
+
+  get audible() {
+    return this.nativeTab.soundPlaying;
+  }
+
+  get browser() {
+    return this.nativeTab.linkedBrowser;
+  }
+
+  get frameLoader() {
+    // If we don't have a frameLoader yet, just return a dummy with no width and
+    // height.
+    return super.frameLoader || {lazyWidth: 0, lazyHeight: 0};
+  }
+
+  get cookieStoreId() {
+    return getCookieStoreIdForTab(this, this.nativeTab);
+  }
+
+  get height() {
+    return this.frameLoader.lazyHeight;
+  }
+
+  get index() {
+    return this.nativeTab._tPos;
+  }
+
+  get mutedInfo() {
+    let {nativeTab} = this;
+
+    let mutedInfo = {muted: nativeTab.muted};
+    if (nativeTab.muteReason === null) {
+      mutedInfo.reason = "user";
+    } else if (nativeTab.muteReason) {
+      mutedInfo.reason = "extension";
+      mutedInfo.extensionId = nativeTab.muteReason;
+    }
+
+    return mutedInfo;
+  }
+
+  get lastAccessed() {
+    return this.nativeTab.lastAccessed;
+  }
+
+  get pinned() {
+    return this.nativeTab.pinned;
+  }
+
+  get active() {
+    return this.nativeTab.selected;
+  }
+
+  get selected() {
+    return this.nativeTab.selected;
+  }
+
+  get status() {
+    if (this.nativeTab.getAttribute("busy") === "true") {
+      return "loading";
+    }
+    return "complete";
+  }
+
+  get width() {
+    return this.frameLoader.lazyWidth;
+  }
+
+  get window() {
+    return this.nativeTab.ownerGlobal;
+  }
+
+  get windowId() {
+    return windowTracker.getId(this.window);
+  }
+
+  /**
+   * Converts session store data to an object compatible with the return value
+   * of the convert() method, representing that data.
+   *
+   * @param {Extension} extension
+   *        The extension for which to convert the data.
+   * @param {Object} tabData
+   *        Session store data for a closed tab, as returned by
+   *        `SessionStore.getClosedTabData()`.
+   * @param {DOMWindow} [window = null]
+   *        The browser window which the tab belonged to before it was closed.
+   *        May be null if the window the tab belonged to no longer exists.
+   *
+   * @returns {Object}
+   * @static
+   */
+  static convertFromSessionStoreClosedData(extension, tabData, window = null) {
+    let result = {
+      sessionId: String(tabData.closedId),
+      index: tabData.pos ? tabData.pos : 0,
+      windowId: window && windowTracker.getId(window),
+      highlighted: false,
+      active: false,
+      pinned: false,
+      incognito: Boolean(tabData.state && tabData.state.isPrivate),
+      lastAccessed: tabData.state ? tabData.state.lastAccessed : tabData.lastAccessed,
+    };
+
+    if (extension.tabManager.hasTabPermission(tabData)) {
+      let entries = tabData.state ? tabData.state.entries : tabData.entries;
+      let entry = entries[entries.length - 1];
+      result.url = entry.url;
+      result.title = entry.title;
+      if (tabData.image) {
+        result.favIconUrl = tabData.image;
+      }
+    }
+
+    return result;
+  }
+}
+
+class Window extends WindowBase {
+  /**
+   * Update the geometry of the browser window.
+   *
+   * @param {Object} options
+   *        An object containing new values for the window's geometry.
+   * @param {integer} [options.left]
+   *        The new pixel distance of the left side of the browser window from
+   *        the left of the screen.
+   * @param {integer} [options.top]
+   *        The new pixel distance of the top side of the browser window from
+   *        the top of the screen.
+   * @param {integer} [options.width]
+   *        The new pixel width of the window.
+   * @param {integer} [options.height]
+   *        The new pixel height of the window.
+   */
+  updateGeometry(options) {
+    let {window} = this;
+
+    if (options.left !== null || options.top !== null) {
+      let left = options.left !== null ? options.left : window.screenX;
+      let top = options.top !== null ? options.top : window.screenY;
+      window.moveTo(left, top);
+    }
+
+    if (options.width !== null || options.height !== null) {
+      let width = options.width !== null ? options.width : window.outerWidth;
+      let height = options.height !== null ? options.height : window.outerHeight;
+      window.resizeTo(width, height);
+    }
+  }
+
+  get title() {
+    return this.window.document.title;
+  }
+
+  setTitlePreface(titlePreface) {
+    this.window.document.documentElement.setAttribute("titlepreface", titlePreface);
+  }
+
+  get focused() {
+    return this.window.document.hasFocus();
+  }
+
+  get top() {
+    return this.window.screenY;
+  }
+
+  get left() {
+    return this.window.screenX;
+  }
+
+  get width() {
+    return this.window.outerWidth;
+  }
+
+  get height() {
+    return this.window.outerHeight;
+  }
+
+  get incognito() {
+    return PrivateBrowsingUtils.isWindowPrivate(this.window);
+  }
+
+  get alwaysOnTop() {
+    return this.xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ;
+  }
+
+  get isLastFocused() {
+    return this.window === windowTracker.topWindow;
+  }
+
+  static getState(window) {
+    const STATES = {
+      [window.STATE_MAXIMIZED]: "maximized",
+      [window.STATE_MINIMIZED]: "minimized",
+      [window.STATE_NORMAL]: "normal",
+    };
+    let state = STATES[window.windowState];
+    if (window.fullScreen) {
+      state = "fullscreen";
+    }
+    return state;
+  }
+
+  get state() {
+    return Window.getState(this.window);
+  }
+
+  set state(state) {
+    let {window} = this;
+    if (state !== "fullscreen" && window.fullScreen) {
+      window.fullScreen = false;
+    }
+
+    switch (state) {
+      case "maximized":
+        window.maximize();
+        break;
+
+      case "minimized":
+      case "docked":
+        window.minimize();
+        break;
+
+      case "normal":
+        // Restore sometimes returns the window to its previous state, rather
+        // than to the "normal" state, so it may need to be called anywhere from
+        // zero to two times.
+        window.restore();
+        if (window.windowState !== window.STATE_NORMAL) {
+          window.restore();
+        }
+        if (window.windowState !== window.STATE_NORMAL) {
+          // And on OS-X, where normal vs. maximized is basically a heuristic,
+          // we need to cheat.
+          window.sizeToContent();
+        }
+        break;
+
+      case "fullscreen":
+        window.fullScreen = true;
+        break;
+
+      default:
+        throw new Error(`Unexpected window state: ${state}`);
+    }
+  }
+
+  * getTabs() {
+    let {tabManager} = this.extension;
+
+    for (let nativeTab of this.window.gBrowser.tabs) {
+      yield tabManager.getWrapper(nativeTab);
+    }
+  }
+
+  /**
+   * Converts session store data to an object compatible with the return value
+   * of the convert() method, representing that data.
+   *
+   * @param {Extension} extension
+   *        The extension for which to convert the data.
+   * @param {Object} windowData
+   *        Session store data for a closed window, as returned by
+   *        `SessionStore.getClosedWindowData()`.
+   *
+   * @returns {Object}
+   * @static
+   */
+  static convertFromSessionStoreClosedData(extension, windowData) {
+    let result = {
+      sessionId: String(windowData.closedId),
+      focused: false,
+      incognito: false,
+      type: "normal", // this is always "normal" for a closed window
+      // Surely this does not actually work?
+      state: this.getState(windowData),
+      alwaysOnTop: false,
+    };
+
+    if (windowData.tabs.length) {
+      result.tabs = windowData.tabs.map(tabData => {
+        return Tab.convertFromSessionStoreClosedData(extension, tabData);
+      });
+    }
+
+    return result;
+  }
+}
+
+Object.assign(global, {Tab, Window});
+
+class TabManager extends TabManagerBase {
+  get(tabId, default_ = undefined) {
+    let nativeTab = tabTracker.getTab(tabId, default_);
+
+    if (nativeTab) {
+      return this.getWrapper(nativeTab);
+    }
+    return default_;
+  }
+
+  addActiveTabPermission(nativeTab = tabTracker.activeTab) {
+    return super.addActiveTabPermission(nativeTab);
+  }
+
+  revokeActiveTabPermission(nativeTab = tabTracker.activeTab) {
+    return super.revokeActiveTabPermission(nativeTab);
+  }
+
+  wrapTab(nativeTab) {
+    return new Tab(this.extension, nativeTab, tabTracker.getId(nativeTab));
+  }
+}
+
+class WindowManager extends WindowManagerBase {
+  get(windowId, context) {
+    let window = windowTracker.getWindow(windowId, context);
+
+    return this.getWrapper(window);
+  }
+
+  * getAll() {
+    for (let window of windowTracker.browserWindows()) {
+      yield this.getWrapper(window);
+    }
+  }
+
+  wrapWindow(window) {
+    return new Window(this.extension, window, windowTracker.getId(window));
+  }
+}
+
+
+extensions.on("startup", (type, extension) => { // eslint-disable-line mozilla/balanced-listeners
+  defineLazyGetter(extension, "tabManager",
+                   () => new TabManager(extension));
+  defineLazyGetter(extension, "windowManager",
+                   () => new WindowManager(extension));
 });
copy from browser/components/extensions/ext-browser.js
copy to browser/components/extensions/ext-browser.json
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.json
@@ -1,239 +1,159 @@
-"use strict";
-
-// The ext-* files are imported into the same scopes.
-/* import-globals-from ext-utils.js */
-
-XPCOMUtils.defineLazyModuleGetter(global, "EventEmitter",
-                                  "resource://gre/modules/EventEmitter.jsm");
-
-// This function is pretty tightly tied to Extension.jsm.
-// Its job is to fill in the |tab| property of the sender.
-const 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).
-    tabId = sender.tabId;
-    delete sender.tabId;
-  } else if (target instanceof Ci.nsIDOMXULElement) {
-    tabId = tabTracker.getBrowserData(target).tabId;
-  }
-
-  if (tabId) {
-    let tab = extension.tabManager.get(tabId, null);
-    if (tab) {
-      sender.tab = tab.convert();
-    }
-  }
-};
-
-// Used by Extension.jsm
-global.tabGetSender = getSender;
-
-/* eslint-disable mozilla/balanced-listeners */
-extensions.on("uninstall", (msg, extension) => {
-  if (extension.uninstallURL) {
-    let browser = windowTracker.topWindow.gBrowser;
-    browser.addTab(extension.uninstallURL, {relatedToCurrent: true});
-  }
-});
-
-extensions.on("page-shutdown", (type, context) => {
-  if (context.viewType == "tab") {
-    if (context.extension.id !== context.xulBrowser.contentPrincipal.addonId) {
-      // Only close extension tabs.
-      // This check prevents about:addons from closing when it contains a
-      // WebExtension as an embedded inline options page.
-      return;
-    }
-    let {gBrowser} = context.xulBrowser.ownerGlobal;
-    if (gBrowser) {
-      let nativeTab = gBrowser.getTabForBrowser(context.xulBrowser);
-      if (nativeTab) {
-        gBrowser.removeTab(nativeTab);
-      }
-    }
-  }
-});
-/* eslint-enable mozilla/balanced-listeners */
-
-global.openOptionsPage = (extension) => {
-  let window = windowTracker.topWindow;
-  if (!window) {
-    return Promise.reject({message: "No browser window available"});
-  }
-
-  if (extension.manifest.options_ui.open_in_tab) {
-    window.switchToTabHavingURI(extension.manifest.options_ui.page, true, {
-      triggeringPrincipal: extension.principal,
-    });
-    return Promise.resolve();
-  }
-
-  let viewId = `addons://detail/${encodeURIComponent(extension.id)}/preferences`;
-
-  return window.BrowserOpenAddonsMgr(viewId);
-};
-
-extensions.registerModules({
-  bookmarks: {
-    url: "chrome://browser/content/ext-bookmarks.js",
-    schema: "chrome://browser/content/schemas/bookmarks.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["bookmarks"],
-    ],
+{
+  "bookmarks": {
+    "url": "chrome://browser/content/ext-bookmarks.js",
+    "schema": "chrome://browser/content/schemas/bookmarks.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["bookmarks"]
+    ]
+  },
+  "browserAction": {
+    "url": "chrome://browser/content/ext-browserAction.js",
+    "schema": "chrome://browser/content/schemas/browser_action.json",
+    "scopes": ["addon_parent"],
+    "manifest": ["browser_action"],
+    "paths": [
+      ["browserAction"]
+    ]
+  },
+  "browsingData": {
+    "url": "chrome://browser/content/ext-browsingData.js",
+    "schema": "chrome://browser/content/schemas/browsing_data.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["browsingData"]
+    ]
+  },
+  "chrome_settings_overrides": {
+    "url": "chrome://browser/content/ext-chrome-settings-overrides.js",
+    "scopes": [],
+    "schema": "chrome://browser/content/schemas/chrome_settings_overrides.json",
+    "manifest": ["chrome_settings_overrides"]
   },
-  browserAction: {
-    url: "chrome://browser/content/ext-browserAction.js",
-    schema: "chrome://browser/content/schemas/browser_action.json",
-    scopes: ["addon_parent"],
-    manifest: ["browser_action"],
-    paths: [
-      ["browserAction"],
-    ],
+  "commands": {
+    "url": "chrome://browser/content/ext-commands.js",
+    "schema": "chrome://browser/content/schemas/commands.json",
+    "scopes": ["addon_parent"],
+    "manifest": ["commands"],
+    "paths": [
+      ["commands"]
+    ]
+  },
+  "devtools": {
+    "url": "chrome://browser/content/ext-devtools.js",
+    "schema": "chrome://browser/content/schemas/devtools.json",
+    "scopes": ["devtools_parent"],
+    "manifest": ["devtools_page"],
+    "paths": [
+      ["devtools"]
+    ]
   },
-  browsingData: {
-    url: "chrome://browser/content/ext-browsingData.js",
-    schema: "chrome://browser/content/schemas/browsing_data.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["browsingData"],
-    ],
+  "devtools_inspectedWindow": {
+    "url": "chrome://browser/content/ext-devtools-inspectedWindow.js",
+    "schema": "chrome://browser/content/schemas/devtools_inspected_window.json",
+    "scopes": ["devtools_parent"],
+    "paths": [
+      ["devtools", "inspectedWindow"]
+    ]
   },
-  chrome_settings_overrides: {
-    url: "chrome://browser/content/ext-chrome-settings-overrides.js",
-    scopes: [],
-    schema: "chrome://browser/content/schemas/chrome_settings_overrides.json",
-    manifest: ["chrome_settings_overrides"],
+  "devtools_network": {
+    "url": "chrome://browser/content/ext-devtools-network.js",
+    "schema": "chrome://browser/content/schemas/devtools_network.json",
+    "scopes": ["devtools_parent"],
+    "paths": [
+      ["devtools", "network"]
+    ]
   },
-  commands: {
-    url: "chrome://browser/content/ext-commands.js",
-    schema: "chrome://browser/content/schemas/commands.json",
-    scopes: ["addon_parent"],
-    manifest: ["commands"],
-    paths: [
-      ["commands"],
-    ],
+  "devtools_panels": {
+    "url": "chrome://browser/content/ext-devtools-panels.js",
+    "schema": "chrome://browser/content/schemas/devtools_panels.json",
+    "scopes": ["devtools_parent"],
+    "paths": [
+      ["devtools", "panels"]
+    ]
   },
-  devtools: {
-    url: "chrome://browser/content/ext-devtools.js",
-    schema: "chrome://browser/content/schemas/devtools.json",
-    scopes: ["devtools_parent"],
-    manifest: ["devtools_page"],
-    paths: [
-      ["devtools"],
-    ],
+  "history": {
+    "url": "chrome://browser/content/ext-history.js",
+    "schema": "chrome://browser/content/schemas/history.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["history"]
+    ]
   },
-  devtools_inspectedWindow: {
-    url: "chrome://browser/content/ext-devtools-inspectedWindow.js",
-    schema: "chrome://browser/content/schemas/devtools_inspected_window.json",
-    scopes: ["devtools_parent"],
-    paths: [
-      ["devtools", "inspectedWindow"],
-    ],
-  },
-  devtools_network: {
-    url: "chrome://browser/content/ext-devtools-network.js",
-    schema: "chrome://browser/content/schemas/devtools_network.json",
-    scopes: ["devtools_parent"],
-    paths: [
-      ["devtools", "network"],
-    ],
+  "menusInternal": {
+    "url": "chrome://browser/content/ext-menus.js",
+    "schema": "chrome://browser/content/schemas/menus.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["menusInternal"]
+    ]
   },
-  devtools_panels: {
-    url: "chrome://browser/content/ext-devtools-panels.js",
-    schema: "chrome://browser/content/schemas/devtools_panels.json",
-    scopes: ["devtools_parent"],
-    paths: [
-      ["devtools", "panels"],
-    ],
-  },
-  history: {
-    url: "chrome://browser/content/ext-history.js",
-    schema: "chrome://browser/content/schemas/history.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["history"],
-    ],
+  "omnibox": {
+    "url": "chrome://browser/content/ext-omnibox.js",
+    "schema": "chrome://browser/content/schemas/omnibox.json",
+    "scopes": ["addon_parent"],
+    "manifest": ["omnibox"],
+    "paths": [
+      ["omnibox"]
+    ]
   },
-  // This module supports the "menus" and "contextMenus" namespaces,
-  // and because of permissions, the module name must differ from both.
-  menusInternal: {
-    url: "chrome://browser/content/ext-menus.js",
-    schema: "chrome://browser/content/schemas/menus.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["menusInternal"],
-    ],
+  "pageAction": {
+    "url": "chrome://browser/content/ext-pageAction.js",
+    "schema": "chrome://browser/content/schemas/page_action.json",
+    "scopes": ["addon_parent"],
+    "manifest": ["page_action"],
+    "paths": [
+      ["pageAction"]
+    ]
   },
-  omnibox: {
-    url: "chrome://browser/content/ext-omnibox.js",
-    schema: "chrome://browser/content/schemas/omnibox.json",
-    scopes: ["addon_parent"],
-    manifest: ["omnibox"],
-    paths: [
-      ["omnibox"],
-    ],
+  "geckoProfiler": {
+    "url": "chrome://browser/content/ext-geckoProfiler.js",
+    "schema": "chrome://browser/content/schemas/geckoProfiler.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["geckoProfiler"]
+    ]
   },
-  pageAction: {
-    url: "chrome://browser/content/ext-pageAction.js",
-    schema: "chrome://browser/content/schemas/page_action.json",
-    scopes: ["addon_parent"],
-    manifest: ["page_action"],
-    paths: [
-      ["pageAction"],
-    ],
+  "sessions": {
+    "url": "chrome://browser/content/ext-sessions.js",
+    "schema": "chrome://browser/content/schemas/sessions.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["sessions"]
+    ]
   },
-  geckoProfiler: {
-    url: "chrome://browser/content/ext-geckoProfiler.js",
-    schema: "chrome://browser/content/schemas/geckoProfiler.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["geckoProfiler"],
-    ],
-  },
-  sessions: {
-    url: "chrome://browser/content/ext-sessions.js",
-    schema: "chrome://browser/content/schemas/sessions.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["sessions"],
-    ],
+  "sidebarAction": {
+    "url": "chrome://browser/content/ext-sidebarAction.js",
+    "schema": "chrome://browser/content/schemas/sidebar_action.json",
+    "scopes": ["addon_parent"],
+    "manifest": ["sidebar_action"],
+    "paths": [
+      ["sidebarAction"]
+    ]
   },
-  sidebarAction: {
-    url: "chrome://browser/content/ext-sidebarAction.js",
-    schema: "chrome://browser/content/schemas/sidebar_action.json",
-    scopes: ["addon_parent"],
-    manifest: ["sidebar_action"],
-    paths: [
-      ["sidebarAction"],
-    ],
-  },
-  tabs: {
-    url: "chrome://browser/content/ext-tabs.js",
-    schema: "chrome://browser/content/schemas/tabs.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["tabs"],
-    ],
+  "tabs": {
+    "url": "chrome://browser/content/ext-tabs.js",
+    "schema": "chrome://browser/content/schemas/tabs.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["tabs"]
+    ]
   },
-  urlOverrides: {
-    url: "chrome://browser/content/ext-url-overrides.js",
-    schema: "chrome://browser/content/schemas/url_overrides.json",
-    scopes: ["addon_parent"],
-    manifest: ["chrome_url_overrides"],
-    paths: [
-      ["urlOverrides"],
-    ],
+  "urlOverrides": {
+    "url": "chrome://browser/content/ext-url-overrides.js",
+    "schema": "chrome://browser/content/schemas/url_overrides.json",
+    "scopes": ["addon_parent"],
+    "manifest": ["chrome_url_overrides"],
+    "paths": [
+      ["urlOverrides"]
+    ]
   },
-  windows: {
-    url: "chrome://browser/content/ext-windows.js",
-    schema: "chrome://browser/content/schemas/windows.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["windows"],
-    ],
-  },
-});
+  "windows": {
+    "url": "chrome://browser/content/ext-windows.js",
+    "schema": "chrome://browser/content/schemas/windows.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["windows"]
+    ]
+  }
+}
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -1,17 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 /* exported browserActionFor, sidebarActionFor, pageActionFor */
 /* global browserActionFor:false, sidebarActionFor:false, pageActionFor:false */
 
 // The ext-* files are imported into the same scopes.
-/* import-globals-from ext-utils.js */
+/* import-globals-from ext-browser.js */
 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
                                   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
@@ -26,16 +26,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
 var {
   DefaultWeakMap,
 } = ExtensionUtils;
 
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
 
 var {
   IconDetails,
+  StartupCache,
 } = ExtensionParent;
 
 const POPUP_PRELOAD_TIMEOUT_MS = 200;
 const POPUP_OPEN_MS_HISTOGRAM = "WEBEXT_BROWSERACTION_POPUP_OPEN_MS";
 const POPUP_RESULT_HISTOGRAM = "WEBEXT_BROWSERACTION_POPUP_PRELOAD_RESULT_COUNT";
 
 var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
@@ -60,17 +61,17 @@ XPCOMUtils.defineLazyGetter(this, "brows
   };
 });
 
 this.browserAction = class extends ExtensionAPI {
   static for(extension) {
     return browserActionMap.get(extension);
   }
 
-  onManifestEntry(entryName) {
+  async onManifestEntry(entryName) {
     let {extension} = this;
 
     let options = extension.manifest.browser_action;
 
     this.iconData = new DefaultWeakMap(icons => this.getIconData(icons));
 
     let widgetId = makeWidgetId(extension.id);
     this.id = `${widgetId}-browser-action`;
@@ -83,37 +84,45 @@ this.browserAction = class extends Exten
 
     this.tabManager = extension.tabManager;
 
     this.defaults = {
       enabled: true,
       title: options.default_title || extension.name,
       badgeText: "",
       badgeBackgroundColor: null,
-      icon: IconDetails.normalize({
-        path: options.default_icon,
-        themeIcons: options.theme_icons,
-      }, extension),
       popup: options.default_popup || "",
       area: browserAreas[options.default_area || "navbar"],
     };
 
     this.browserStyle = options.browser_style || false;
     if (options.browser_style === null) {
       this.extension.logger.warn("Please specify whether you want browser_style " +
                                  "or not in your browser_action options.");
     }
 
+    browserActionMap.set(extension, this);
+
+    this.defaults.icon = await StartupCache.get(
+      extension, ["browserAction", "default_icon"],
+      () => IconDetails.normalize({
+        path: options.default_icon,
+        themeIcons: options.theme_icons,
+      }, extension));
+
+    this.iconData.set(
+      this.defaults.icon,
+      await StartupCache.get(
+        extension, ["browserAction", "default_icon_data"],
+        () => this.getIconData(this.defaults.icon)));
+
     this.tabContext = new TabContext(tab => Object.create(this.defaults),
                                      extension);
 
-    EventEmitter.decorate(this);
-
     this.build();
-    browserActionMap.set(extension, this);
   }
 
   onShutdown(reason) {
     browserActionMap.delete(this.extension);
 
     this.tabContext.shutdown();
     CustomizableUI.destroyWidget(this.id);
 
@@ -125,16 +134,20 @@ this.browserAction = class extends Exten
       id: this.id,
       viewId: this.viewId,
       type: "view",
       removable: true,
       label: this.defaults.title || this.extension.name,
       tooltiptext: this.defaults.title || "",
       defaultArea: this.defaults.area,
 
+      // Don't attempt to load properties from the built-in widget string
+      // bundle.
+      localized: false,
+
       onBeforeCreated: document => {
         let view = document.createElementNS(XUL_NS, "panelview");
         view.id = this.viewId;
         view.setAttribute("flex", "1");
         view.setAttribute("extension", true);
 
         document.getElementById("PanelUI-multiView").appendChild(view);
 
--- a/browser/components/extensions/ext-c-devtools-panels.js
+++ b/browser/components/extensions/ext-c-devtools-panels.js
@@ -1,34 +1,32 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
 /* import-globals-from ../../../toolkit/components/extensions/ext-c-toolkit.js */
 
-XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
-                                  "resource://gre/modules/EventEmitter.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChildDevToolsUtils",
                                   "resource://gre/modules/ExtensionChildDevToolsUtils.jsm");
 
 var {
   promiseDocumentLoaded,
 } = ExtensionUtils;
 
 /**
  * Represents an addon devtools panel in the child process.
  *
  * @param {DevtoolsExtensionContext}
  *   A devtools extension context running in a child process.
  * @param {object} panelOptions
  * @param {string} panelOptions.id
  *   The id of the addon devtools panel registered in the main process.
  */
-class ChildDevToolsPanel extends EventEmitter {
+class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
   constructor(context, {id}) {
     super();
 
     this.context = context;
     this.context.callOnClose(this);
 
     this.id = id;
     this._panelContext = null;
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -1,15 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
 /* import-globals-from ext-browserAction.js */
-/* import-globals-from ext-utils.js */
+/* import-globals-from ext-browser.js */
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
                                   "resource://gre/modules/ExtensionParent.jsm");
 
 var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 this.commands = class extends ExtensionAPI {
   onManifestEntry(entryName) {
@@ -20,17 +20,16 @@ this.commands = class extends ExtensionA
 
     // Map[{String} commandName -> {Object} commandProperties]
     this.commands = this.loadCommandsFromManifest(this.extension.manifest);
 
     // WeakMap[Window -> <xul:keyset>]
     this.keysetsMap = new WeakMap();
 
     this.register();
-    EventEmitter.decorate(this);
   }
 
   onShutdown(reason) {
     this.unregister();
   }
 
   /**
    * Registers the commands to all open windows and to any which
--- a/browser/components/extensions/ext-devtools-panels.js
+++ b/browser/components/extensions/ext-devtools-panels.js
@@ -1,15 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
 /* import-globals-from ext-devtools.js */
-/* import-globals-from ext-utils.js */
+/* import-globals-from ext-browser.js */
 
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
                                   "resource:///modules/E10SUtils.jsm");
 
 var {
   IconDetails,
--- a/browser/components/extensions/ext-devtools.js
+++ b/browser/components/extensions/ext-devtools.js
@@ -1,17 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 /* exported getDevToolsTargetForContext */
 /* global getTargetTabIdForToolbox, getDevToolsTargetForContext */
 
 // The ext-* files are imported into the same scopes.
-/* import-globals-from ext-utils.js */
+/* import-globals-from ext-browser.js */
 
 /**
  * This module provides helpers used by the other specialized `ext-devtools-*.js` modules
  * and the implementation of the `devtools_page`.
  */
 
 XPCOMUtils.defineLazyModuleGetter(this, "DevToolsShim",
                                   "chrome://devtools-shim/content/DevToolsShim.jsm");
--- a/browser/components/extensions/ext-history.js
+++ b/browser/components/extensions/ext-history.js
@@ -87,47 +87,46 @@ const convertNavHistoryContainerResultNo
   container.containerOpen = false;
   return results;
 };
 
 var _observer;
 
 const getHistoryObserver = () => {
   if (!_observer) {
-    _observer = {
-      onDeleteURI: function(uri, guid, reason) {
+    _observer = new class extends EventEmitter {
+      onDeleteURI(uri, guid, reason) {
         this.emit("visitRemoved", {allHistory: false, urls: [uri.spec]});
-      },
-      onVisit: function(uri, visitId, time, sessionId, referringId, transitionType, guid, hidden, visitCount, typed, lastKnownTitle) {
+      }
+      onVisit(uri, visitId, time, sessionId, referringId, transitionType, guid, hidden, visitCount, typed, lastKnownTitle) {
         let data = {
           id: guid,
           url: uri.spec,
           title: lastKnownTitle || "",
           lastVisitTime: time / 1000,  // time from Places is microseconds,
           visitCount,
           typedCount: typed,
         };
         this.emit("visited", data);
-      },
-      onBeginUpdateBatch: function() {},
-      onEndUpdateBatch: function() {},
-      onTitleChanged: function(uri, title) {
+      }
+      onBeginUpdateBatch() {}
+      onEndUpdateBatch() {}
+      onTitleChanged(uri, title) {
         this.emit("titleChanged", {url: uri.spec, title: title});
-      },
-      onClearHistory: function() {
+      }
+      onClearHistory() {
         this.emit("visitRemoved", {allHistory: true, urls: []});
-      },
-      onPageChanged: function() {},
-      onFrecencyChanged: function() {},
-      onManyFrecenciesChanged: function() {},
-      onDeleteVisits: function(uri, time, guid, reason) {
+      }
+      onPageChanged() {}
+      onFrecencyChanged() {}
+      onManyFrecenciesChanged() {}
+      onDeleteVisits(uri, time, guid, reason) {
         this.emit("visitRemoved", {allHistory: false, urls: [uri.spec]});
-      },
-    };
-    EventEmitter.decorate(_observer);
+      }
+    }();
     PlacesUtils.history.addObserver(_observer);
   }
   return _observer;
 };
 
 this.history = class extends ExtensionAPI {
   getAPI(context) {
     return {
--- a/browser/components/extensions/ext-menus.js
+++ b/browser/components/extensions/ext-menus.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
-/* import-globals-from ext-utils.js */
+/* import-globals-from ext-browser.js */
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 var {
   ExtensionError,
 } = ExtensionUtils;
 
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -1,56 +1,56 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
 /* import-globals-from ext-browserAction.js */
-/* import-globals-from ext-utils.js */
+/* import-globals-from ext-browser.js */
 
 XPCOMUtils.defineLazyModuleGetter(this, "PanelPopup",
                                   "resource:///modules/ExtensionPopups.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                   "resource://gre/modules/TelemetryStopwatch.jsm");
 
 
 var {
   DefaultWeakMap,
 } = ExtensionUtils;
 
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
 
 var {
   IconDetails,
+  StartupCache,
 } = ExtensionParent;
 
 const popupOpenTimingHistogram = "WEBEXT_PAGEACTION_POPUP_OPEN_MS";
 
 // WeakMap[Extension -> PageAction]
 let pageActionMap = new WeakMap();
 
 this.pageAction = class extends ExtensionAPI {
   static for(extension) {
     return pageActionMap.get(extension);
   }
 
-  onManifestEntry(entryName) {
+  async onManifestEntry(entryName) {
     let {extension} = this;
     let options = extension.manifest.page_action;
 
     this.iconData = new DefaultWeakMap(icons => this.getIconData(icons));
 
     this.id = makeWidgetId(extension.id) + "-page-action";
 
     this.tabManager = extension.tabManager;
 
     this.defaults = {
       show: false,
       title: options.default_title || extension.name,
-      icon: IconDetails.normalize({path: options.default_icon}, extension),
       popup: options.default_popup || "",
     };
 
     this.browserStyle = options.browser_style || false;
     if (options.browser_style === null) {
       this.extension.logger.warn("Please specify whether you want browser_style " +
                                  "or not in your page_action options.");
     }
@@ -58,19 +58,27 @@ this.pageAction = class extends Extensio
     this.tabContext = new TabContext(tab => Object.create(this.defaults),
                                      extension);
 
     this.tabContext.on("location-change", this.handleLocationChange.bind(this)); // eslint-disable-line mozilla/balanced-listeners
 
     // WeakMap[ChromeWindow -> <xul:image>]
     this.buttons = new WeakMap();
 
-    EventEmitter.decorate(this);
+    pageActionMap.set(extension, this);
+
+    this.defaults.icon = await StartupCache.get(
+      extension, ["pageAction", "default_icon"],
+      () => IconDetails.normalize({path: options.default_icon}, extension));
 
-    pageActionMap.set(extension, this);
+    this.iconData.set(
+      this.defaults.icon,
+      await StartupCache.get(
+        extension, ["pageAction", "default_icon_data"],
+        () => this.getIconData(this.defaults.icon)));
   }
 
   onShutdown(reason) {
     pageActionMap.delete(this.extension);
 
     this.tabContext.shutdown();
 
     for (let window of windowTracker.browserWindows()) {
--- a/browser/components/extensions/ext-sessions.js
+++ b/browser/components/extensions/ext-sessions.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
-/* import-globals-from ext-utils.js */
+/* import-globals-from ext-browser.js */
 
 var {
   ExtensionError,
   promiseObserved,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
--- a/browser/components/extensions/ext-sidebarAction.js
+++ b/browser/components/extensions/ext-sidebarAction.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
-/* import-globals-from ext-utils.js */
+/* import-globals-from ext-browser.js */
 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
 
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
-/* import-globals-from ext-utils.js */
+/* import-globals-from ext-browser.js */
 
 XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
   const stringSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
   return stringSvc.createBundle("chrome://global/locale/extensions.properties");
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
deleted file mode 100644
--- a/browser/components/extensions/ext-utils.js
+++ /dev/null
@@ -1,819 +0,0 @@
-/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set sts=2 sw=2 et tw=80: */
-"use strict";
-
-/* exported WindowEventManager, makeWidgetId */
-/* global EventEmitter:false, TabContext:false, WindowEventManager:false,
-          makeWidgetId:false, tabTracker:true, windowTracker:true */
-/* import-globals-from ../../../toolkit/components/extensions/ext-toolkit.js */
-
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
-                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-Cu.import("resource://gre/modules/ExtensionTabs.jsm");
-
-var {
-  ExtensionError,
-  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
-// shared among the different ext-*.js scripts.
-
-global.makeWidgetId = id => {
-  id = id.toLowerCase();
-  // FIXME: This allows for collisions.
-  return id.replace(/[^a-z0-9_-]/g, "_");
-};
-
-// Manages tab-specific context data, and dispatching tab select events
-// across all windows.
-global.TabContext = function TabContext(getDefaults, extension) {
-  this.extension = extension;
-  this.getDefaults = getDefaults;
-
-  this.tabData = new WeakMap();
-  this.lastLocation = new WeakMap();
-
-  windowTracker.addListener("progress", this);
-  windowTracker.addListener("TabSelect", this);
-
-  EventEmitter.decorate(this);
-};
-
-TabContext.prototype = {
-  get(nativeTab) {
-    if (!this.tabData.has(nativeTab)) {
-      this.tabData.set(nativeTab, this.getDefaults(nativeTab));
-    }
-
-    return this.tabData.get(nativeTab);
-  },
-
-  clear(nativeTab) {
-    this.tabData.delete(nativeTab);
-  },
-
-  handleEvent(event) {
-    if (event.type == "TabSelect") {
-      let nativeTab = event.target;
-      this.emit("tab-select", nativeTab);
-      this.emit("location-change", nativeTab);
-    }
-  },
-
-  onStateChange(browser, webProgress, request, stateFlags, statusCode) {
-    let flags = Ci.nsIWebProgressListener;
-
-    if (!(~stateFlags & (flags.STATE_IS_WINDOW | flags.STATE_START) ||
-          this.lastLocation.has(browser))) {
-      this.lastLocation.set(browser, request.URI);
-    }
-  },
-
-  onLocationChange(browser, webProgress, request, locationURI, flags) {
-    let gBrowser = browser.ownerGlobal.gBrowser;
-    let lastLocation = this.lastLocation.get(browser);
-    if (browser === gBrowser.selectedBrowser &&
-        !(lastLocation && lastLocation.equalsExceptRef(browser.currentURI))) {
-      let nativeTab = gBrowser.getTabForBrowser(browser);
-      this.emit("location-change", nativeTab, true);
-    }
-    this.lastLocation.set(browser, browser.currentURI);
-  },
-
-  shutdown() {
-    windowTracker.removeListener("progress", this);
-    windowTracker.removeListener("TabSelect", this);
-  },
-};
-
-
-class WindowTracker extends WindowTrackerBase {
-  addProgressListener(window, listener) {
-    window.gBrowser.addTabsProgressListener(listener);
-  }
-
-  removeProgressListener(window, listener) {
-    window.gBrowser.removeTabsProgressListener(listener);
-  }
-}
-
-/**
- * An event manager API provider which listens for a DOM event in any browser
- * window, and calls the given listener function whenever an event is received.
- * That listener function receives a `fire` object, which it can use to dispatch
- * events to the extension, and a DOM event object.
- *
- * @param {BaseContext} context
- *        The extension context which the event manager belongs to.
- * @param {string} name
- *        The API name of the event manager, e.g.,"runtime.onMessage".
- * @param {string} event
- *        The name of the DOM event to listen for.
- * @param {function} listener
- *        The listener function to call when a DOM event is received.
- */
-global.WindowEventManager = class extends EventManager {
-  constructor(context, name, event, listener) {
-    super(context, name, fire => {
-      let listener2 = listener.bind(null, fire);
-
-      windowTracker.addListener(event, listener2);
-      return () => {
-        windowTracker.removeListener(event, listener2);
-      };
-    });
-  }
-};
-
-class TabTracker extends TabTrackerBase {
-  constructor() {
-    super();
-
-    this._tabs = new WeakMap();
-    this._tabIds = new Map();
-    this._nextId = 1;
-
-    this._handleTabDestroyed = this._handleTabDestroyed.bind(this);
-  }
-
-  init() {
-    if (this.initialized) {
-      return;
-    }
-    this.initialized = true;
-
-    this.adoptedTabs = new WeakMap();
-
-    this._handleWindowOpen = this._handleWindowOpen.bind(this);
-    this._handleWindowClose = this._handleWindowClose.bind(this);
-
-    windowTracker.addListener("TabClose", this);
-    windowTracker.addListener("TabOpen", this);
-    windowTracker.addListener("TabSelect", this);
-    windowTracker.addOpenListener(this._handleWindowOpen);
-    windowTracker.addCloseListener(this._handleWindowClose);
-
-    /* eslint-disable mozilla/balanced-listeners */
-    this.on("tab-detached", this._handleTabDestroyed);
-    this.on("tab-removed", this._handleTabDestroyed);
-    /* eslint-enable mozilla/balanced-listeners */
-  }
-
-  getId(nativeTab) {
-    if (this._tabs.has(nativeTab)) {
-      return this._tabs.get(nativeTab);
-    }
-
-    this.init();
-
-    let id = this._nextId++;
-    this.setId(nativeTab, id);
-    return id;
-  }
-
-  setId(nativeTab, id) {
-    this._tabs.set(nativeTab, id);
-    this._tabIds.set(id, nativeTab);
-  }
-
-  _handleTabDestroyed(event, {nativeTab}) {
-    let id = this._tabs.get(nativeTab);
-    if (id) {
-      this._tabs.delete(nativeTab);
-      if (this._tabIds.get(id) === nativeTab) {
-        this._tabIds.delete(id);
-      }
-    }
-  }
-
-  /**
-   * Returns the XUL <tab> element associated with the given tab ID. If no tab
-   * with the given ID exists, and no default value is provided, an error is
-   * raised, belonging to the scope of the given context.
-   *
-   * @param {integer} tabId
-   *        The ID of the tab to retrieve.
-   * @param {*} default_
-   *        The value to return if no tab exists with the given ID.
-   * @returns {Element<tab>}
-   *        A XUL <tab> element.
-   */
-  getTab(tabId, default_ = undefined) {
-    let nativeTab = this._tabIds.get(tabId);
-    if (nativeTab) {
-      return nativeTab;
-    }
-    if (default_ !== undefined) {
-      return default_;
-    }
-    throw new ExtensionError(`Invalid tab ID: ${tabId}`);
-  }
-
-  /**
-   * @param {Event} event
-   *        The DOM Event to handle.
-   * @private
-   */
-  handleEvent(event) {
-    let nativeTab = event.target;
-
-    switch (event.type) {
-      case "TabOpen":
-        let {adoptedTab} = event.detail;
-        if (adoptedTab) {
-          this.adoptedTabs.set(adoptedTab, event.target);
-
-          // This tab is being created to adopt a tab from a different window.
-          // Copy the ID from the old tab to the new.
-          this.setId(nativeTab, this.getId(adoptedTab));
-
-          adoptedTab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetFrameData", {
-            windowId: windowTracker.getId(nativeTab.ownerGlobal),
-          });
-        }
-
-        // Save the current tab, since the newly-created tab will likely be
-        // active by the time the promise below resolves and the event is
-        // dispatched.
-        let currentTab = nativeTab.ownerGlobal.gBrowser.selectedTab;
-
-        // We need to delay sending this event until the next tick, since the
-        // tab does not have its final index when the TabOpen event is dispatched.
-        Promise.resolve().then(() => {
-          if (event.detail.adoptedTab) {
-            this.emitAttached(event.originalTarget);
-          } else {
-            this.emitCreated(event.originalTarget, currentTab);
-          }
-        });
-        break;
-
-      case "TabClose":
-        let {adoptedBy} = event.detail;
-        if (adoptedBy) {
-          // This tab is being closed because it was adopted by a new window.
-          // Copy its ID to the new tab, in case it was created as the first tab
-          // of a new window, and did not have an `adoptedTab` detail when it was
-          // opened.
-          this.setId(adoptedBy, this.getId(nativeTab));
-
-          this.emitDetached(nativeTab, adoptedBy);
-        } else {
-          this.emitRemoved(nativeTab, false);
-        }
-        break;
-
-      case "TabSelect":
-        // Because we are delaying calling emitCreated above, we also need to
-        // delay sending this event because it shouldn't fire before onCreated.
-        Promise.resolve().then(() => {
-          this.emitActivated(nativeTab);
-        });
-        break;
-    }
-  }
-
-  /**
-   * A private method which is called whenever a new browser window is opened,
-   * and dispatches the necessary events for it.
-   *
-   * @param {DOMWindow} window
-   *        The window being opened.
-   * @private
-   */
-  _handleWindowOpen(window) {
-    if (window.arguments && window.arguments[0] instanceof window.XULElement) {
-      // If the first window argument is a XUL element, it means the
-      // window is about to adopt a tab from another window to replace its
-      // initial tab.
-      //
-      // Note that this event handler depends on running before the
-      // delayed startup code in browser.js, which is currently triggered
-      // by the first MozAfterPaint event. That code handles finally
-      // adopting the tab, and clears it from the arguments list in the
-      // process, so if we run later than it, we're too late.
-      let nativeTab = window.arguments[0];
-      let adoptedBy = window.gBrowser.tabs[0];
-
-      this.adoptedTabs.set(nativeTab, adoptedBy);
-      this.setId(adoptedBy, this.getId(nativeTab));
-
-      // We need to be sure to fire this event after the onDetached event
-      // for the original tab.
-      let listener = (event, details) => {
-        if (details.nativeTab === nativeTab) {
-          this.off("tab-detached", listener);
-
-          Promise.resolve().then(() => {
-            this.emitAttached(details.adoptedBy);
-          });
-        }
-      };
-
-      this.on("tab-detached", listener);
-    } else {
-      for (let nativeTab of window.gBrowser.tabs) {
-        this.emitCreated(nativeTab);
-      }
-
-      // emitActivated to trigger tab.onActivated/tab.onHighlighted for a newly opened window.
-      this.emitActivated(window.gBrowser.tabs[0]);
-    }
-  }
-
-  /**
-   * A private method which is called whenever a browser window is closed,
-   * and dispatches the necessary events for it.
-   *
-   * @param {DOMWindow} window
-   *        The window being closed.
-   * @private
-   */
-  _handleWindowClose(window) {
-    for (let nativeTab of window.gBrowser.tabs) {
-      if (this.adoptedTabs.has(nativeTab)) {
-        this.emitDetached(nativeTab, this.adoptedTabs.get(nativeTab));
-      } else {
-        this.emitRemoved(nativeTab, true);
-      }
-    }
-  }
-
-  /**
-   * Emits a "tab-activated" event for the given tab element.
-   *
-   * @param {NativeTab} nativeTab
-   *        The tab element which has been activated.
-   * @private
-   */
-  emitActivated(nativeTab) {
-    this.emit("tab-activated", {
-      tabId: this.getId(nativeTab),
-      windowId: windowTracker.getId(nativeTab.ownerGlobal)});
-  }
-
-  /**
-   * Emits a "tab-attached" event for the given tab element.
-   *
-   * @param {NativeTab} nativeTab
-   *        The tab element in the window to which the tab is being attached.
-   * @private
-   */
-  emitAttached(nativeTab) {
-    let newWindowId = windowTracker.getId(nativeTab.ownerGlobal);
-    let tabId = this.getId(nativeTab);
-
-    this.emit("tab-attached", {nativeTab, tabId, newWindowId, newPosition: nativeTab._tPos});
-  }
-
-  /**
-   * Emits a "tab-detached" event for the given tab element.
-   *
-   * @param {NativeTab} nativeTab
-   *        The tab element in the window from which the tab is being detached.
-   * @param {NativeTab} adoptedBy
-   *        The tab element in the window to which detached tab is being moved,
-   *        and will adopt this tab's contents.
-   * @private
-   */
-  emitDetached(nativeTab, adoptedBy) {
-    let oldWindowId = windowTracker.getId(nativeTab.ownerGlobal);
-    let tabId = this.getId(nativeTab);
-
-    this.emit("tab-detached", {nativeTab, adoptedBy, tabId, oldWindowId, oldPosition: nativeTab._tPos});
-  }
-
-  /**
-   * Emits a "tab-created" event for the given tab element.
-   *
-   * @param {NativeTab} nativeTab
-   *        The tab element which is being created.
-   * @param {NativeTab} [currentTab]
-   *        The tab element for the currently active tab.
-   * @private
-   */
-  emitCreated(nativeTab, currentTab) {
-    this.emit("tab-created", {nativeTab, currentTab});
-  }
-
-  /**
-   * Emits a "tab-removed" event for the given tab element.
-   *
-   * @param {NativeTab} nativeTab
-   *        The tab element which is being removed.
-   * @param {boolean} isWindowClosing
-   *        True if the tab is being removed because the browser window is
-   *        closing.
-   * @private
-   */
-  emitRemoved(nativeTab, isWindowClosing) {
-    let windowId = windowTracker.getId(nativeTab.ownerGlobal);
-    let tabId = this.getId(nativeTab);
-
-    // When addons run in-process, `window.close()` is synchronous. Most other
-    // addon-invoked calls are asynchronous since they go through a proxy
-    // context via the message manager. This includes event registrations such
-    // as `tabs.onRemoved.addListener`.
-    //
-    // So, even if `window.close()` were to be called (in-process) after calling
-    // `tabs.onRemoved.addListener`, then the tab would be closed before the
-    // event listener is registered. To make sure that the event listener is
-    // notified, we dispatch `tabs.onRemoved` asynchronously.
-    Services.tm.dispatchToMainThread(() => {
-      this.emit("tab-removed", {nativeTab, tabId, windowId, isWindowClosing});
-    });
-  }
-
-  getBrowserData(browser) {
-    if (browser.ownerGlobal.location.href === "about:addons") {
-      // When we're loaded into a <browser> inside about:addons, we need to go up
-      // one more level.
-      browser = browser.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDocShell)
-                       .chromeEventHandler;
-    }
-
-    let result = {
-      tabId: -1,
-      windowId: -1,
-    };
-
-    let {gBrowser} = browser.ownerGlobal;
-    // Some non-browser windows have gBrowser but not
-    // getTabForBrowser!
-    if (gBrowser && gBrowser.getTabForBrowser) {
-      result.windowId = windowTracker.getId(browser.ownerGlobal);
-
-      let nativeTab = gBrowser.getTabForBrowser(browser);
-      if (nativeTab) {
-        result.tabId = this.getId(nativeTab);
-      }
-    }
-
-    return result;
-  }
-
-  get activeTab() {
-    let window = windowTracker.topWindow;
-    if (window && window.gBrowser) {
-      return window.gBrowser.selectedTab;
-    }
-    return null;
-  }
-}
-
-windowTracker = new WindowTracker();
-tabTracker = new TabTracker();
-
-Object.assign(global, {tabTracker, windowTracker});
-
-class Tab extends TabBase {
-  get _favIconUrl() {
-    return this.window.gBrowser.getIcon(this.nativeTab);
-  }
-
-  get audible() {
-    return this.nativeTab.soundPlaying;
-  }
-
-  get browser() {
-    return this.nativeTab.linkedBrowser;
-  }
-
-  get frameLoader() {
-    // If we don't have a frameLoader yet, just return a dummy with no width and
-    // height.
-    return super.frameLoader || {lazyWidth: 0, lazyHeight: 0};
-  }
-
-  get cookieStoreId() {
-    return getCookieStoreIdForTab(this, this.nativeTab);
-  }
-
-  get height() {
-    return this.frameLoader.lazyHeight;
-  }
-
-  get index() {
-    return this.nativeTab._tPos;
-  }
-
-  get mutedInfo() {
-    let {nativeTab} = this;
-
-    let mutedInfo = {muted: nativeTab.muted};
-    if (nativeTab.muteReason === null) {
-      mutedInfo.reason = "user";
-    } else if (nativeTab.muteReason) {
-      mutedInfo.reason = "extension";
-      mutedInfo.extensionId = nativeTab.muteReason;
-    }
-
-    return mutedInfo;
-  }
-
-  get lastAccessed() {
-    return this.nativeTab.lastAccessed;
-  }
-
-  get pinned() {
-    return this.nativeTab.pinned;
-  }
-
-  get active() {
-    return this.nativeTab.selected;
-  }
-
-  get selected() {
-    return this.nativeTab.selected;
-  }
-
-  get status() {
-    if (this.nativeTab.getAttribute("busy") === "true") {
-      return "loading";
-    }
-    return "complete";
-  }
-
-  get width() {
-    return this.frameLoader.lazyWidth;
-  }
-
-  get window() {
-    return this.nativeTab.ownerGlobal;
-  }
-
-  get windowId() {
-    return windowTracker.getId(this.window);
-  }
-
-  /**
-   * Converts session store data to an object compatible with the return value
-   * of the convert() method, representing that data.
-   *
-   * @param {Extension} extension
-   *        The extension for which to convert the data.
-   * @param {Object} tabData
-   *        Session store data for a closed tab, as returned by
-   *        `SessionStore.getClosedTabData()`.
-   * @param {DOMWindow} [window = null]
-   *        The browser window which the tab belonged to before it was closed.
-   *        May be null if the window the tab belonged to no longer exists.
-   *
-   * @returns {Object}
-   * @static
-   */
-  static convertFromSessionStoreClosedData(extension, tabData, window = null) {
-    let result = {
-      sessionId: String(tabData.closedId),
-      index: tabData.pos ? tabData.pos : 0,
-      windowId: window && windowTracker.getId(window),
-      highlighted: false,
-      active: false,
-      pinned: false,
-      incognito: Boolean(tabData.state && tabData.state.isPrivate),
-      lastAccessed: tabData.state ? tabData.state.lastAccessed : tabData.lastAccessed,
-    };
-
-    if (extension.tabManager.hasTabPermission(tabData)) {
-      let entries = tabData.state ? tabData.state.entries : tabData.entries;
-      let entry = entries[entries.length - 1];
-      result.url = entry.url;
-      result.title = entry.title;
-      if (tabData.image) {
-        result.favIconUrl = tabData.image;
-      }
-    }
-
-    return result;
-  }
-}
-
-class Window extends WindowBase {
-  /**
-   * Update the geometry of the browser window.
-   *
-   * @param {Object} options
-   *        An object containing new values for the window's geometry.
-   * @param {integer} [options.left]
-   *        The new pixel distance of the left side of the browser window from
-   *        the left of the screen.
-   * @param {integer} [options.top]
-   *        The new pixel distance of the top side of the browser window from
-   *        the top of the screen.
-   * @param {integer} [options.width]
-   *        The new pixel width of the window.
-   * @param {integer} [options.height]
-   *        The new pixel height of the window.
-   */
-  updateGeometry(options) {
-    let {window} = this;
-
-    if (options.left !== null || options.top !== null) {
-      let left = options.left !== null ? options.left : window.screenX;
-      let top = options.top !== null ? options.top : window.screenY;
-      window.moveTo(left, top);
-    }
-
-    if (options.width !== null || options.height !== null) {
-      let width = options.width !== null ? options.width : window.outerWidth;
-      let height = options.height !== null ? options.height : window.outerHeight;
-      window.resizeTo(width, height);
-    }
-  }
-
-  get title() {
-    return this.window.document.title;
-  }
-
-  setTitlePreface(titlePreface) {
-    this.window.document.documentElement.setAttribute("titlepreface", titlePreface);
-  }
-
-  get focused() {
-    return this.window.document.hasFocus();
-  }
-
-  get top() {
-    return this.window.screenY;
-  }
-
-  get left() {
-    return this.window.screenX;
-  }
-
-  get width() {
-    return this.window.outerWidth;
-  }
-
-  get height() {
-    return this.window.outerHeight;
-  }
-
-  get incognito() {
-    return PrivateBrowsingUtils.isWindowPrivate(this.window);
-  }
-
-  get alwaysOnTop() {
-    return this.xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ;
-  }
-
-  get isLastFocused() {
-    return this.window === windowTracker.topWindow;
-  }
-
-  static getState(window) {
-    const STATES = {
-      [window.STATE_MAXIMIZED]: "maximized",
-      [window.STATE_MINIMIZED]: "minimized",
-      [window.STATE_NORMAL]: "normal",
-    };
-    let state = STATES[window.windowState];
-    if (window.fullScreen) {
-      state = "fullscreen";
-    }
-    return state;
-  }
-
-  get state() {
-    return Window.getState(this.window);
-  }
-
-  set state(state) {
-    let {window} = this;
-    if (state !== "fullscreen" && window.fullScreen) {
-      window.fullScreen = false;
-    }
-
-    switch (state) {
-      case "maximized":
-        window.maximize();
-        break;
-
-      case "minimized":
-      case "docked":
-        window.minimize();
-        break;
-
-      case "normal":
-        // Restore sometimes returns the window to its previous state, rather
-        // than to the "normal" state, so it may need to be called anywhere from
-        // zero to two times.
-        window.restore();
-        if (window.windowState !== window.STATE_NORMAL) {
-          window.restore();
-        }
-        if (window.windowState !== window.STATE_NORMAL) {
-          // And on OS-X, where normal vs. maximized is basically a heuristic,
-          // we need to cheat.
-          window.sizeToContent();
-        }
-        break;
-
-      case "fullscreen":
-        window.fullScreen = true;
-        break;
-
-      default:
-        throw new Error(`Unexpected window state: ${state}`);
-    }
-  }
-
-  * getTabs() {
-    let {tabManager} = this.extension;
-
-    for (let nativeTab of this.window.gBrowser.tabs) {
-      yield tabManager.getWrapper(nativeTab);
-    }
-  }
-
-  /**
-   * Converts session store data to an object compatible with the return value
-   * of the convert() method, representing that data.
-   *
-   * @param {Extension} extension
-   *        The extension for which to convert the data.
-   * @param {Object} windowData
-   *        Session store data for a closed window, as returned by
-   *        `SessionStore.getClosedWindowData()`.
-   *
-   * @returns {Object}
-   * @static
-   */
-  static convertFromSessionStoreClosedData(extension, windowData) {
-    let result = {
-      sessionId: String(windowData.closedId),
-      focused: false,
-      incognito: false,
-      type: "normal", // this is always "normal" for a closed window
-      // Surely this does not actually work?
-      state: this.getState(windowData),
-      alwaysOnTop: false,
-    };
-
-    if (windowData.tabs.length) {
-      result.tabs = windowData.tabs.map(tabData => {
-        return Tab.convertFromSessionStoreClosedData(extension, tabData);
-      });
-    }
-
-    return result;
-  }
-}
-
-Object.assign(global, {Tab, Window});
-
-class TabManager extends TabManagerBase {
-  get(tabId, default_ = undefined) {
-    let nativeTab = tabTracker.getTab(tabId, default_);
-
-    if (nativeTab) {
-      return this.getWrapper(nativeTab);
-    }
-    return default_;
-  }
-
-  addActiveTabPermission(nativeTab = tabTracker.activeTab) {
-    return super.addActiveTabPermission(nativeTab);
-  }
-
-  revokeActiveTabPermission(nativeTab = tabTracker.activeTab) {
-    return super.revokeActiveTabPermission(nativeTab);
-  }
-
-  wrapTab(nativeTab) {
-    return new Tab(this.extension, nativeTab, tabTracker.getId(nativeTab));
-  }
-}
-
-class WindowManager extends WindowManagerBase {
-  get(windowId, context) {
-    let window = windowTracker.getWindow(windowId, context);
-
-    return this.getWrapper(window);
-  }
-
-  * getAll() {
-    for (let window of windowTracker.browserWindows()) {
-      yield this.getWrapper(window);
-    }
-  }
-
-  wrapWindow(window) {
-    return new Window(this.extension, window, windowTracker.getId(window));
-  }
-}
-
-
-extensions.on("startup", (type, extension) => { // eslint-disable-line mozilla/balanced-listeners
-  defineLazyGetter(extension, "tabManager",
-                   () => new TabManager(extension));
-  defineLazyGetter(extension, "windowManager",
-                   () => new WindowManager(extension));
-});
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
-/* import-globals-from ext-utils.js */
+/* import-globals-from ext-browser.js */
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 var {
--- a/browser/components/extensions/extensions-browser.manifest
+++ b/browser/components/extensions/extensions-browser.manifest
@@ -1,6 +1,7 @@
-category webextension-scripts browser chrome://browser/content/ext-browser.js
-category webextension-scripts utils chrome://browser/content/ext-utils.js
+category webextension-modules browser chrome://browser/content/ext-browser.json
+
+category webextension-scripts c-browser chrome://browser/content/ext-browser.js
 category webextension-scripts-devtools browser chrome://browser/content/ext-c-browser.js
 category webextension-scripts-addon browser chrome://browser/content/ext-c-browser.js
 
 category webextension-schemas menus_internal chrome://browser/content/schemas/menus_internal.json
--- a/browser/components/extensions/jar.mn
+++ b/browser/components/extensions/jar.mn
@@ -9,16 +9,17 @@ browser.jar:
     content/browser/extension-mac-panel.css
 #endif
 #ifdef XP_WIN
     content/browser/extension-win-panel.css
 #endif
     content/browser/extension.svg
     content/browser/ext-bookmarks.js
     content/browser/ext-browser.js
+    content/browser/ext-browser.json
     content/browser/ext-browserAction.js
     content/browser/ext-browsingData.js
     content/browser/ext-chrome-settings-overrides.js
     content/browser/ext-commands.js
     content/browser/ext-devtools.js
     content/browser/ext-devtools-inspectedWindow.js
     content/browser/ext-devtools-network.js
     content/browser/ext-devtools-panels.js
@@ -26,17 +27,16 @@ browser.jar:
     content/browser/ext-history.js
     content/browser/ext-menus.js
     content/browser/ext-omnibox.js
     content/browser/ext-pageAction.js
     content/browser/ext-sessions.js
     content/browser/ext-sidebarAction.js
     content/browser/ext-tabs.js
     content/browser/ext-url-overrides.js
-    content/browser/ext-utils.js
     content/browser/ext-windows.js
     content/browser/ext-c-browser.js
     content/browser/ext-c-devtools-inspectedWindow.js
     content/browser/ext-c-devtools-panels.js
     content/browser/ext-c-devtools.js
     content/browser/ext-c-menus.js
     content/browser/ext-c-omnibox.js
     content/browser/ext-c-tabs.js
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -504,20 +504,28 @@ Section "-Application" APP_IDX
       ${EndIf}
       ${LogMsg} "Added Shortcut: $SMPROGRAMS\${BrandFullName}.lnk"
     ${Else}
       ${LogMsg} "** ERROR Adding Shortcut: $SMPROGRAMS\${BrandFullName}.lnk"
     ${EndIf}
   ${EndIf}
 
   ; Update lastwritetime of the Start Menu shortcut to clear the tile cache.
+  ; Do this for both shell contexts in case the user has shortcuts in multiple
+  ; locations, then restore the previous context at the end.
   ${If} ${AtLeastWin8}
-  ${AndIf} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
-    FileOpen $0 "$SMPROGRAMS\${BrandFullName}.lnk" a
-    FileClose $0
+    SetShellVarContext all
+    ${TouchStartMenuShortcut}
+    SetShellVarContext current
+    ${TouchStartMenuShortcut}
+    ${If} $TmpVal == "HKLM"
+      SetShellVarContext all
+    ${ElseIf} $TmpVal == "HKCU"
+      SetShellVarContext current
+    ${EndIf}
   ${EndIf}
 
   ${If} $AddDesktopSC == 1
     CreateShortCut "$DESKTOP\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
     ${If} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
       ShellLink::SetShortCutWorkingDirectory "$DESKTOP\${BrandFullName}.lnk" \
                                              "$INSTDIR"
       ${If} ${AtLeastWin7}
--- a/browser/installer/windows/nsis/shared.nsh
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -66,20 +66,28 @@
     ${EndIf}
   ${EndIf}
 
   ; Migrate the application's Start Menu directory to a single shortcut in the
   ; root of the Start Menu Programs directory.
   ${MigrateStartMenuShortcut}
 
   ; Update lastwritetime of the Start Menu shortcut to clear the tile cache.
+  ; Do this for both shell contexts in case the user has shortcuts in multiple
+  ; locations, then restore the previous context at the end.
   ${If} ${AtLeastWin8}
-  ${AndIf} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
-    FileOpen $0 "$SMPROGRAMS\${BrandFullName}.lnk" a
-    FileClose $0
+    SetShellVarContext all
+    ${TouchStartMenuShortcut}
+    SetShellVarContext current
+    ${TouchStartMenuShortcut}
+    ${If} $TmpVal == "HKLM"
+      SetShellVarContext all
+    ${ElseIf} $TmpVal == "HKCU"
+      SetShellVarContext current
+    ${EndIf}
   ${EndIf}
 
   ; Adds a pinned Task Bar shortcut (see MigrateTaskBarShortcut for details).
   ${MigrateTaskBarShortcut}
 
   ${UpdateShortcutBranding}
 
   ${RemoveDeprecatedKeys}
@@ -147,16 +155,32 @@
       ; will just fail to be attempted to be installed.
       nsExec::Exec "$\"$INSTDIR\maintenanceservice_installer.exe$\""
     ${EndIf}
   ${EndIf}
 !endif
 !macroend
 !define PostUpdate "!insertmacro PostUpdate"
 
+; Update the last modified time on the Start Menu shortcut, so that its icon
+; gets refreshed. Should be called on Win8+ after MigrateStartMenuShortcut.
+!macro TouchStartMenuShortcut
+  ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+    FileOpen $0 "$SMPROGRAMS\${BrandFullName}.lnk" a
+    ${IfNot} ${Errors}
+      System::Call '*(i, i) p .r1'
+      System::Call 'kernel32::GetSystemTimeAsFileTime(p r1)'
+      System::Call 'kernel32::SetFileTime(p r0, i 0, i 0, p r1) i .r2'
+      System::Free $1
+      FileClose $0
+    ${EndIf}
+  ${EndIf}
+!macroend
+!define TouchStartMenuShortcut "!insertmacro TouchStartMenuShortcut"
+
 !macro SetAsDefaultAppGlobal
   ${RemoveDeprecatedKeys} ; Does not use SHCTX
 
   SetShellVarContext all      ; Set SHCTX to all users (e.g. HKLM)
   ${SetHandlers} ; Uses SHCTX
   ${SetStartMenuInternet} "HKLM"
   ${FixShellIconHandler} "HKLM"
   ${ShowShortcuts}
--- a/build/gyp.mozbuild
+++ b/build/gyp.mozbuild
@@ -15,17 +15,17 @@ gyp_vars.update({
     'build_with_mozilla': 1,
     'build_with_chromium': 0,
     # 10.9 once we move to TC cross-compiles - bug 1270217
     'mac_sdk_min': '10.7',
     'mac_deployment_target': '10.7',
     'use_official_google_api_keys': 0,
     'have_clock_monotonic': 1 if CONFIG['HAVE_CLOCK_MONOTONIC'] else 0,
     'have_ethtool_cmd_speed_hi': 1 if CONFIG['MOZ_WEBRTC_HAVE_ETHTOOL_SPEED_HI'] else 0,
-    'include_alsa_audio': 0,
+    'include_alsa_audio': 1 if CONFIG['MOZ_ALSA'] else 0,
     'include_pulse_audio': 1 if CONFIG['MOZ_PULSEAUDIO'] else 0,
     # basic stuff for everything
     'include_internal_video_render': 0,
     'clang': 1 if CONFIG['CLANG_CXX'] else 0,
     'clang_use_chrome_plugins': 0,
     'enable_protobuf': 0,
     'include_tests': 0,
     'enable_android_opensl': 1,
--- a/devtools/shared/debounce.js
+++ b/devtools/shared/debounce.js
@@ -5,17 +5,17 @@
 "use strict";
 
 /**
  * From underscore's `_.debounce`
  * http://underscorejs.org
  * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Underscore may be freely distributed under the MIT license.
  *
- * [and in turn extracted from "sdk/lang/functional/concurrent.js"]
+ * [and in turn extracted from the SDK's "lang/functional/concurrent.js"]
  */
 exports.debounce = function (fn, wait) {
   let timeout, args, context, timestamp, result;
 
   let later = function () {
     let last = Date.now() - timestamp;
     if (last < wait) {
       timeout = setTimeout(later, wait - last);
--- a/dom/base/nsGenericDOMDataNode.h
+++ b/dom/base/nsGenericDOMDataNode.h
@@ -42,20 +42,28 @@ enum {
   // This bit is set to indicate that we have a cached
   // TextIsOnlyWhitespace value
   NS_CACHED_TEXT_IS_ONLY_WHITESPACE =     DATA_NODE_FLAG_BIT(2),
 
   // This bit is only meaningful if the NS_CACHED_TEXT_IS_ONLY_WHITESPACE
   // bit is set, and if so it indicates whether we're only whitespace or
   // not.
   NS_TEXT_IS_ONLY_WHITESPACE =            DATA_NODE_FLAG_BIT(3),
+
+  // This bit is set if there is a NewlineProperty attached to the node
+  // (used by nsTextFrame).
+  NS_HAS_NEWLINE_PROPERTY =               DATA_NODE_FLAG_BIT(4),
+
+  // This bit is set if there is a FlowLengthProperty attached to the node
+  // (used by nsTextFrame).
+  NS_HAS_FLOWLENGTH_PROPERTY =            DATA_NODE_FLAG_BIT(5),
 };
 
 // Make sure we have enough space for those bits
-ASSERT_NODE_FLAGS_SPACE(NODE_TYPE_SPECIFIC_BITS_OFFSET + 4);
+ASSERT_NODE_FLAGS_SPACE(NODE_TYPE_SPECIFIC_BITS_OFFSET + 6);
 
 #undef DATA_NODE_FLAG_BIT
 
 class nsGenericDOMDataNode : public nsIContent
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -29,18 +29,17 @@ StaticRefPtr<nsWindowMemoryReporter> sWi
  * Don't trigger a ghost window check when a DOM window is detached if we've
  * run it this recently.
  */
 const int32_t kTimeBetweenChecks = 45; /* seconds */
 
 nsWindowMemoryReporter::nsWindowMemoryReporter()
   : mLastCheckForGhostWindows(TimeStamp::NowLoRes()),
     mCycleCollectorIsRunning(false),
-    mCheckTimerWaitingForCCEnd(false),
-    mGhostWindowCount(0)
+    mCheckTimerWaitingForCCEnd(false)
 {
 }
 
 nsWindowMemoryReporter::~nsWindowMemoryReporter()
 {
   KillCheckTimer();
 }
 
@@ -115,17 +114,18 @@ nsWindowMemoryReporter::Init()
     os->AddObserver(sWindowReporter, "after-minimize-memory-usage",
                     /* weakRef = */ true);
     os->AddObserver(sWindowReporter, "cycle-collector-begin",
                     /* weakRef = */ true);
     os->AddObserver(sWindowReporter, "cycle-collector-end",
                     /* weakRef = */ true);
   }
 
-  RegisterGhostWindowsDistinguishedAmount(GhostWindowsDistinguishedAmount);
+  RegisterStrongMemoryReporter(new GhostWindowsReporter());
+  RegisterGhostWindowsDistinguishedAmount(GhostWindowsReporter::DistinguishedAmount);
 }
 
 /* static */ nsWindowMemoryReporter*
 nsWindowMemoryReporter::Get()
 {
   return sWindowReporter;
 }
 
@@ -501,27 +501,16 @@ nsWindowMemoryReporter::CollectReports(n
       path,
       nsIMemoryReporter::KIND_OTHER,
       nsIMemoryReporter::UNITS_COUNT,
       /* amount = */ 1,
       /* description = */ NS_LITERAL_CSTRING("A ghost window."),
       aData);
   }
 
-  MOZ_COLLECT_REPORT(
-    "ghost-windows", KIND_OTHER, UNITS_COUNT, ghostWindows.Count(),
-"The number of ghost windows present (the number of nodes underneath "
-"explicit/window-objects/top(none)/ghost, modulo race conditions).  A ghost "
-"window is not shown in any tab, does not share a domain with any non-detached "
-"windows, and has met these criteria for at least "
-"memory.ghost_window_timeout_seconds, or has survived a round of "
-"about:memory's minimize memory usage button.\n\n"
-"Ghost windows can happen legitimately, but they are often indicative of "
-"leaks in the browser or add-ons.");
-
   WindowPaths windowPaths;
   WindowPaths topWindowPaths;
 
   // Collect window memory usage.
   SizeOfState fakeState(nullptr);   // this won't be used
   nsWindowSizes windowTotalSizes(fakeState);
   nsCOMPtr<amIAddonManager> addonManager;
   if (XRE_IsParentProcess()) {
@@ -756,46 +745,39 @@ nsWindowMemoryReporter::CheckForGhostWin
     NS_WARNING("GetWindowsTable returned null");
     return;
   }
 
   mLastCheckForGhostWindows = TimeStamp::NowLoRes();
   KillCheckTimer();
 
   nsTHashtable<nsCStringHashKey> nonDetachedWindowDomains;
-  nsDataHashtable<nsISupportsHashKey, nsCString> domainMap;
 
   // Populate nonDetachedWindowDomains.
   for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
     // Null outer window implies null top, but calling GetTop() when there's no
     // outer window causes us to spew debug warnings.
     nsGlobalWindow* window = iter.UserData();
     if (!window->GetOuterWindow() || !window->GetTopInternal()) {
       // This window is detached, so we don't care about its domain.
       continue;
     }
 
     nsCOMPtr<nsIURI> uri = GetWindowURI(window);
     nsAutoCString domain;
     if (uri) {
-      domain = domainMap.LookupForAdd(uri).OrInsert([&]() {
-        nsCString d;
-        tldService->GetBaseDomain(uri, 0, d);
-        return d;
-      });
+      tldService->GetBaseDomain(uri, 0, domain);
     }
-
     nonDetachedWindowDomains.PutEntry(domain);
   }
 
   // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
   // if it's not null.
   uint32_t ghostTimeout = GetGhostTimeout();
   TimeStamp now = mLastCheckForGhostWindows;
-  mGhostWindowCount = 0;
   for (auto iter = mDetachedWindows.Iter(); !iter.Done(); iter.Next()) {
     nsWeakPtr weakKey = do_QueryInterface(iter.Key());
     nsCOMPtr<mozIDOMWindow> iwindow = do_QueryReferent(weakKey);
     if (!iwindow) {
       // The window object has been destroyed.  Stop tracking its weak ref in
       // our hashtable.
       iter.Remove();
       continue;
@@ -836,29 +818,33 @@ nsWindowMemoryReporter::CheckForGhostWin
       // This window does not share a domain with a non-detached window, so it
       // meets ghost criterion (2).
       if (timeStamp.IsNull()) {
         // This may become a ghost window later; start its clock.
         timeStamp = now;
       } else if ((now - timeStamp).ToSeconds() > ghostTimeout) {
         // This definitely is a ghost window, so add it to aOutGhostIDs, if
         // that is not null.
-        mGhostWindowCount++;
         if (aOutGhostIDs && window) {
           aOutGhostIDs->PutEntry(window->WindowID());
         }
       }
     }
   }
 }
 
+NS_IMPL_ISUPPORTS(nsWindowMemoryReporter::GhostWindowsReporter,
+                  nsIMemoryReporter)
+
 /* static */ int64_t
-nsWindowMemoryReporter::GhostWindowsDistinguishedAmount()
+nsWindowMemoryReporter::GhostWindowsReporter::DistinguishedAmount()
 {
-  return sWindowReporter->mGhostWindowCount;
+  nsTHashtable<nsUint64HashKey> ghostWindows;
+  sWindowReporter->CheckForGhostWindows(&ghostWindows);
+  return ghostWindows.Count();
 }
 
 void
 nsWindowMemoryReporter::KillCheckTimer()
 {
   if (mCheckTimer) {
     mCheckTimer->Cancel();
     mCheckTimer = nullptr;
--- a/dom/base/nsWindowMemoryReporter.h
+++ b/dom/base/nsWindowMemoryReporter.h
@@ -159,21 +159,50 @@ public:
    * to become ghost windows in the first place.
    */
   static void UnlinkGhostWindows();
 #endif
 
   static nsWindowMemoryReporter* Get();
   void ObserveDOMWindowDetached(nsGlobalWindow* aWindow);
 
-  static int64_t GhostWindowsDistinguishedAmount();
-
 private:
   ~nsWindowMemoryReporter();
 
+  /**
+   * nsGhostWindowReporter generates the "ghost-windows" report, which counts
+   * the number of ghost windows present.
+   */
+  class GhostWindowsReporter final : public nsIMemoryReporter
+  {
+    ~GhostWindowsReporter() {}
+  public:
+    NS_DECL_ISUPPORTS
+
+    static int64_t DistinguishedAmount();
+
+    NS_IMETHOD
+    CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+                   bool aAnonymize) override
+    {
+      MOZ_COLLECT_REPORT(
+        "ghost-windows", KIND_OTHER, UNITS_COUNT, DistinguishedAmount(),
+"The number of ghost windows present (the number of nodes underneath "
+"explicit/window-objects/top(none)/ghost, modulo race conditions).  A ghost "
+"window is not shown in any tab, does not share a domain with any non-detached "
+"windows, and has met these criteria for at least "
+"memory.ghost_window_timeout_seconds, or has survived a round of "
+"about:memory's minimize memory usage button.\n\n"
+"Ghost windows can happen legitimately, but they are often indicative of "
+"leaks in the browser or add-ons.");
+
+      return NS_OK;
+    }
+  };
+
   // Protect ctor, use Init() instead.
   nsWindowMemoryReporter();
 
   /**
    * Get the number of seconds for which a window must satisfy ghost criteria
    * (1) and (2) before we deem that it satisfies criterion (3).
    */
   uint32_t GetGhostTimeout();
@@ -226,14 +255,12 @@ private:
    */
   mozilla::TimeStamp mLastCheckForGhostWindows;
 
   nsCOMPtr<nsITimer> mCheckTimer;
 
   bool mCycleCollectorIsRunning;
 
   bool mCheckTimerWaitingForCCEnd;
-
-  int64_t mGhostWindowCount;
 };
 
 #endif // nsWindowMemoryReporter_h__
 
--- a/editor/libeditor/tests/test_CF_HTML_clipboard.html
+++ b/editor/libeditor/tests/test_CF_HTML_clipboard.html
@@ -133,17 +133,17 @@ function runTest() {
           if (gTestIndex == gTests.length)
             SimpleTest.finish();
           else
             runTest();
         }, {once: true});
         win.focus();
       }, 0);
     }, {once: true});
-    iframe.src = "data:text/html,";
+    iframe.srcdoc = "foo";
   }, SimpleTest.finish);
 }
 
 var isMac = ("nsILocalFileMac" in SpecialPowers.Ci);
 if (isMac)
   SimpleTest.waitForFocus(runTest);
 else {
   // This test is not yet supported on non-Mac platforms, see bug 574005.
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -818,16 +818,19 @@ public:
   }
 
   void RemoveUserData(UserDataKey *key) {
     mUserData.RemoveAndDestroy(key);
   }
 
   const RefPtr<UnscaledFont>& GetUnscaledFont() const { return mUnscaledFont; }
 
+  virtual cairo_scaled_font_t* GetCairoScaledFont() { return nullptr; }
+  virtual void SetCairoScaledFont(cairo_scaled_font_t* font) {}
+
 protected:
   explicit ScaledFont(const RefPtr<UnscaledFont>& aUnscaledFont)
     : mUnscaledFont(aUnscaledFont)
   {}
 
   UserData mUserData;
   RefPtr<UnscaledFont> mUnscaledFont;
 };
--- a/gfx/2d/ScaledFontBase.h
+++ b/gfx/2d/ScaledFontBase.h
@@ -43,18 +43,18 @@ public:
   virtual SkTypeface* GetSkTypeface() { return mTypeface; }
 #endif
 
   // Not true, but required to instantiate a ScaledFontBase.
   virtual FontType GetType() const { return FontType::SKIA; }
 
 #ifdef USE_CAIRO_SCALED_FONT
   bool PopulateCairoScaledFont();
-  cairo_scaled_font_t* GetCairoScaledFont() { return mScaledFont; }
-  void SetCairoScaledFont(cairo_scaled_font_t* font);
+  virtual cairo_scaled_font_t* GetCairoScaledFont() { return mScaledFont; }
+  virtual void SetCairoScaledFont(cairo_scaled_font_t* font);
 #endif
 
 protected:
   friend class DrawTargetSkia;
 #ifdef USE_SKIA
   SkTypeface* mTypeface;
   SkPath GetSkiaPathForGlyphs(const GlyphBuffer &aBuffer);
 #endif
--- a/gfx/thebes/gfxAndroidPlatform.cpp
+++ b/gfx/thebes/gfxAndroidPlatform.cpp
@@ -257,22 +257,16 @@ gfxAndroidPlatform::CreateFontGroup(cons
 }
 
 FT_Library
 gfxAndroidPlatform::GetFTLibrary()
 {
     return gPlatformFTLibrary;
 }
 
-already_AddRefed<ScaledFont>
-gfxAndroidPlatform::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
-{
-    return GetScaledFontForFontWithCairoSkia(aTarget, aFont);
-}
-
 bool
 gfxAndroidPlatform::FontHintingEnabled()
 {
     // In "mobile" builds, we sometimes use non-reflow-zoom, so we
     // might not want hinting.  Let's see.
 
 #ifdef MOZ_WIDGET_ANDROID
     // On Android, we currently only use gecko to render web
--- a/gfx/thebes/gfxAndroidPlatform.h
+++ b/gfx/thebes/gfxAndroidPlatform.h
@@ -27,19 +27,16 @@ public:
         return (gfxAndroidPlatform*) gfxPlatform::GetPlatform();
     }
 
     virtual already_AddRefed<gfxASurface>
     CreateOffscreenSurface(const IntSize& aSize,
                            gfxImageFormat aFormat) override;
     
     virtual gfxImageFormat GetOffscreenFormat() override { return mOffscreenFormat; }
-    
-    already_AddRefed<mozilla::gfx::ScaledFont>
-      GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override;
 
     // to support IPC font list (sharing between chrome and content)
     void GetSystemFontList(InfallibleTArray<FontListEntry>* retValue);
 
     // platform implementations of font functions
     virtual gfxPlatformFontList* CreatePlatformFontList() override;
 
     virtual void GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
--- a/gfx/thebes/gfxDWriteFonts.cpp
+++ b/gfx/thebes/gfxDWriteFonts.cpp
@@ -455,17 +455,17 @@ uint32_t
 gfxDWriteFont::GetSpaceGlyph()
 {
     return mSpaceGlyph;
 }
 
 bool
 gfxDWriteFont::SetupCairoFont(DrawTarget* aDrawTarget)
 {
-    cairo_scaled_font_t *scaledFont = GetCairoScaledFont();
+    cairo_scaled_font_t *scaledFont = InitCairoScaledFont();
     if (cairo_scaled_font_status(scaledFont) != CAIRO_STATUS_SUCCESS) {
         // Don't cairo_set_scaled_font as that would propagate the error to
         // the cairo_t, precluding any further drawing.
         return false;
     }
     cairo_set_scaled_font(gfxFont::RefCairo(aDrawTarget), scaledFont);
     return true;
 }
@@ -492,17 +492,17 @@ gfxDWriteFont::CairoFontFace()
             ((gfxDWriteFontEntry*)mFontEntry.get())->mFont, mFontFace);
 #endif
     }
     return mCairoFontFace;
 }
 
 
 cairo_scaled_font_t *
-gfxDWriteFont::GetCairoScaledFont()
+gfxDWriteFont::InitCairoScaledFont()
 {
     if (!mScaledFont) {
         cairo_matrix_t sizeMatrix;
         cairo_matrix_t identityMatrix;
 
         cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
         cairo_matrix_init_identity(&identityMatrix);
 
@@ -674,52 +674,51 @@ gfxDWriteFont::AddSizeOfIncludingThis(Ma
 {
     aSizes->mFontInstances += aMallocSizeOf(this);
     AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
 }
 
 already_AddRefed<ScaledFont>
 gfxDWriteFont::GetScaledFont(mozilla::gfx::DrawTarget *aTarget)
 {
-  bool wantCairo = aTarget->GetBackendType() == BackendType::CAIRO;
-  if (mAzureScaledFont && mAzureScaledFontIsCairo == wantCairo) {
-    RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
-    return scaledFont.forget();
-  }
-
-  NativeFont nativeFont;
-  nativeFont.mType = NativeFontType::DWRITE_FONT_FACE;
-  nativeFont.mFont = GetFontFace();
+    if (!mAzureScaledFont) {
+        gfxDWriteFontEntry *fe =
+            static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
+        bool useEmbeddedBitmap =
+            fe->IsCJKFont() &&
+            HasBitmapStrikeForSize(NS_lround(mAdjustedSize));
+        bool forceGDI = GetForceGDIClassic();
 
-  if (wantCairo) {
-    mAzureScaledFont = Factory::CreateScaledFontWithCairo(nativeFont,
-                                                        GetUnscaledFont(),
-                                                        GetAdjustedSize(),
-                                                        GetCairoScaledFont());
-  } else {
-    gfxDWriteFontEntry *fe =
-        static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
-    bool useEmbeddedBitmap = (fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize)));
-    bool forceGDI = GetForceGDIClassic();
+        IDWriteRenderingParams* params = gfxWindowsPlatform::GetPlatform()->GetRenderingParams(
+            mUseClearType ?
+                (forceGDI ?
+                    gfxWindowsPlatform::TEXT_RENDERING_GDI_CLASSIC :
+                    gfxWindowsPlatform::TEXT_RENDERING_NORMAL) :
+                gfxWindowsPlatform::TEXT_RENDERING_NO_CLEARTYPE);
 
-    IDWriteRenderingParams* params = gfxWindowsPlatform::GetPlatform()->GetRenderingParams(
-      mUseClearType ?
-        (forceGDI ?
-          gfxWindowsPlatform::TEXT_RENDERING_GDI_CLASSIC : gfxWindowsPlatform::TEXT_RENDERING_NORMAL) :
-        gfxWindowsPlatform::TEXT_RENDERING_NO_CLEARTYPE);
-
-    const gfxFontStyle* fontStyle = GetStyle();
-    mAzureScaledFont =
+        const gfxFontStyle* fontStyle = GetStyle();
+        mAzureScaledFont =
             Factory::CreateScaledFontForDWriteFont(mFontFace, fontStyle,
                                                    GetUnscaledFont(),
                                                    GetAdjustedSize(),
                                                    useEmbeddedBitmap,
                                                    forceGDI,
                                                    params,
                                                    params->GetGamma(),
                                                    params->GetEnhancedContrast());
-  }
-
-  mAzureScaledFontIsCairo = wantCairo;
+        if (!mAzureScaledFont) {
+            return nullptr;
+        }
+    }
 
-  RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
-  return scaledFont.forget();
+    if (aTarget->GetBackendType() == BackendType::CAIRO) {
+        if (!mAzureScaledFont->GetCairoScaledFont()) {
+            cairo_scaled_font_t* cairoScaledFont = InitCairoScaledFont();
+            if (!cairoScaledFont) {
+                return nullptr;
+            }
+            mAzureScaledFont->SetCairoScaledFont(cairoScaledFont);
+        }
+    }
+
+    RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
+    return scaledFont.forget();
 }
--- a/gfx/thebes/gfxDWriteFonts.h
+++ b/gfx/thebes/gfxDWriteFonts.h
@@ -66,19 +66,19 @@ public:
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const override;
 
     virtual FontType GetType() const override { return FONT_TYPE_DWRITE; }
 
     virtual already_AddRefed<mozilla::gfx::ScaledFont>
     GetScaledFont(mozilla::gfx::DrawTarget *aTarget) override;
 
-    virtual cairo_scaled_font_t *GetCairoScaledFont() override;
+protected:
+    cairo_scaled_font_t *InitCairoScaledFont();
 
-protected:
     virtual const Metrics& GetHorizontalMetrics() override;
 
     bool GetFakeMetricsForArialBlack(DWRITE_FONT_METRICS *aFontMetrics);
 
     void ComputeMetrics(AntialiasOption anAAOption);
 
     bool HasBitmapStrikeForSize(uint32_t aSize);
 
@@ -100,13 +100,12 @@ protected:
     mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey,int32_t>> mGlyphWidths;
 
     uint32_t mSpaceGlyph;
 
     bool mNeedsOblique;
     bool mNeedsBold;
     bool mUseSubpixelPositions;
     bool mAllowManualShowGlyphs;
-    bool mAzureScaledFontIsCairo;
     static bool mUseClearType;
 };
 
 #endif
--- a/gfx/thebes/gfxFT2FontBase.cpp
+++ b/gfx/thebes/gfxFT2FontBase.cpp
@@ -40,17 +40,17 @@ gfxFT2FontBase::~gfxFT2FontBase()
 uint32_t
 gfxFT2FontBase::GetGlyph(uint32_t aCharCode)
 {
     // FcFreeTypeCharIndex needs to lock the FT_Face and can end up searching
     // through all the postscript glyph names in the font.  Therefore use a
     // lightweight cache, which is stored on the cairo_font_face_t.
 
     cairo_font_face_t *face =
-        cairo_scaled_font_get_font_face(CairoScaledFont());
+        cairo_scaled_font_get_font_face(GetCairoScaledFont());
 
     if (cairo_font_face_status(face) != CAIRO_STATUS_SUCCESS)
         return 0;
 
     // This cache algorithm and size is based on what is done in
     // cairo_scaled_font_text_to_glyphs and pango_fc_font_real_get_glyph.  I
     // think the concept is that adjacent characters probably come mostly from
     // one Unicode block.  This assumption is probably not so valid with
@@ -108,17 +108,17 @@ gfxFT2FontBase::GetGlyphExtents(uint32_t
     glyphs[0].index = aGlyph;
     glyphs[0].x = 0.0;
     glyphs[0].y = 0.0;
     // cairo does some caching for us here but perhaps a small gain could be
     // made by caching more.  It is usually only the advance that is needed,
     // so caching only the advance could allow many requests to be cached with
     // little memory use.  Ideally this cache would be merged with
     // gfxGlyphExtents.
-    cairo_scaled_font_glyph_extents(CairoScaledFont(), glyphs, 1, aExtents);
+    cairo_scaled_font_glyph_extents(GetCairoScaledFont(), glyphs, 1, aExtents);
 }
 
 // aScale is intended for a 16.16 x/y_scale of an FT_Size_Metrics
 static inline FT_Long
 ScaleRoundDesignUnits(FT_Short aDesignMetric, FT_Fixed aScale)
 {
     FT_Long fixed26dot6 = FT_MulFix(aDesignMetric, aScale);
     return ROUND_26_6_TO_INT(fixed26dot6);
@@ -481,17 +481,17 @@ bool
 gfxFT2FontBase::SetupCairoFont(DrawTarget* aDrawTarget)
 {
     // The scaled font ctm is not relevant right here because
     // cairo_set_scaled_font does not record the scaled font itself, but
     // merely the font_face, font_matrix, font_options.  The scaled_font used
     // for the target can be different from the scaled_font passed to
     // cairo_set_scaled_font.  (Unfortunately we have measured only for an
     // identity ctm.)
-    cairo_scaled_font_t *cairoFont = CairoScaledFont();
+    cairo_scaled_font_t *cairoFont = GetCairoScaledFont();
 
     if (cairo_scaled_font_status(cairoFont) != CAIRO_STATUS_SUCCESS) {
         // Don't cairo_set_scaled_font as that would propagate the error to
         // the cairo_t, precluding any further drawing.
         return false;
     }
     // Thoughts on which font_options to set on the context:
     //
--- a/gfx/thebes/gfxFT2FontBase.h
+++ b/gfx/thebes/gfxFT2FontBase.h
@@ -26,17 +26,16 @@ public:
     virtual uint32_t GetSpaceGlyph() override;
     virtual bool ProvidesGetGlyph() const override { return true; }
     virtual uint32_t GetGlyph(uint32_t unicode,
                               uint32_t variation_selector) override;
     virtual bool ProvidesGlyphWidths() const override { return true; }
     virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget,
                                   uint16_t aGID) override;
 
-    cairo_scaled_font_t *CairoScaledFont() { return mScaledFont; };
     virtual bool SetupCairoFont(DrawTarget* aDrawTarget) override;
 
     virtual FontType GetType() const override { return FONT_TYPE_FT2; }
 
 private:
     uint32_t GetCharExtents(char aChar, cairo_text_extents_t* aExtents);
     void InitMetrics();
 
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -29,16 +29,19 @@
 
 #include "mozilla/Logging.h"
 #include "prinit.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/gfx/2D.h"
 
+using namespace mozilla;
+using namespace mozilla::gfx;
+
 /**
  * gfxFT2Font
  */
 
 bool
 gfxFT2Font::ShapeText(DrawTarget     *aDrawTarget,
                       const char16_t *aText,
                       uint32_t        aOffset,
@@ -152,32 +155,49 @@ gfxFT2Font::AddRange(const char16_t *aTe
             details.mYOffset = 0;
             gfxShapedText::CompressedGlyph g;
             g.SetComplex(charGlyphs[aOffset].IsClusterStart(), true, 1);
             aShapedText->SetGlyphs(aOffset, g, &details);
         }
     }
 }
 
-gfxFT2Font::gfxFT2Font(const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont,
+gfxFT2Font::gfxFT2Font(const RefPtr<UnscaledFontFreeType>& aUnscaledFont,
                        cairo_scaled_font_t *aCairoFont,
                        FT2FontEntry *aFontEntry,
                        const gfxFontStyle *aFontStyle,
                        bool aNeedsBold)
     : gfxFT2FontBase(aUnscaledFont, aCairoFont, aFontEntry, aFontStyle)
     , mCharGlyphCache(32)
 {
     NS_ASSERTION(mFontEntry, "Unable to find font entry for font.  Something is whack.");
     mApplySyntheticBold = aNeedsBold;
 }
 
 gfxFT2Font::~gfxFT2Font()
 {
 }
 
+already_AddRefed<ScaledFont>
+gfxFT2Font::GetScaledFont(DrawTarget *aTarget)
+{
+    if (!mAzureScaledFont) {
+        NativeFont nativeFont;
+        nativeFont.mType = NativeFontType::CAIRO_FONT_FACE;
+        nativeFont.mFont = GetCairoScaledFont();
+        mAzureScaledFont =
+            Factory::CreateScaledFontForNativeFont(nativeFont,
+                                                   GetUnscaledFont(),
+                                                   GetAdjustedSize());
+    }
+
+    RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
+    return scaledFont.forget();
+}
+
 void
 gfxFT2Font::FillGlyphDataForChar(FT_Face face, uint32_t ch, CachedGlyphData *gd)
 {
     if (!face->charmap || face->charmap->encoding != FT_ENCODING_UNICODE) {
         FT_Select_Charmap(face, FT_ENCODING_UNICODE);
     }
     FT_UInt gid = FT_Get_Char_Index(face, ch);
 
@@ -203,24 +223,24 @@ gfxFT2Font::FillGlyphDataForChar(FT_Face
 
     gd->glyphIndex = gid;
     gd->lsbDelta = face->glyph->lsb_delta;
     gd->rsbDelta = face->glyph->rsb_delta;
     gd->xAdvance = face->glyph->advance.x;
 }
 
 void
-gfxFT2Font::AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+gfxFT2Font::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                    FontCacheSizes* aSizes) const
 {
     gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
     aSizes->mFontInstances +=
         mCharGlyphCache.ShallowSizeOfExcludingThis(aMallocSizeOf);
 }
 
 void
-gfxFT2Font::AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+gfxFT2Font::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                                    FontCacheSizes* aSizes) const
 {
     aSizes->mFontInstances += aMallocSizeOf(this);
     AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
 }
 
--- a/gfx/thebes/gfxFT2Fonts.h
+++ b/gfx/thebes/gfxFT2Fonts.h
@@ -23,16 +23,19 @@ public: // new functions
                cairo_scaled_font_t *aCairoFont,
                FT2FontEntry *aFontEntry,
                const gfxFontStyle *aFontStyle,
                bool aNeedsBold);
     virtual ~gfxFT2Font ();
 
     FT2FontEntry *GetFontEntry();
 
+    virtual already_AddRefed<mozilla::gfx::ScaledFont>
+    GetScaledFont(DrawTarget *aTarget) override;
+
     virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const override;
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const override;
 
 protected:
     struct CachedGlyphData {
         CachedGlyphData()
--- a/gfx/thebes/gfxFT2Utils.h
+++ b/gfx/thebes/gfxFT2Utils.h
@@ -26,22 +26,22 @@ typedef struct FT_FaceRec_* FT_Face;
  * Do not attempt to call into Cairo within the scope of gfxFT2LockedFace,
  * as that may accidentally try to re-lock the face within Cairo itself
  * and thus deadlock.
  */
 class gfxFT2LockedFace {
 public:
     explicit gfxFT2LockedFace(gfxFT2FontBase *aFont) :
         mGfxFont(aFont),
-        mFace(cairo_ft_scaled_font_lock_face(aFont->CairoScaledFont()))
+        mFace(cairo_ft_scaled_font_lock_face(aFont->GetCairoScaledFont()))
     { }
     ~gfxFT2LockedFace()
     {
         if (mFace) {
-            cairo_ft_scaled_font_unlock_face(mGfxFont->CairoScaledFont());
+            cairo_ft_scaled_font_unlock_face(mGfxFont->GetCairoScaledFont());
         }
     }
 
     FT_Face get() { return mFace; };
 
     /**
      * Get the glyph id for a Unicode character representable by a single
      * glyph, or return zero if there is no such glyph.  This does no caching,
--- a/gfx/thebes/gfxFcPlatformFontList.cpp
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -50,16 +50,30 @@ using namespace mozilla::unicode;
                                LogLevel::Debug, args)
 #define LOG_FONTLIST_ENABLED() MOZ_LOG_TEST( \
                                    gfxPlatform::GetLog(eGfxLog_fontlist), \
                                    LogLevel::Debug)
 #define LOG_CMAPDATA_ENABLED() MOZ_LOG_TEST( \
                                    gfxPlatform::GetLog(eGfxLog_cmapdata), \
                                    LogLevel::Debug)
 
+template <>
+class nsAutoRefTraits<FcFontSet> : public nsPointerRefTraits<FcFontSet>
+{
+public:
+    static void Release(FcFontSet *ptr) { FcFontSetDestroy(ptr); }
+};
+
+template <>
+class nsAutoRefTraits<FcObjectSet> : public nsPointerRefTraits<FcObjectSet>
+{
+public:
+    static void Release(FcObjectSet *ptr) { FcObjectSetDestroy(ptr); }
+};
+
 static const FcChar8*
 ToFcChar8Ptr(const char* aStr)
 {
     return reinterpret_cast<const FcChar8*>(aStr);
 }
 
 static const char*
 ToCharPtr(const FcChar8 *aStr)
@@ -1176,26 +1190,42 @@ gfxFontconfigFontFamily::~gfxFontconfigF
 }
 
 gfxFontconfigFont::gfxFontconfigFont(const RefPtr<UnscaledFontFontconfig>& aUnscaledFont,
                                      cairo_scaled_font_t *aScaledFont,
                                      FcPattern *aPattern,
                                      gfxFloat aAdjustedSize,
                                      gfxFontEntry *aFontEntry,
                                      const gfxFontStyle *aFontStyle,
-                                     bool aNeedsBold) :
-    gfxFontconfigFontBase(aUnscaledFont, aScaledFont, aPattern, aFontEntry, aFontStyle)
+                                     bool aNeedsBold)
+    : gfxFT2FontBase(aUnscaledFont, aScaledFont, aFontEntry, aFontStyle)
+    , mPattern(aPattern)
 {
     mAdjustedSize = aAdjustedSize;
 }
 
 gfxFontconfigFont::~gfxFontconfigFont()
 {
 }
 
+already_AddRefed<ScaledFont>
+gfxFontconfigFont::GetScaledFont(mozilla::gfx::DrawTarget *aTarget)
+{
+    if (!mAzureScaledFont) {
+        mAzureScaledFont =
+            Factory::CreateScaledFontForFontconfigFont(GetCairoScaledFont(),
+                                                       GetPattern(),
+                                                       GetUnscaledFont(),
+                                                       GetAdjustedSize());
+    }
+
+    RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
+    return scaledFont.forget();
+}
+
 gfxFcPlatformFontList::gfxFcPlatformFontList()
     : mLocalNames(64)
     , mGenericMappings(32)
     , mFcSubstituteCache(64)
     , mLastConfig(nullptr)
     , mAlwaysUseFontconfigGenerics(true)
 {
     // if the rescan interval is set, start the timer
--- a/gfx/thebes/gfxFcPlatformFontList.h
+++ b/gfx/thebes/gfxFcPlatformFontList.h
@@ -6,32 +6,32 @@
 #ifndef GFXFCPLATFORMFONTLIST_H_
 #define GFXFCPLATFORMFONTLIST_H_
 
 #include "gfxFont.h"
 #include "gfxFontEntry.h"
 #include "gfxFT2FontBase.h"
 #include "gfxPlatformFontList.h"
 #include "mozilla/mozalloc.h"
+#include "nsAutoRef.h"
 #include "nsClassHashtable.h"
 
 #include <fontconfig/fontconfig.h>
 #include "ft2build.h"
 #include FT_FREETYPE_H
 #include FT_TRUETYPE_TABLES_H
 #include <cairo.h>
 #include <cairo-ft.h>
 
-#include "gfxFontconfigUtils.h" // xxx - only for nsAutoRefTraits<FcPattern>, etc.
-
 template <>
-class nsAutoRefTraits<FcObjectSet> : public nsPointerRefTraits<FcObjectSet>
+class nsAutoRefTraits<FcPattern> : public nsPointerRefTraits<FcPattern>
 {
 public:
-    static void Release(FcObjectSet *ptr) { FcObjectSetDestroy(ptr); }
+    static void Release(FcPattern *ptr) { FcPatternDestroy(ptr); }
+    static void AddRef(FcPattern *ptr) { FcPatternReference(ptr); }
 };
 
 template <>
 class nsAutoRefTraits<FcConfig> : public nsPointerRefTraits<FcConfig>
 {
 public:
     static void Release(FcConfig *ptr) { FcConfigDestroy(ptr); }
     static void AddRef(FcConfig *ptr) { FcConfigReference(ptr); }
@@ -204,28 +204,36 @@ protected:
 
     nsTArray<nsCountedRef<FcPattern> > mFontPatterns;
 
     bool      mContainsAppFonts;
     bool      mHasNonScalableFaces;
     bool      mForceScalable;
 };
 
-class gfxFontconfigFont : public gfxFontconfigFontBase {
+class gfxFontconfigFont : public gfxFT2FontBase {
 public:
     gfxFontconfigFont(const RefPtr<mozilla::gfx::UnscaledFontFontconfig> &aUnscaledFont,
                       cairo_scaled_font_t *aScaledFont,
                       FcPattern *aPattern,
                       gfxFloat aAdjustedSize,
                       gfxFontEntry *aFontEntry,
                       const gfxFontStyle *aFontStyle,
                       bool aNeedsBold);
 
-protected:
+    virtual FontType GetType() const override { return FONT_TYPE_FONTCONFIG; }
+    virtual FcPattern *GetPattern() const { return mPattern; }
+
+    virtual already_AddRefed<mozilla::gfx::ScaledFont>
+    GetScaledFont(DrawTarget *aTarget) override;
+
+private:
     virtual ~gfxFontconfigFont();
+
+    nsCountedRef<FcPattern> mPattern;
 };
 
 class gfxFcPlatformFontList : public gfxPlatformFontList {
 public:
     gfxFcPlatformFontList();
 
     static gfxFcPlatformFontList* PlatformFontList() {
         return static_cast<gfxFcPlatformFontList*>(sPlatformFontList);
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -1441,17 +1441,17 @@ public:
             // outside the hinted outline.
             // Also NOTE: it is relatively expensive to request this,
             // as it does not use cached glyph extents in the font.
     } BoundingBoxType;
 
     const nsString& GetName() const { return mFontEntry->Name(); }
     const gfxFontStyle *GetStyle() const { return &mStyle; }
 
-    virtual cairo_scaled_font_t* GetCairoScaledFont() { return mScaledFont; }
+    cairo_scaled_font_t* GetCairoScaledFont() { return mScaledFont; }
 
     virtual mozilla::UniquePtr<gfxFont>
     CopyWithAntialiasOption(AntialiasOption anAAOption) {
         // platforms where this actually matters should override
         return nullptr;
     }
 
     gfxFloat GetAdjustedSize() const {
@@ -1826,20 +1826,17 @@ public:
     } FontType;
 
     virtual FontType GetType() const = 0;
 
     const RefPtr<mozilla::gfx::UnscaledFont>& GetUnscaledFont() const {
         return mUnscaledFont;
     }
 
-    virtual already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(DrawTarget* aTarget)
-    {
-        return gfxPlatform::GetPlatform()->GetScaledFontForFont(aTarget, this);
-    }
+    virtual already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(DrawTarget* aTarget) = 0;
 
     bool KerningDisabled() {
         return mKerningSet && !mKerningEnabled;
     }
 
     /**
      * Subclass this object to be notified of glyph changes. Delete the object
      * when no longer needed.
deleted file mode 100644
--- a/gfx/thebes/gfxFontconfigUtils.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef GFX_FONTCONFIG_UTILS_H
-#define GFX_FONTCONFIG_UTILS_H
-
-#include "gfxPlatform.h"
-
-#include "nsAutoRef.h"
-#include "gfxFT2FontBase.h"
-
-#include <fontconfig/fontconfig.h>
-
-
-template <>
-class nsAutoRefTraits<FcPattern> : public nsPointerRefTraits<FcPattern>
-{
-public:
-    static void Release(FcPattern *ptr) { FcPatternDestroy(ptr); }
-    static void AddRef(FcPattern *ptr) { FcPatternReference(ptr); }
-};
-
-template <>
-class nsAutoRefTraits<FcFontSet> : public nsPointerRefTraits<FcFontSet>
-{
-public:
-    static void Release(FcFontSet *ptr) { FcFontSetDestroy(ptr); }
-};
-
-template <>
-class nsAutoRefTraits<FcCharSet> : public nsPointerRefTraits<FcCharSet>
-{
-public:
-    static void Release(FcCharSet *ptr) { FcCharSetDestroy(ptr); }
-};
-
-class gfxFontconfigFontBase : public gfxFT2FontBase {
-public:
-    gfxFontconfigFontBase(const RefPtr<mozilla::gfx::UnscaledFontFontconfig>& aUnscaledFont,
-                          cairo_scaled_font_t *aScaledFont,
-                          FcPattern *aPattern,
-                          gfxFontEntry *aFontEntry,
-                          const gfxFontStyle *aFontStyle)
-      : gfxFT2FontBase(aUnscaledFont, aScaledFont, aFontEntry, aFontStyle)
-      , mPattern(aPattern) { }
-
-    virtual FontType GetType() const override { return FONT_TYPE_FONTCONFIG; }
-    virtual FcPattern *GetPattern() const { return mPattern; }
-
-private:
-    nsCountedRef<FcPattern> mPattern;
-};
-
-#endif /* GFX_FONTCONFIG_UTILS_H */
--- a/gfx/thebes/gfxGDIFont.cpp
+++ b/gfx/thebes/gfxGDIFont.cpp
@@ -17,16 +17,17 @@
 #include "gfxFontConstants.h"
 #include "gfxTextRun.h"
 
 #include "cairo-win32.h"
 
 #define ROUND(x) floor((x) + 0.5)
 
 using namespace mozilla;
+using namespace mozilla::gfx;
 using namespace mozilla::unicode;
 
 static inline cairo_antialias_t
 GetCairoAntialiasOption(gfxFont::AntialiasOption anAntialiasOption)
 {
     switch (anAntialiasOption) {
     default:
     case gfxFont::kAntialiasDefault:
@@ -130,16 +131,37 @@ gfxGDIFont::SetupCairoFont(DrawTarget* a
         // Don't cairo_set_scaled_font as that would propagate the error to
         // the cairo_t, precluding any further drawing.
         return false;
     }
     cairo_set_scaled_font(gfxFont::RefCairo(aDrawTarget), mScaledFont);
     return true;
 }
 
+already_AddRefed<ScaledFont>
+gfxGDIFont::GetScaledFont(DrawTarget *aTarget)
+{
+    if (!mAzureScaledFont) {
+        NativeFont nativeFont;
+        nativeFont.mType = NativeFontType::GDI_FONT_FACE;
+        LOGFONT lf;
+        GetObject(GetHFONT(), sizeof(LOGFONT), &lf);
+        nativeFont.mFont = &lf;
+
+        mAzureScaledFont =
+          Factory::CreateScaledFontWithCairo(nativeFont,
+                                             GetUnscaledFont(),
+                                             GetAdjustedSize(),
+                                             GetCairoScaledFont());
+    }
+
+    RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
+    return scaledFont.forget();
+}
+
 gfxFont::RunMetrics
 gfxGDIFont::Measure(const gfxTextRun *aTextRun,
                     uint32_t aStart, uint32_t aEnd,
                     BoundingBoxType aBoundingBoxType,
                     DrawTarget *aRefDrawTarget,
                     Spacing *aSpacing,
                     gfx::ShapedTextFlags aOrientation)
 {
--- a/gfx/thebes/gfxGDIFont.h
+++ b/gfx/thebes/gfxGDIFont.h
@@ -23,24 +23,26 @@ public:
                const gfxFontStyle *aFontStyle,
                bool aNeedsBold,
                AntialiasOption anAAOption = kAntialiasDefault);
 
     virtual ~gfxGDIFont();
 
     HFONT GetHFONT() { return mFont; }
 
-    cairo_font_face_t   *CairoFontFace() { return mFontFace; }
-    cairo_scaled_font_t *CairoScaledFont() { return mScaledFont; }
+    cairo_font_face_t* CairoFontFace() { return mFontFace; }
 
     /* overrides for the pure virtual methods in gfxFont */
     virtual uint32_t GetSpaceGlyph() override;
 
     virtual bool SetupCairoFont(DrawTarget* aDrawTarget) override;
 
+    virtual already_AddRefed<mozilla::gfx::ScaledFont>
+    GetScaledFont(DrawTarget *aTarget) override;
+
     /* override Measure to add padding for antialiasing */
     virtual RunMetrics Measure(const gfxTextRun *aTextRun,
                                uint32_t aStart, uint32_t aEnd,
                                BoundingBoxType aBoundingBoxType,
                                DrawTarget *aDrawTargetForTightBoundingBox,
                                Spacing *aSpacing,
                                mozilla::gfx::ShapedTextFlags aOrientation) override;
 
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -1271,27 +1271,16 @@ gfxPlatform::GetWrappedDataSourceSurface
   // to make sure aSurface stays alive until we are done with the data.
   auto *srcSurfUD = new DependentSourceSurfaceUserData;
   srcSurfUD->mSurface = aSurface;
   result->AddUserData(&kThebesSurface, srcSurfUD, SourceSurfaceDestroyed);
 
   return result.forget();
 }
 
-already_AddRefed<ScaledFont>
-gfxPlatform::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
-{
-  NativeFont nativeFont;
-  nativeFont.mType = NativeFontType::CAIRO_FONT_FACE;
-  nativeFont.mFont = aFont->GetCairoScaledFont();
-  return Factory::CreateScaledFontForNativeFont(nativeFont,
-                                                aFont->GetUnscaledFont(),
-                                                aFont->GetAdjustedSize());
-}
-
 void
 gfxPlatform::ComputeTileSize()
 {
   // The tile size should be picked in the parent processes
   // and sent to the child processes over IPDL GetTileSize.
   if (!XRE_IsParentProcess()) {
     return;
   }
@@ -2574,27 +2563,16 @@ gfxPlatform::BufferRotationEnabled()
 void
 gfxPlatform::DisableBufferRotation()
 {
   MutexAutoLock autoLock(*gGfxPlatformPrefsLock);
 
   sBufferRotationCheckPref = false;
 }
 
-already_AddRefed<ScaledFont>
-gfxPlatform::GetScaledFontForFontWithCairoSkia(DrawTarget* aTarget, gfxFont* aFont)
-{
-    NativeFont nativeFont;
-    nativeFont.mType = NativeFontType::CAIRO_FONT_FACE;
-    nativeFont.mFont = aFont->GetCairoScaledFont();
-    return Factory::CreateScaledFontForNativeFont(nativeFont,
-                                                  aFont->GetUnscaledFont(),
-                                                  aFont->GetAdjustedSize());
-}
-
 /* static */ bool
 gfxPlatform::UsesOffMainThreadCompositing()
 {
   if (XRE_GetProcessType() == GeckoProcessType_GPU) {
     return true;
   }
 
   static bool firstTime = true;
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -234,19 +234,16 @@ public:
                                  gfxASurface *aSurface,
                                  bool aIsPlugin = false);
 
     static void ClearSourceSurfaceForSurface(gfxASurface *aSurface);
 
     static already_AddRefed<DataSourceSurface>
         GetWrappedDataSourceSurface(gfxASurface *aSurface);
 
-    virtual already_AddRefed<mozilla::gfx::ScaledFont>
-      GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont);
-
     already_AddRefed<DrawTarget>
       CreateOffscreenContentDrawTarget(const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat);
 
     already_AddRefed<DrawTarget>
       CreateOffscreenCanvasDrawTarget(const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat);
 
     already_AddRefed<DrawTarget>
       CreateSimilarSoftwareDrawTarget(DrawTarget* aDT, const IntSize &aSize, mozilla::gfx::SurfaceFormat aFormat);
@@ -775,19 +772,16 @@ protected:
      */
     static mozilla::gfx::BackendType GetBackendPref(const char* aBackendPrefName,
                                                     uint32_t &aBackendBitmask);
     /**
      * Decode the backend enumberation from a string.
      */
     static mozilla::gfx::BackendType BackendTypeForName(const nsCString& aName);
 
-    static already_AddRefed<mozilla::gfx::ScaledFont>
-      GetScaledFontForFontWithCairoSkia(mozilla::gfx::DrawTarget* aTarget, gfxFont* aFont);
-
     virtual bool CanUseHardwareVideoDecoding();
 
     int8_t  mAllowDownloadableFonts;
     int8_t  mGraphiteShapingEnabled;
     int8_t  mOpenTypeSVGEnabled;
 
     int8_t  mBidiNumeralOption;
 
--- a/gfx/thebes/gfxPlatformGtk.cpp
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -8,17 +8,16 @@
 
 #include "gfxPlatformGtk.h"
 #include "prenv.h"
 
 #include "nsUnicharUtils.h"
 #include "nsUnicodeProperties.h"
 #include "gfx2DGlue.h"
 #include "gfxFcPlatformFontList.h"
-#include "gfxFontconfigUtils.h"
 #include "gfxFontconfigFonts.h"
 #include "gfxConfig.h"
 #include "gfxContext.h"
 #include "gfxUserFontSet.h"
 #include "gfxUtils.h"
 #include "gfxFT2FontBase.h"
 #include "gfxPrefs.h"
 #include "VsyncSource.h"
@@ -563,30 +562,16 @@ gfxPlatformGtk::GetGdkDrawable(cairo_sur
         return result;
     }
 #endif
 
     return nullptr;
 }
 #endif
 
-already_AddRefed<ScaledFont>
-gfxPlatformGtk::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
-{
-  if (aFont->GetType() == gfxFont::FONT_TYPE_FONTCONFIG) {
-      gfxFontconfigFontBase* fcFont = static_cast<gfxFontconfigFontBase*>(aFont);
-      return Factory::CreateScaledFontForFontconfigFont(
-              fcFont->GetCairoScaledFont(),
-              fcFont->GetPattern(),
-              fcFont->GetUnscaledFont(),
-              fcFont->GetAdjustedSize());
-  }
-  return GetScaledFontForFontWithCairoSkia(aTarget, aFont);
-}
-
 #ifdef GL_PROVIDER_GLX
 
 class GLXVsyncSource final : public VsyncSource
 {
 public:
   GLXVsyncSource()
   {
     MOZ_ASSERT(NS_IsMainThread());
--- a/gfx/thebes/gfxPlatformGtk.h
+++ b/gfx/thebes/gfxPlatformGtk.h
@@ -17,34 +17,29 @@ extern "C" {
 }
 #endif
 
 #ifdef MOZ_X11
 struct _XDisplay;
 typedef struct _XDisplay Display;
 #endif // MOZ_X11
 
-class gfxFontconfigUtils;
-
 class gfxPlatformGtk : public gfxPlatform {
 public:
     gfxPlatformGtk();
     virtual ~gfxPlatformGtk();
 
     static gfxPlatformGtk *GetPlatform() {
         return (gfxPlatformGtk*) gfxPlatform::GetPlatform();
     }
 
     virtual already_AddRefed<gfxASurface>
       CreateOffscreenSurface(const IntSize& aSize,
                              gfxImageFormat aFormat) override;
 
-    virtual already_AddRefed<mozilla::gfx::ScaledFont>
-      GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override;
-
     virtual nsresult GetFontList(nsIAtom *aLangGroup,
                                  const nsACString& aGenericFamily,
                                  nsTArray<nsString>& aListOfFonts) override;
 
     virtual nsresult UpdateFontList() override;
 
     virtual void
     GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
@@ -133,18 +128,16 @@ public:
 
 #ifdef MOZ_X11
     Display* GetCompositorDisplay() {
       return mCompositorDisplay;
     }
 #endif // MOZ_X11
 
 protected:
-    static gfxFontconfigUtils *sFontconfigUtils;
-
     int8_t mMaxGenericSubstitutions;
 
 private:
     virtual void GetPlatformCMSOutputProfile(void *&mem,
                                              size_t &size) override;
 
 #ifdef MOZ_X11
     Display* mCompositorDisplay;
--- a/gfx/thebes/gfxPlatformMac.cpp
+++ b/gfx/thebes/gfxPlatformMac.cpp
@@ -128,23 +128,16 @@ gfxPlatformMac::CreateOffscreenSurface(c
         return nullptr;
     }
 
     RefPtr<gfxASurface> newSurface =
       new gfxQuartzSurface(aSize, aFormat);
     return newSurface.forget();
 }
 
-already_AddRefed<ScaledFont>
-gfxPlatformMac::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
-{
-    gfxMacFont *font = static_cast<gfxMacFont*>(aFont);
-    return font->GetScaledFont(aTarget);
-}
-
 gfxFontGroup *
 gfxPlatformMac::CreateFontGroup(const FontFamilyList& aFontFamilyList,
                                 const gfxFontStyle *aStyle,
                                 gfxTextPerfMetrics* aTextPerf,
                                 gfxUserFontSet *aUserFontSet,
                                 gfxFloat aDevToCssSize)
 {
     return new gfxFontGroup(aFontFamilyList, aStyle, aTextPerf,
--- a/gfx/thebes/gfxPlatformMac.h
+++ b/gfx/thebes/gfxPlatformMac.h
@@ -25,19 +25,16 @@ public:
     static gfxPlatformMac *GetPlatform() {
         return (gfxPlatformMac*) gfxPlatform::GetPlatform();
     }
 
     virtual already_AddRefed<gfxASurface>
       CreateOffscreenSurface(const IntSize& aSize,
                              gfxImageFormat aFormat) override;
 
-    already_AddRefed<mozilla::gfx::ScaledFont>
-      GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override;
-
     gfxFontGroup*
     CreateFontGroup(const mozilla::FontFamilyList& aFontFamilyList,
                     const gfxFontStyle *aStyle,
                     gfxTextPerfMetrics* aTextPerf,
                     gfxUserFontSet *aUserFontSet,
                     gfxFloat aDevToCssSize) override;
 
     virtual gfxPlatformFontList* CreatePlatformFontList() override;
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -549,44 +549,16 @@ gfxWindowsPlatform::CreateOffscreenSurfa
 
     if (!surf || surf->CairoStatus()) {
         surf = new gfxImageSurface(aSize, aFormat);
     }
 
     return surf.forget();
 }
 
-already_AddRefed<ScaledFont>
-gfxWindowsPlatform::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
-{
-    if (aFont->GetType() == gfxFont::FONT_TYPE_DWRITE) {
-        return aFont->GetScaledFont(aTarget);
-    }
-
-    NS_ASSERTION(aFont->GetType() == gfxFont::FONT_TYPE_GDI,
-        "Fonts on windows should be GDI or DWrite!");
-
-    NativeFont nativeFont;
-    nativeFont.mType = NativeFontType::GDI_FONT_FACE;
-    LOGFONT lf;
-    GetObject(static_cast<gfxGDIFont*>(aFont)->GetHFONT(), sizeof(LOGFONT), &lf);
-    nativeFont.mFont = &lf;
-
-    if (aTarget->GetBackendType() == BackendType::CAIRO) {
-      return Factory::CreateScaledFontWithCairo(nativeFont,
-                                                aFont->GetUnscaledFont(),
-                                                aFont->GetAdjustedSize(),
-                                                aFont->GetCairoScaledFont());
-    }
-
-    return Factory::CreateScaledFontForNativeFont(nativeFont,
-                                                  aFont->GetUnscaledFont(),
-                                                  aFont->GetAdjustedSize());
-}
-
 static const char kFontAparajita[] = "Aparajita";
 static const char kFontArabicTypesetting[] = "Arabic Typesetting";
 static const char kFontArial[] = "Arial";
 static const char kFontArialUnicodeMS[] = "Arial Unicode MS";
 static const char kFontCambria[] = "Cambria";
 static const char kFontCambriaMath[] = "Cambria Math";
 static const char kFontEbrima[] = "Ebrima";
 static const char kFontEstrangeloEdessa[] = "Estrangelo Edessa";
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -116,19 +116,16 @@ public:
     }
 
     virtual gfxPlatformFontList* CreatePlatformFontList() override;
 
     virtual already_AddRefed<gfxASurface>
       CreateOffscreenSurface(const IntSize& aSize,
                              gfxImageFormat aFormat) override;
 
-    virtual already_AddRefed<mozilla::gfx::ScaledFont>
-      GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override;
-
     enum RenderMode {
         /* Use GDI and windows surfaces */
         RENDER_GDI = 0,
 
         /* Use 32bpp image surfaces and call StretchDIBits */
         RENDER_IMAGE_STRETCH32,
 
         /* Use 32bpp image surfaces, and do 32->24 conversion before calling StretchDIBits */
--- a/ipc/testshell/XPCShellEnvironment.cpp
+++ b/ipc/testshell/XPCShellEnvironment.cpp
@@ -176,18 +176,17 @@ Load(JSContext *cx,
 static bool
 Version(JSContext *cx,
         unsigned argc,
         JS::Value *vp)
 {
     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     args.rval().setInt32(JS_GetVersion(cx));
     if (args.get(0).isInt32())
-        JS_SetVersionForCompartment(js::GetContextCompartment(cx),
-                                    JSVersion(args[0].toInt32()));
+        JS::SetVersionForCurrentRealm(cx, JSVersion(args[0].toInt32()));
     return true;
 }
 
 static bool
 Quit(JSContext *cx,
      unsigned argc,
      JS::Value *vp)
 {
--- a/js/public/GCPolicyAPI.h
+++ b/js/public/GCPolicyAPI.h
@@ -172,11 +172,13 @@ struct GCPolicy<mozilla::Maybe<T>>
     }
     static bool needsSweep(mozilla::Maybe<T>* tp) {
         if (tp->isSome())
             return GCPolicy<T>::needsSweep(tp->ptr());
         return false;
     }
 };
 
+template <> struct GCPolicy<JS::Realm*>;  // see Realm.h
+
 } // namespace JS
 
 #endif // GCPolicyAPI_h
--- a/js/public/Realm.h
+++ b/js/public/Realm.h
@@ -8,35 +8,115 @@
  * Ways to get various per-Realm objects. All the getters declared in this
  * header operate on the Realm corresponding to the current compartment on the
  * JSContext.
  */
 
 #ifndef js_Realm_h
 #define js_Realm_h
 
-#include "jstypes.h"
+#include "jspubtd.h"
+#include "js/GCPolicyAPI.h"
+#include "js/TypeDecls.h"  // forward-declaration of JS::Realm
 
-struct JSContext;
-class JSObject;
+namespace js {
+namespace gc {
+JS_PUBLIC_API(void) TraceRealm(JSTracer* trc, JS::Realm* realm, const char* name);
+JS_PUBLIC_API(bool) RealmNeedsSweep(JS::Realm* realm);
+}
+}
 
 namespace JS {
 
+// Each Realm holds a strong reference to its GlobalObject, and vice versa.
+template <>
+struct GCPolicy<Realm*>
+{
+    static Realm* initial() { return nullptr; }
+    static void trace(JSTracer* trc, Realm** vp, const char* name) {
+        if (*vp)
+            ::js::gc::TraceRealm(trc, *vp, name);
+    }
+    static bool needsSweep(Realm** vp) {
+        return *vp && ::js::gc::RealmNeedsSweep(*vp);
+    }
+};
+
+// Get the current realm, if any. The ECMAScript spec calls this "the current
+// Realm Record".
+extern JS_PUBLIC_API(Realm*)
+GetCurrentRealmOrNull(JSContext* cx);
+
+// Return the compartment that contains a given realm.
+inline JSCompartment*
+GetCompartmentForRealm(Realm* realm) {
+    // Implementation note: For now, realms are a fiction; we treat realms and
+    // compartments as being one-to-one, but they are actually identical.
+    return reinterpret_cast<JSCompartment*>(realm);
+}
+
+// Return the realm in a given compartment.
+inline Realm*
+GetRealmForCompartment(JSCompartment* compartment) {
+    return reinterpret_cast<Realm*>(compartment);
+}
+
+// Get the value of the "private data" internal field of the given Realm.
+// This field is initially null and is set using SetRealmPrivate.
+// It's a pointer to embeddding-specific data that SpiderMonkey never uses.
+extern JS_PUBLIC_API(void*)
+GetRealmPrivate(Realm* realm);
+
+// Set the "private data" internal field of the given Realm.
+extern JS_PUBLIC_API(void)
+SetRealmPrivate(Realm* realm, void* data);
+
+typedef void
+(* DestroyRealmCallback)(JSFreeOp* fop, Realm* realm);
+
+// Set the callback SpiderMonkey calls just before garbage-collecting a realm.
+// Embeddings can use this callback to free private data associated with the
+// realm via SetRealmPrivate.
+//
+// By the time this is called, the global object for the realm has already been
+// collected.
+extern JS_PUBLIC_API(void)
+SetDestroyRealmCallback(JSContext* cx, DestroyRealmCallback callback);
+
+typedef void
+(* RealmNameCallback)(JSContext* cx, Handle<Realm*> realm, char* buf, size_t bufsize);
+
+// Set the callback SpiderMonkey calls to get the name of a realm, for
+// diagnostic output.
+extern JS_PUBLIC_API(void)
+SetRealmNameCallback(JSContext* cx, RealmNameCallback callback);
+
 extern JS_PUBLIC_API(JSObject*)
 GetRealmObjectPrototype(JSContext* cx);
 
 extern JS_PUBLIC_API(JSObject*)
 GetRealmFunctionPrototype(JSContext* cx);
 
 extern JS_PUBLIC_API(JSObject*)
 GetRealmArrayPrototype(JSContext* cx);
 
 extern JS_PUBLIC_API(JSObject*)
 GetRealmErrorPrototype(JSContext* cx);
 
 extern JS_PUBLIC_API(JSObject*)
 GetRealmIteratorPrototype(JSContext* cx);
 
+/**
+ * Change the JS language version for the current Realm. This is discouraged,
+ * but necessary to support the `version()` builtin function in the js and xpc
+ * shells.
+ *
+ * It would be nice to put this in jsfriendapi, but the linkage requirements
+ * of the shells make that impossible.
+ */
+JS_PUBLIC_API(void)
+SetVersionForCurrentRealm(JSContext* cx, JSVersion version);
+
 } // namespace JS
 
 #endif // js_Realm_h
 
 
--- a/js/public/TraceKind.h
+++ b/js/public/TraceKind.h
@@ -128,27 +128,31 @@ template <TraceKind traceKind> struct Ma
 #define JS_EXPAND_DEF(name, _0, _1) \
     template <> struct MapTraceKindToRootKind<JS::TraceKind::name> { \
         static const JS::RootKind kind = JS::RootKind::name; \
     };
 JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF)
 #undef JS_EXPAND_DEF
 
 // Specify the RootKind for all types. Value and jsid map to special cases;
-// pointer types we can derive directly from the TraceKind; everything else
+// Cell pointer types we can derive directly from the TraceKind; everything else
 // should go in the Traceable list and use GCPolicy<T>::trace for tracing.
 template <typename T>
 struct MapTypeToRootKind {
     static const JS::RootKind kind = JS::RootKind::Traceable;
 };
 template <typename T>
 struct MapTypeToRootKind<T*> {
     static const JS::RootKind kind =
         JS::MapTraceKindToRootKind<JS::MapTypeToTraceKind<T>::kind>::kind;
 };
+template <> struct MapTypeToRootKind<JS::Realm*> {
+    // Not a pointer to a GC cell. Use GCPolicy.
+    static const JS::RootKind kind = JS::RootKind::Traceable;
+};
 template <typename T>
 struct MapTypeToRootKind<mozilla::UniquePtr<T>> {
     static const JS::RootKind kind = JS::MapTypeToRootKind<T>::kind;
 };
 template <> struct MapTypeToRootKind<JS::Value> {
     static const JS::RootKind kind = JS::RootKind::Value;
 };
 template <> struct MapTypeToRootKind<jsid> {
--- a/js/public/TypeDecls.h
+++ b/js/public/TypeDecls.h
@@ -23,25 +23,27 @@
 #include "js-config.h"
 
 struct JSContext;
 class JSFunction;
 class JSObject;
 class JSScript;
 class JSString;
 class JSAddonId;
+struct JSFreeOp;
 
 struct jsid;
 
 namespace JS {
 
 typedef unsigned char Latin1Char;
 
 class Symbol;
 class Value;
+class Realm;
 template <typename T> class Handle;
 template <typename T> class MutableHandle;
 template <typename T> class Rooted;
 template <typename T> class PersistentRooted;
 
 typedef Handle<JSFunction*> HandleFunction;
 typedef Handle<jsid>        HandleId;
 typedef Handle<JSObject*>   HandleObject;
--- a/js/src/irregexp/RegExpStack.h
+++ b/js/src/irregexp/RegExpStack.h
@@ -85,17 +85,17 @@ class RegExpStack
     void* base() { return base_; }
     void* limit() { return limit_; }
 
   private:
     // Artificial limit used when no memory has been allocated.
     static const uintptr_t kMemoryTop = static_cast<uintptr_t>(-1);
 
     // Minimal size of allocated stack area, in bytes.
-    static const size_t kMinimumStackSize = 1 * 1024;
+    static const size_t kMinimumStackSize = 256;
 
     // Maximal size of allocated stack area, in bytes.
     static const size_t kMaximumStackSize = 64 * 1024 * 1024;
 
     // If size > 0 then base must be non-nullptr.
     void* base_;
 
     // Length in bytes of base.
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -583,22 +583,16 @@ JS::SetSingleThreadedExecutionCallbacks(
 }
 
 JS_PUBLIC_API(JSVersion)
 JS_GetVersion(JSContext* cx)
 {
     return VersionNumber(cx->findVersion());
 }
 
-JS_PUBLIC_API(void)
-JS_SetVersionForCompartment(JSCompartment* compartment, JSVersion version)
-{
-    compartment->behaviors().setVersion(version);
-}
-
 static const struct v2smap {
     JSVersion   version;
     const char* string;
 } v2smap[] = {
     {JSVERSION_ECMA_3,  "ECMAv3"},
     {JSVERSION_1_6,     "1.6"},
     {JSVERSION_1_7,     "1.7"},
     {JSVERSION_1_8,     "1.8"},
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1130,27 +1130,16 @@ class MOZ_RAII JSAutoRequest
     static void* operator new(size_t) CPP_THROW_NEW { return 0; }
     static void operator delete(void*, size_t) { }
 #endif
 };
 
 extern JS_PUBLIC_API(JSVersion)
 JS_GetVersion(JSContext* cx);
 
-/**
- * Mutate the version on the compartment. This is generally discouraged, but
- * necessary to support the version mutation in the js and xpc shell command
- * set.
- *
- * It would be nice to put this in jsfriendapi, but the linkage requirements
- * of the shells make that impossible.
- */
-JS_PUBLIC_API(void)
-JS_SetVersionForCompartment(JSCompartment* compartment, JSVersion version);
-
 extern JS_PUBLIC_API(const char*)
 JS_VersionToString(JSVersion version);
 
 extern JS_PUBLIC_API(JSVersion)
 JS_StringToVersion(const char* string);
 
 namespace JS {
 
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -63,16 +63,17 @@ JSCompartment::JSCompartment(Zone* zone,
     warnedAboutStringGenericsMethods(0),
 #ifdef DEBUG
     firedOnNewGlobalObject(false),
 #endif
     global_(nullptr),
     enterCompartmentDepth(0),
     performanceMonitoring(runtime_),
     data(nullptr),
+    realmData(nullptr),
     allocationMetadataBuilder(nullptr),
     lastAnimationTime(0),
     regExps(zone),
     globalWriteBarriered(0),
     detachedTypedObjects(0),
     objectMetadataState(ImmediateMetadata()),
     selfHostingScriptSource(nullptr),
     objectMetadataTable(nullptr),
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -664,36 +664,40 @@ struct JSCompartment
     }
 
     // Note: Unrestricted access to the zone's runtime from an arbitrary
     // thread can easily lead to races. Use this method very carefully.
     JSRuntime* runtimeFromAnyThread() const {
         return runtime_;
     }
 
-    /*
-     * Nb: global_ might be nullptr, if (a) it's the atoms compartment, or
-     * (b) the compartment's global has been collected.  The latter can happen
-     * if e.g. a string in a compartment is rooted but no object is, and thus
-     * the global isn't rooted, and thus the global can be finalized while the
-     * compartment lives on.
+    /* The global object for this compartment.
+     *
+     * This returns nullptr if this is the atoms compartment.  (The global_
+     * field is also null briefly during GC, after the global object is
+     * collected; but when that happens the JSCompartment is destroyed during
+     * the same GC.)
      *
      * In contrast, JSObject::global() is infallible because marking a JSObject
      * always marks its global as well.
      * TODO: add infallible JSScript::global()
      */
     inline js::GlobalObject* maybeGlobal() const;
 
     /* An unbarriered getter for use while tracing. */
     inline js::GlobalObject* unsafeUnbarrieredMaybeGlobal() const;
 
+    /* True if a global object exists, but it's being collected. */
+    inline bool globalIsAboutToBeFinalized();
+
     inline void initGlobal(js::GlobalObject& global);
 
   public:
     void*                        data;
+    void*                        realmData;
 
   private:
     const js::AllocationMetadataBuilder *allocationMetadataBuilder;
 
     js::SavedStacks              savedStacks_;
 
     js::WrapperMap               crossCompartmentWrappers;
 
--- a/js/src/jscompartmentinlines.h
+++ b/js/src/jscompartmentinlines.h
@@ -6,16 +6,17 @@
 
 #ifndef jscompartmentinlines_h
 #define jscompartmentinlines_h
 
 #include "jscompartment.h"
 #include "jsiter.h"
 
 #include "gc/Barrier.h"
+#include "gc/Marking.h"
 
 #include "jscntxtinlines.h"
 
 inline void
 JSCompartment::initGlobal(js::GlobalObject& global)
 {
     MOZ_ASSERT(global.compartment() == this);
     MOZ_ASSERT(!global_);
@@ -30,16 +31,23 @@ JSCompartment::maybeGlobal() const
 }
 
 js::GlobalObject*
 JSCompartment::unsafeUnbarrieredMaybeGlobal() const
 {
     return *global_.unsafeGet();
 }
 
+inline bool
+JSCompartment::globalIsAboutToBeFinalized()
+{
+    MOZ_ASSERT(zone_->isGCSweeping());
+    return global_ && js::gc::IsAboutToBeFinalizedUnbarriered(global_.unsafeGet());
+}
+
 template <typename T>
 js::AutoCompartment::AutoCompartment(JSContext* cx, const T& target)
   : cx_(cx),
     origin_(cx->compartment()),
     maybeLock_(nullptr)
 {
     cx_->enterCompartmentOf(target);
 }
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3439,16 +3439,18 @@ JS::Zone::sweepUniqueIds(js::FreeOp* fop
 {
     uniqueIds().sweep();
 }
 
 void
 JSCompartment::destroy(FreeOp* fop)
 {
     JSRuntime* rt = fop->runtime();
+    if (auto callback = rt->destroyRealmCallback)
+        callback(fop, JS::GetRealmForCompartment(this));
     if (auto callback = rt->destroyCompartmentCallback)
         callback(fop, this);
     if (principals())
         JS_DropPrincipals(TlsContext.get(), principals());
     fop->delete_(this);
     rt->gc.stats().sweptCompartment();
 }
 
@@ -3459,20 +3461,19 @@ Zone::destroy(FreeOp* fop)
     fop->delete_(this);
     fop->runtime()->gc.stats().sweptZone();
 }
 
 /*
  * It's simpler if we preserve the invariant that every zone has at least one
  * compartment. If we know we're deleting the entire zone, then
  * SweepCompartments is allowed to delete all compartments. In this case,
- * |keepAtleastOne| is false. If some objects remain in the zone so that it
- * cannot be deleted, then we set |keepAtleastOne| to true, which prohibits
- * SweepCompartments from deleting every compartment. Instead, it preserves an
- * arbitrary compartment in the zone.
+ * |keepAtleastOne| is false. If any cells remain alive in the zone, set
+ * |keepAtleastOne| true to prohibit sweepCompartments from deleting every
+ * compartment. Instead, it preserves an arbitrary compartment in the zone.
  */
 void
 Zone::sweepCompartments(FreeOp* fop, bool keepAtleastOne, bool destroyingRuntime)
 {
     MOZ_ASSERT(!compartments().empty());
 
     mozilla::DebugOnly<JSRuntime*> rt = runtimeFromActiveCooperatingThread();
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -127,18 +127,16 @@ using mozilla::TimeStamp;
 
 enum JSShellExitCode {
     EXITCODE_RUNTIME_ERROR      = 3,
     EXITCODE_FILE_NOT_FOUND     = 4,
     EXITCODE_OUT_OF_MEMORY      = 5,
     EXITCODE_TIMEOUT            = 6
 };
 
-static const size_t gStackChunkSize = 8192;
-
 /*
  * Note: This limit should match the stack limit set by the browser in
  *       js/xpconnect/src/XPCJSContext.cpp
  */
 #if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN))
 static const size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024;
 #else
 static const size_t gMaxStackSize = 128 * sizeof(size_t) * 1024;
@@ -989,17 +987,17 @@ Version(JSContext* cx, unsigned argc, Va
             if (NumberEqualsInt32(fv, &fvi))
                 v = fvi;
         }
         if (v < 0 || v > JSVERSION_LATEST) {
             JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
                                       "version");
             return false;
         }
-        JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v));
+        SetVersionForCurrentRealm(cx, JSVersion(v));
         args.rval().setInt32(origVersion);
     }
     return true;
 }
 
 #ifdef XP_WIN
 #  define GET_FD_FROM_FILE(a) int(_get_osfhandle(fileno(a)))
 #else
--- a/js/src/vm/Realm.cpp
+++ b/js/src/vm/Realm.cpp
@@ -2,22 +2,67 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "js/Realm.h"
 
 #include "jscntxt.h"
-#include "jscompartment.h" // For JSContext::global
+#include "jscompartment.h"
 
 #include "vm/GlobalObject.h"
 
+#include "jscompartmentinlines.h"
+
 using namespace js;
 
+JS_PUBLIC_API(void)
+gc::TraceRealm(JSTracer* trc, JS::Realm* realm, const char* name)
+{
+    // The way GC works with compartments is basically incomprehensible.
+    // For Realms, what we want is very simple: each Realm has a strong
+    // reference to its GlobalObject, and vice versa.
+    //
+    // Here we simply trace our side of that edge. During GC,
+    // GCRuntime::traceRuntimeCommon() marks all other compartment roots, for
+    // all compartments.
+    JS::GetCompartmentForRealm(realm)->traceGlobal(trc);
+}
+
+JS_PUBLIC_API(bool)
+gc::RealmNeedsSweep(JS::Realm* realm)
+{
+    return JS::GetCompartmentForRealm(realm)->globalIsAboutToBeFinalized();
+}
+
+JS_PUBLIC_API(void*)
+JS::GetRealmPrivate(JS::Realm* realm)
+{
+    return GetCompartmentForRealm(realm)->realmData;
+}
+
+JS_PUBLIC_API(void)
+JS::SetRealmPrivate(JS::Realm* realm, void* data)
+{
+    GetCompartmentForRealm(realm)->realmData = data;
+}
+
+JS_PUBLIC_API(void)
+JS::SetDestroyRealmCallback(JSContext* cx, JS::DestroyRealmCallback callback)
+{
+    cx->runtime()->destroyRealmCallback = callback;
+}
+
+JS_PUBLIC_API(void)
+JS::SetRealmNameCallback(JSContext* cx, JS::RealmNameCallback callback)
+{
+    cx->runtime()->realmNameCallback = callback;
+}
+
 JS_PUBLIC_API(JSObject*)
 JS::GetRealmObjectPrototype(JSContext* cx)
 {
     CHECK_REQUEST(cx);
     return GlobalObject::getOrCreateObjectPrototype(cx, cx->global());
 }
 
 JS_PUBLIC_API(JSObject*)
@@ -42,8 +87,15 @@ JS::GetRealmErrorPrototype(JSContext* cx
 }
 
 JS_PUBLIC_API(JSObject*)
 JS::GetRealmIteratorPrototype(JSContext* cx)
 {
     CHECK_REQUEST(cx);
     return GlobalObject::getOrCreateIteratorPrototype(cx, cx->global());
 }
+
+JS_PUBLIC_API(void)
+JS::SetVersionForCurrentRealm(JSContext* cx, JSVersion version)
+{
+    JSCompartment* compartment = GetContextCompartment(cx);
+    compartment->behaviors().setVersion(version);
+}
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -114,16 +114,18 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     readableStreamClosedCallback(nullptr),
     readableStreamErroredCallback(nullptr),
     readableStreamFinalizeCallback(nullptr),
     hadOutOfMemory(false),
     allowRelazificationForTesting(false),
     destroyCompartmentCallback(nullptr),
     sizeOfIncludingThisCompartmentCallback(nullptr),
     compartmentNameCallback(nullptr),
+    destroyRealmCallback(nullptr),
+    realmNameCallback(nullptr),
     externalStringSizeofCallback(nullptr),
     securityCallbacks(&NullSecurityCallbacks),
     DOMcallbacks(nullptr),
     destroyPrincipals(nullptr),
     readPrincipals(nullptr),
     warningReporter(nullptr),
     geckoProfiler_(thisFromCtor()),
     buildIdOp(nullptr),
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -496,16 +496,22 @@ struct JSRuntime : public js::MallocProv
     js::ActiveThreadData<JSDestroyCompartmentCallback> destroyCompartmentCallback;
 
     /* Compartment memory reporting callback. */
     js::ActiveThreadData<JSSizeOfIncludingThisCompartmentCallback> sizeOfIncludingThisCompartmentCallback;
 
     /* Call this to get the name of a compartment. */
     js::ActiveThreadData<JSCompartmentNameCallback> compartmentNameCallback;
 
+    /* Realm destroy callback. */
+    js::ActiveThreadData<JS::DestroyRealmCallback> destroyRealmCallback;
+
+    /* Call this to get the name of a realm. */
+    js::ActiveThreadData<JS::RealmNameCallback> realmNameCallback;
+
     /* Callback for doing memory reporting on external strings. */
     js::ActiveThreadData<JSExternalStringSizeofCallback> externalStringSizeofCallback;
 
     js::ActiveThreadData<mozilla::UniquePtr<js::SourceHook>> sourceHook;
 
     js::ActiveThreadData<const JSSecurityCallbacks*> securityCallbacks;
     js::ActiveThreadData<const js::DOMCallbacks*> DOMcallbacks;
     js::ActiveThreadData<JSDestroyPrincipalsOp> destroyPrincipals;
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3345,20 +3345,18 @@ nsXPCComponents_Utils::SetAddonInterposi
 NS_IMETHODIMP
 nsXPCComponents_Utils::SetAddonCallInterposition(HandleValue target,
                                                  JSContext* cx)
 {
     NS_ENSURE_TRUE(target.isObject(), NS_ERROR_INVALID_ARG);
     RootedObject targetObj(cx, &target.toObject());
     targetObj = js::CheckedUnwrap(targetObj);
     NS_ENSURE_TRUE(targetObj, NS_ERROR_INVALID_ARG);
-    XPCWrappedNativeScope* xpcScope = ObjectScope(targetObj);
-    NS_ENSURE_TRUE(xpcScope, NS_ERROR_INVALID_ARG);
-
-    xpcScope->SetAddonCallInterposition();
+
+    xpc::CompartmentPrivate::Get(targetObj)->SetAddonCallInterposition();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::AllowCPOWsInAddon(const nsACString& addonIdStr,
                                          bool allow,
                                          JSContext* cx)
 {
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -170,16 +170,17 @@ namespace xpc {
 
 CompartmentPrivate::CompartmentPrivate(JSCompartment* c)
     : wantXrays(false)
     , allowWaivers(true)
     , writeToGlobalPrototype(false)
     , skipWriteToGlobalPrototype(false)
     , isWebExtensionContentScript(false)
     , waiveInterposition(false)
+    , addonCallInterposition(false)
     , allowCPOWs(false)
     , universalXPConnectEnabled(false)
     , forcePermissiveCOWs(false)
     , wasNuked(false)
     , scriptability(c)
     , scope(nullptr)
     , mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH))
 {
@@ -2658,16 +2659,23 @@ CompartmentNameCallback(JSContext* cx, J
     // we don't need to anonymize compartment names.
     int anonymizeID = 0;
     GetCompartmentName(comp, name, &anonymizeID, /* replaceSlashes = */ false);
     if (name.Length() >= bufsize)
         name.Truncate(bufsize - 1);
     memcpy(buf, name.get(), name.Length() + 1);
 }
 
+static void
+GetRealmName(JSContext* cx, JS::Handle<JS::Realm*> realm, char* buf, size_t bufsize)
+{
+    JSCompartment* comp = JS::GetCompartmentForRealm(realm);
+    CompartmentNameCallback(cx, comp, buf, bufsize);
+}
+
 static bool
 PreserveWrapper(JSContext* cx, JSObject* obj)
 {
     MOZ_ASSERT(cx);
     MOZ_ASSERT(obj);
     MOZ_ASSERT(IS_WN_REFLECTOR(obj) || mozilla::dom::IsDOMObject(obj));
 
     return mozilla::dom::IsDOMObject(obj) && mozilla::dom::TryPreserveWrapper(obj);
@@ -2829,16 +2837,17 @@ XPCJSRuntime::Initialize(JSContext* cx)
     // This leaves the maximum-JS_malloc-bytes threshold still in effect
     // to cause period, and we hope hygienic, last-ditch GCs from within
     // the GC's allocator.
     JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff);
 
     JS_SetDestroyCompartmentCallback(cx, CompartmentDestroyedCallback);
     JS_SetSizeOfIncludingThisCompartmentCallback(cx, CompartmentSizeOfIncludingThisCallback);
     JS_SetCompartmentNameCallback(cx, CompartmentNameCallback);
+    JS::SetRealmNameCallback(cx, GetRealmName);
     mPrevGCSliceCallback = JS::SetGCSliceCallback(cx, GCSliceCallback);
     mPrevDoCycleCollectionCallback = JS::SetDoCycleCollectionCallback(cx,
             DoCycleCollectionCallback);
     JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr);
     JS_AddWeakPointerZonesCallback(cx, WeakPointerZonesCallback, this);
     JS_AddWeakPointerCompartmentCallback(cx, WeakPointerCompartmentCallback, this);
     JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
     js::SetPreserveWrapperCallback(cx, PreserveWrapper);
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -387,18 +387,17 @@ Load(JSContext* cx, unsigned argc, Value
 }
 
 static bool
 Version(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setInt32(JS_GetVersion(cx));
     if (args.get(0).isInt32())
-        JS_SetVersionForCompartment(js::GetContextCompartment(cx),
-                                    JSVersion(args[0].toInt32()));
+        SetVersionForCurrentRealm(cx, JSVersion(args[0].toInt32()));
     return true;
 }
 
 static bool
 Quit(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -1081,18 +1080,17 @@ ProcessArgs(AutoJSAPI& jsapi, char** arg
             isInteractive = false;
             break;
         }
         switch (argv[i][1]) {
         case 'v':
             if (++i == argc) {
                 return printUsageAndSetExitCode();
             }
-            JS_SetVersionForCompartment(js::GetContextCompartment(cx),
-                                        JSVersion(atoi(argv[i])));
+            SetVersionForCurrentRealm(cx, JSVersion(atoi(argv[i])));
             break;
         case 'W':
             reportWarnings = false;
             break;
         case 'w':
             reportWarnings = true;
             break;
         case 'x':
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -91,17 +91,16 @@ RemoteXULForbidsXBLScope(nsIPrincipal* a
 
 XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx,
                                              JS::HandleObject aGlobal)
       : mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)),
         mWrappedNativeProtoMap(ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)),
         mComponents(nullptr),
         mNext(nullptr),
         mGlobalJSObject(aGlobal),
-        mHasCallInterpositions(false),
         mIsContentXBLScope(false),
         mIsAddonScope(false)
 {
     // add ourselves to the scopes list
     {
         MOZ_ASSERT(aGlobal);
         DebugOnly<const js::Class*> clasp = js::GetObjectClass(aGlobal);
         MOZ_ASSERT(clasp->flags & (JSCLASS_PRIVATE_IS_NSISUPPORTS |
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -1017,19 +1017,16 @@ public:
     static bool SetAddonInterposition(JSContext* cx,
                                       JSAddonId* addonId,
                                       nsIAddonInterposition* interp);
 
     static InterpositionWhitelist* GetInterpositionWhitelist(nsIAddonInterposition* interposition);
     static bool UpdateInterpositionWhitelist(JSContext* cx,
                                              nsIAddonInterposition* interposition);
 
-    void SetAddonCallInterposition() { mHasCallInterpositions = true; }
-    bool HasCallInterposition() { return mHasCallInterpositions; };
-
     static bool AllowCPOWsInAddon(JSContext* cx, JSAddonId* addonId, bool allow);
 
 protected:
     virtual ~XPCWrappedNativeScope();
 
     XPCWrappedNativeScope() = delete;
 
 private:
@@ -1067,20 +1064,16 @@ private:
 
     // Lazily created sandboxes for addon code.
     nsTArray<JS::ObjectPtr>          mAddonScopes;
 
     // This is a service that will be use to interpose on some property accesses on
     // objects from other scope, for add-on compatibility reasons.
     nsCOMPtr<nsIAddonInterposition>  mInterposition;
 
-    // If this flag is set, we intercept function calls on vanilla JS function objects
-    // from this scope if the caller scope has mInterposition set.
-    bool mHasCallInterpositions;
-
     JS::WeakMapPtr<JSObject*, JSObject*> mXrayExpandos;
 
     bool mIsContentXBLScope;
     bool mIsAddonScope;
 
     // For remote XUL domains, we run all XBL in the content scope for compat
     // reasons (though we sometimes pref this off for automation). We separately
     // track the result of this decision (mAllowContentXBLScope), from the decision
@@ -3013,16 +3006,19 @@ enum WrapperDenialType {
     WrapperDenialForXray = 0,
     WrapperDenialForCOW,
     WrapperDenialTypeCount
 };
 bool ReportWrapperDenial(JSContext* cx, JS::HandleId id, WrapperDenialType type, const char* reason);
 
 class CompartmentPrivate
 {
+    CompartmentPrivate() = delete;
+    CompartmentPrivate(const CompartmentPrivate&) = delete;
+
 public:
     enum LocationHint {
         LocationHintRegular,
         LocationHintAddon
     };
 
     explicit CompartmentPrivate(JSCompartment* c);
 
@@ -3058,25 +3054,30 @@ public:
     // module owner.
     bool writeToGlobalPrototype;
 
     // When writeToGlobalPrototype is true, we use this flag to temporarily
     // disable the writeToGlobalPrototype behavior (when resolving standard
     // classes, for example).
     bool skipWriteToGlobalPrototype;
 
-    // This scope corresponds to a WebExtension content script, and receives
-    // various bits of special compatibility behavior.
+    // This compartment corresponds to a WebExtension content script, and
+    // receives various bits of special compatibility behavior.
     bool isWebExtensionContentScript;
 
     // Even if an add-on needs interposition, it does not necessary need it
-    // for every scope. If this flag is set we waive interposition for this
-    // scope.
+    // for every compartment. If this flag is set we waive interposition for
+    // this compartment.
     bool waiveInterposition;
 
+    // If this flag is set, we intercept function calls on vanilla JS function
+    // objects from this compartment if the caller compartment has the
+    // hasInterposition flag set.
+    bool addonCallInterposition;
+
     // If CPOWs are disabled for browser code via the
     // dom.ipc.cpows.forbid-unsafe-from-browser preferences, then only
     // add-ons can use CPOWs. This flag allows a non-addon scope
     // to opt into CPOWs. It's necessary for the implementation of
     // RemoteAddonsParent.jsm.
     bool allowCPOWs;
 
     // This is only ever set during mochitest runs when enablePrivilege is called.
@@ -3147,16 +3148,18 @@ public:
     void SetLocationURI(nsIURI* aLocationURI) {
         if (!aLocationURI)
             return;
         if (locationURI)
             return;
         locationURI = aLocationURI;
     }
 
+    void SetAddonCallInterposition() { addonCallInterposition = true; }
+
     JSObject2WrappedJSMap* GetWrappedJSMap() const { return mWrappedJSMap; }
     void UpdateWeakPointersAfterGC();
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
 private:
     nsCString location;
     nsCOMPtr<nsIURI> locationURI;
--- a/js/xpconnect/wrappers/AddonWrapper.cpp
+++ b/js/xpconnect/wrappers/AddonWrapper.cpp
@@ -109,18 +109,17 @@ InterposeCall(JSContext* cx, JS::HandleO
 {
     *done = false;
     XPCWrappedNativeScope* scope = ObjectScope(CurrentGlobalOrNull(cx));
     MOZ_ASSERT(scope->HasInterposition());
 
     nsCOMPtr<nsIAddonInterposition> interp = scope->GetInterposition();
 
     RootedObject unwrappedTarget(cx, UncheckedUnwrap(target));
-    XPCWrappedNativeScope* targetScope = ObjectScope(unwrappedTarget);
-    bool hasInterpostion = targetScope->HasCallInterposition();
+    bool hasInterpostion = xpc::CompartmentPrivate::Get(unwrappedTarget)->addonCallInterposition;
 
     if (!hasInterpostion)
         return true;
 
     // If there is a call interpostion, we don't want to propogate the
     // call to Base:
     *done = true; 
 
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -59,17 +59,16 @@
 #include "nsTextFragment.h"
 #include "nsGkAtoms.h"
 #include "nsFrameSelection.h"
 #include "nsRange.h"
 #include "nsCSSRendering.h"
 #include "nsContentUtils.h"
 #include "nsLineBreaker.h"
 #include "nsIWordBreaker.h"
-#include "nsGenericDOMDataNode.h"
 #include "nsIFrameInlines.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "mozilla/layers/LayersMessages.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/layers/StackingContextHelper.h"
@@ -731,17 +730,19 @@ struct FlowLengthProperty {
 };
 
 int32_t nsTextFrame::GetInFlowContentLength() {
   if (!(mState & NS_FRAME_IS_BIDI)) {
     return mContent->TextLength() - mContentOffset;
   }
 
   FlowLengthProperty* flowLength =
-    static_cast<FlowLengthProperty*>(mContent->GetProperty(nsGkAtoms::flowlength));
+    mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)
+    ? static_cast<FlowLengthProperty*>(mContent->GetProperty(nsGkAtoms::flowlength))
+    : nullptr;
 
   /**
    * This frame must start inside the cached flow. If the flow starts at
    * mContentOffset but this frame is empty, logically it might be before the
    * start of the cached flow.
    */
   if (flowLength &&
       (flowLength->mStartOffset < mContentOffset ||
@@ -759,16 +760,17 @@ int32_t nsTextFrame::GetInFlowContentLen
 
   if (!flowLength) {
     flowLength = new FlowLengthProperty;
     if (NS_FAILED(mContent->SetProperty(nsGkAtoms::flowlength, flowLength,
                                         nsINode::DeleteProperty<FlowLengthProperty>))) {
       delete flowLength;
       flowLength = nullptr;
     }
+    mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
   }
   if (flowLength) {
     flowLength->mStartOffset = mContentOffset;
     flowLength->mEndFlowOffset = endFlow;
   }
 
   return endFlow - mContentOffset;
 }
@@ -4375,19 +4377,23 @@ nsTextFrame::Init(nsIContent*       aCon
                   nsIFrame*         aPrevInFlow)
 {
   NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
   NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT),
                   "Bogus content!");
 
   // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
   // might be invalid if the content was modified while there was no frame
-  aContent->DeleteProperty(nsGkAtoms::newline);
-  if (PresContext()->BidiEnabled()) {
+  if (aContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
+    aContent->DeleteProperty(nsGkAtoms::newline);
+    aContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
+  }
+  if (aContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
     aContent->DeleteProperty(nsGkAtoms::flowlength);
+    aContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
   }
 
   // Since our content has a frame now, this flag is no longer needed.
   aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
 
   // We're not a continuing frame.
   // mContentOffset = 0; not necessary since we get zeroed out at init
   nsFrame::Init(aContent, aParent, aPrevInFlow);
@@ -4825,19 +4831,23 @@ nsTextFrame::DisconnectTextRuns()
   if ((GetStateBits() & TEXT_HAS_FONT_INFLATION)) {
     DeleteProperty(UninflatedTextRunProperty());
   }
 }
 
 nsresult
 nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo)
 {
-  mContent->DeleteProperty(nsGkAtoms::newline);
-  if (PresContext()->BidiEnabled()) {
+  if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
+    mContent->DeleteProperty(nsGkAtoms::newline);
+    mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
+  }
+  if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
     mContent->DeleteProperty(nsGkAtoms::flowlength);
+    mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
   }
 
   // Find the first frame whose text has changed. Frames that are entirely
   // before the text change are completely unaffected.
   nsTextFrame* next;
   nsTextFrame* textFrame = this;
   while (true) {
     next = textFrame->GetNextContinuation();
@@ -9273,17 +9283,19 @@ nsTextFrame::ReflowText(nsLineLayout& aL
 
   // Restrict preformatted text to the nearest newline
   int32_t newLineOffset = -1; // this will be -1 or a content offset
   int32_t contentNewLineOffset = -1;
   // Pointer to the nsGkAtoms::newline set on this frame's element
   NewlineProperty* cachedNewlineOffset = nullptr;
   if (textStyle->NewlineIsSignificant(this)) {
     cachedNewlineOffset =
-      static_cast<NewlineProperty*>(mContent->GetProperty(nsGkAtoms::newline));
+      mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)
+      ? static_cast<NewlineProperty*>(mContent->GetProperty(nsGkAtoms::newline))
+      : nullptr;
     if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset &&
         (cachedNewlineOffset->mNewlineOffset == -1 ||
          cachedNewlineOffset->mNewlineOffset >= offset)) {
       contentNewLineOffset = cachedNewlineOffset->mNewlineOffset;
     } else {
       contentNewLineOffset = FindChar(frag, offset,
                                       mContent->TextLength() - offset, '\n');
     }
@@ -9758,23 +9770,25 @@ nsTextFrame::ReflowText(nsLineLayout& aL
        mContentOffset + contentLength <= contentNewLineOffset)) {
     if (!cachedNewlineOffset) {
       cachedNewlineOffset = new NewlineProperty;
       if (NS_FAILED(mContent->SetProperty(nsGkAtoms::newline, cachedNewlineOffset,
                                           nsINode::DeleteProperty<NewlineProperty>))) {
         delete cachedNewlineOffset;
         cachedNewlineOffset = nullptr;
       }
+      mContent->SetFlags(NS_HAS_NEWLINE_PROPERTY);
     }
     if (cachedNewlineOffset) {
       cachedNewlineOffset->mStartOffset = offset;
       cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
     }
   } else if (cachedNewlineOffset) {
     mContent->DeleteProperty(nsGkAtoms::newline);
+    mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
   }
 
   // Compute space and letter counts for justification, if required
   if (!textStyle->WhiteSpaceIsSignificant() &&
       (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
        lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
        shouldSuppressLineBreak) &&
       !nsSVGUtils::IsInSVGTextSubtree(lineContainer)) {
@@ -10247,17 +10261,20 @@ nsTextFrame::GetDebugStateBits() const
     ~(TEXT_WHITESPACE_FLAGS | TEXT_REFLOW_FLAGS);
 }
 #endif
 
 void
 nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd)
 {
   AddStateBits(NS_FRAME_IS_BIDI);
-  mContent->DeleteProperty(nsGkAtoms::flowlength);
+  if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
+    mContent->DeleteProperty(nsGkAtoms::flowlength);
+    mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
+  }
 
   /*
    * After Bidi resolution we may need to reassign text runs.
    * This is called during bidi resolution from the block container, so we
    * shouldn't be holding a local reference to a textrun anywhere.
    */
   ClearTextRuns();
 
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -7,16 +7,17 @@
 #define nsTextFrame_h__
 
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/UniquePtr.h"
 #include "nsFrame.h"
 #include "nsFrameSelection.h"
+#include "nsGenericDOMDataNode.h"
 #include "nsSplittableFrame.h"
 #include "nsLineBox.h"
 #include "gfxSkipChars.h"
 #include "gfxTextRun.h"
 #include "nsDisplayList.h"
 #include "JustificationUtils.h"
 #include "RubyUtils.h"
 
@@ -91,17 +92,20 @@ public:
     NS_ASSERTION(
       !nsSplittableFrame::IsInNextContinuationChain(aNextContinuation, this),
       "creating a loop in continuation chain!");
     mNextContinuation = static_cast<nsTextFrame*>(aNextContinuation);
     if (aNextContinuation)
       aNextContinuation->RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
     // Setting a non-fluid continuation might affect our flow length (they're
     // quite rare so we assume it always does) so we delete our cached value:
-    GetContent()->DeleteProperty(nsGkAtoms::flowlength);
+    if (GetContent()->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
+      GetContent()->DeleteProperty(nsGkAtoms::flowlength);
+      GetContent()->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
+    }
   }
   nsIFrame* GetNextInFlowVirtual() const override { return GetNextInFlow(); }
   nsTextFrame* GetNextInFlow() const
   {
     return mNextContinuation &&
                (mNextContinuation->GetStateBits() &
                 NS_FRAME_IS_FLUID_CONTINUATION)
              ? mNextContinuation
@@ -114,17 +118,20 @@ public:
     NS_ASSERTION(
       !nsSplittableFrame::IsInNextContinuationChain(aNextInFlow, this),
       "creating a loop in continuation chain!");
     mNextContinuation = static_cast<nsTextFrame*>(aNextInFlow);
     if (mNextContinuation &&
         !mNextContinuation->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) {
       // Changing from non-fluid to fluid continuation might affect our flow
       // length, so we delete our cached value:
-      GetContent()->DeleteProperty(nsGkAtoms::flowlength);
+      if (GetContent()->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
+        GetContent()->DeleteProperty(nsGkAtoms::flowlength);
+        GetContent()->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
+      }
     }
     if (aNextInFlow) {
       aNextInFlow->AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
     }
   }
   nsTextFrame* LastInFlow() const final;
   nsTextFrame* LastContinuation() const final;
 
--- a/layout/reftests/bidi/reftest.list
+++ b/layout/reftests/bidi/reftest.list
@@ -82,17 +82,17 @@ random-if(winWidget) == 305643-1.html 30
 == 332655-1.html 332655-1-ref.html
 == 332655-2.html 332655-2-ref.html
 == 381279-1.html 381279-1-ref.html
 == 386339.html 386339-ref.html
 == 409375.html 409375-ref.html
 == 413542-1.html 413542-1-ref.html
 == 413542-2.html 413542-2-ref.html
 fails-if(webrender) == 413928-1.html 413928-1-ref.html
-== 413928-2.html 413928-2-ref.html
+fails-if(webrender) == 413928-2.html 413928-2-ref.html
 == 425338-1a.html 425338-1-ref.html
 == 425338-1b.html 425338-1-ref.html
 == 489517-1.html 489517-1-ref.html
 == 489887-1.html 489887-1-ref.html
 == 492231-1.html 492231-1-ref.html
 == 496006-1.html 496006-1-ref.html
 == 503269-1.html 503269-1-ref.html
 == 503957-1.html 503957-1-ref.html
--- a/memory/replace/dmd/block_analyzer.py
+++ b/memory/replace/dmd/block_analyzer.py
@@ -83,17 +83,17 @@ parser.add_argument('--info', dest='info
 
 parser.add_argument('-sfl', '--max-stack-frame-length', type=int,
                     default=150,
                     help='Maximum number of characters to print from each stack frame')
 
 parser.add_argument('-a', '--ignore-alloc-fns', action='store_true',
                     help='ignore allocation functions at the start of traces')
 
-parser.add_argument('-f', '--max-frames', type=range_1_24,
+parser.add_argument('-f', '--max-frames', type=range_1_24, default=8,
                     help='maximum number of frames to consider in each trace')
 
 parser.add_argument('-c', '--chain-reports', action='store_true',
                     help='if only one block is found to hold onto the object, report the next one, too')
 
 
 ####
 
--- a/memory/replace/dmd/dmd.py
+++ b/memory/replace/dmd/dmd.py
@@ -161,17 +161,17 @@ unless --no-fix-stacks is specified; sta
 and may take some time. If specified, the BREAKPAD_SYMBOLS_PATH environment
 variable is used to find breakpad symbols for stack fixing.
 '''
     p = argparse.ArgumentParser(description=description)
 
     p.add_argument('-o', '--output', type=argparse.FileType('w'),
                    help='output file; stdout if unspecified')
 
-    p.add_argument('-f', '--max-frames', type=range_1_24,
+    p.add_argument('-f', '--max-frames', type=range_1_24, default=8,
                    help='maximum number of frames to consider in each trace')
 
     p.add_argument('-s', '--sort-by', choices=sortByChoices.keys(),
                    default='usable',
                    help='sort the records by a particular metric')
 
     p.add_argument('-a', '--ignore-alloc-fns', action='store_true',
                    help='ignore allocation functions at the start of traces')
--- a/memory/replace/dmd/test/script-max-frames-8-expected.txt
+++ b/memory/replace/dmd/test/script-max-frames-8-expected.txt
@@ -1,10 +1,10 @@
 #-----------------------------------------------------------------
-# dmd.py --filter-stacks-for-testing -o script-max-frames-8-actual.txt --max-frames=8 script-max-frames.json
+# dmd.py --filter-stacks-for-testing -o script-max-frames-8-actual.txt script-max-frames.json
 
 Invocation {
   $DMD = '--mode=live --stacks=full'
   Mode = 'live'
 }
 
 #-----------------------------------------------------------------
 
--- a/memory/replace/dmd/test/test_dmd.js
+++ b/memory/replace/dmd/test/test_dmd.js
@@ -182,17 +182,17 @@ function run_test() {
   // explaining how they work, but JSON doesn't allow comments, so I've put
   // explanations here.
 
   // This just tests that stack traces of various lengths are truncated
   // appropriately. The number of records in the output is different for each
   // of the tested values.
   jsonFile = FileUtils.getFile("CurWorkD", ["script-max-frames.json"]);
   test("script-max-frames-8",
-       ["--max-frames=8", jsonFile.path]);
+       [jsonFile.path]);  // --max-frames=8 is the default
   test("script-max-frames-3",
        ["--max-frames=3", "--no-fix-stacks", jsonFile.path]);
   test("script-max-frames-1",
        ["--max-frames=1", jsonFile.path]);
 
   // This file has three records that are shown in a different order for each
   // of the different sort values. It also tests the handling of gzipped JSON
   // files.
--- a/mobile/android/components/extensions/ext-utils.js
+++ b/mobile/android/components/extensions/ext-utils.js
@@ -1,17 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 /* globals TabBase, WindowBase, TabTrackerBase, WindowTrackerBase, TabManagerBase, WindowManagerBase */
-Cu.import("resource://gre/modules/ExtensionTabs.jsm");
 /* globals EventDispatcher */
 Cu.import("resource://gre/modules/Messaging.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   DefaultWeakMap,
   ExtensionError,
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -22,19 +22,16 @@ from mozboot.openbsd import OpenBSDBoots
 from mozboot.archlinux import ArchlinuxBootstrapper
 from mozboot.windows import WindowsBootstrapper
 from mozboot.mozillabuild import MozillaBuildBootstrapper
 from mozboot.util import (
     get_state_dir,
 )
 
 APPLICATION_CHOICE = '''
-Please choose the version of Firefox you want to build:
-%s
-
 Note on Artifact Mode:
 
 Firefox for Desktop and Android supports a fast build mode called
 artifact mode. Artifact mode downloads pre-built C++ components rather
 than building them locally, trading bandwidth for time.
 
 Artifact builds will be useful to many developers who are not working
 with compiled code. If you want to work on look-and-feel of Firefox,
@@ -51,16 +48,18 @@ want to work on web rendering, you want 
 
 If you don't know what you want, start with just Artifact Mode of the desired
 platform. Your builds will be much shorter than if you build Gecko as well.
 But don't worry! You can always switch configurations later.
 
 You can learn more about Artifact mode builds at
 https://developer.mozilla.org/en-US/docs/Artifact_builds.
 
+Please choose the version of Firefox you want to build:
+%s
 Your choice: '''
 
 APPLICATIONS_LIST=[
     ('Firefox for Desktop Artifact Mode', 'browser_artifact_mode'),
     ('Firefox for Desktop', 'browser'),
     ('Firefox for Android Artifact Mode', 'mobile_android_artifact_mode'),
     ('Firefox for Android', 'mobile_android'),
 ]
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -169,27 +169,30 @@ HistoryStore.prototype = {
       let guid = await this.GUIDForUri(url, true);
       urlsByGUID[guid] = url;
     }
     return urlsByGUID;
   },
 
   async applyIncomingBatch(records) {
     let failed = [];
+    let blockers = [];
 
     // Convert incoming records to mozIPlaceInfo objects. Some records can be
     // ignored or handled directly, so we're rewriting the array in-place.
     let i, k;
     for (i = 0, k = 0; i < records.length; i++) {
       let record = records[k] = records[i];
       let shouldApply;
 
       try {
         if (record.deleted) {
-          await this.remove(record);
+          let promise = this.remove(record);
+          promise = promise.catch(ex => failed.push(record.id));
+          blockers.push(promise);
 
           // No further processing needed. Remove it from the list.
           shouldApply = false;
         } else {
           shouldApply = await this._recordToPlaceInfo(record);
         }
       } catch (ex) {
         if (Async.isShutdownException(ex)) {
@@ -201,33 +204,47 @@ HistoryStore.prototype = {
 
       if (shouldApply) {
         k += 1;
       }
     }
     records.length = k; // truncate array
 
     if (records.length) {
-      await PlacesUtils.history.insertMany(records)
+      blockers.push(new Promise(resolve => {
+        let updatePlacesCallback = {
+          handleResult: function handleResult() {},
+          handleError: function handleError(resultCode, placeInfo) {
+            failed.push(placeInfo.guid);
+          },
+          handleCompletion: resolve,
+        };
+        this._asyncHistory.updatePlaces(records, updatePlacesCallback);
+      }));
     }
 
+    // failed is updated asynchronously, hence the await on blockers.
+    await Promise.all(blockers);
     return failed;
   },
 
   /**
    * Converts a Sync history record to a mozIPlaceInfo.
    *
    * Throws if an invalid record is encountered (invalid URI, etc.),
    * returns true if the record is to be applied, false otherwise
    * (no visits to add, etc.),
    */
   async _recordToPlaceInfo(record) {
     // Sort out invalid URIs and ones Places just simply doesn't want.
-    record.url = PlacesUtils.normalizeToURLOrGUID(record.histUri);
     record.uri = Utils.makeURI(record.histUri);
+    if (!record.uri) {
+      this._log.warn("Attempted to process invalid URI, skipping.");
+      throw new Error("Invalid URI in record");
+    }
 
     if (!Utils.checkGUID(record.id)) {
       this._log.warn("Encountered record with invalid GUID: " + record.id);
       return false;
     }
     record.guid = record.id;
 
     if (!PlacesUtils.history.canAddURI(record.uri)) {
@@ -274,18 +291,18 @@ HistoryStore.prototype = {
       visit.date = Math.round(visit.date);
 
       if (curVisits.indexOf(visit.date + "," + visit.type) != -1) {
         // Visit is a dupe, don't increment 'k' so the element will be
         // overwritten.
         continue;
       }
 
-      visit.date = PlacesUtils.toDate(visit.date);
-      visit.transition = visit.type;
+      visit.visitDate = visit.date;
+      visit.transitionType = visit.type;
       k += 1;
     }
     record.visits.length = k; // truncate array
 
     // No update if there aren't any visits to apply.
     // mozIAsyncHistory::updatePlaces() wants at least one visit.
     // In any case, the only thing we could change would be the title
     // and that shouldn't change without a visit.
--- a/taskcluster/docker/funsize-update-generator/requirements.txt
+++ b/taskcluster/docker/funsize-update-generator/requirements.txt
@@ -1,2 +1,3 @@
 mar==2.1.2
+backports.lzma==0.0.8
 redo
--- a/testing/web-platform/tests/intersection-observer/timestamp.html
+++ b/testing/web-platform/tests/intersection-observer/timestamp.html
@@ -83,17 +83,22 @@ function step2() {
   document.scrollingElement.scrollTop = 0;
   var topWindowTimeAfterNotification = performance.now();
   var iframeWindowTimeAfterNotification = targetIframe.contentWindow.performance.now();
 
   // Test results are only significant if there's a gap between
   // top window time and iframe window time.
   assert_greater_than(topWindowTimeBeforeNotification, iframeWindowTimeAfterNotification,
     "Time ranges for top and iframe windows are disjoint. Times: " +
-      [topWindowTimeOnTestStart, topWindowTimeBeforeCreatingIframe, topWindowTimeBeforeNotification, topWindowTimeAfterNotification, iframeWindowTimeBeforeNotification, iframeWindowTimeAfterNotification]);
+      [topWindowTimeOnTestStart, topWindowTimeBeforeCreatingIframe,
+       topWindowTimeBeforeNotification, topWindowTimeAfterNotification,
+       iframeWindowTimeBeforeNotification, iframeWindowTimeAfterNotification,
+       topWindowEntries[1].time - topWindowTimeBeforeNotification,
+       iframeWindowEntries[1].time - iframeWindowTimeBeforeNotification
+      ]);
 
   assert_equals(topWindowEntries.length, 2, "Top window observer has two notifications.");
   assert_between_inclusive(
       topWindowEntries[1].time,
       topWindowTimeBeforeNotification,
       topWindowTimeAfterNotification,
       "Notification to top window observer is within the expected range.");
 
--- a/toolkit/components/extensions/ExtensionAPI.jsm
+++ b/toolkit/components/extensions/ExtensionAPI.jsm
@@ -13,20 +13,24 @@ const {classes: Cc, interfaces: Ci, util
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI",
                                   "resource://gre/modules/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+
 const global = this;
 
-class ExtensionAPI {
+class ExtensionAPI extends ExtensionUtils.EventEmitter {
   constructor(extension) {
+    super();
+
     this.extension = extension;
 
     extension.once("shutdown", () => {
       if (this.onShutdown) {
         this.onShutdown(extension.shutdownReason);
       }
       this.extension = null;
     });
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -10,16 +10,18 @@
  */
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 /* exported ExtensionCommon */
 
 this.EXPORTED_SYMBOLS = ["ExtensionCommon"];
 
+Cu.importGlobalProperties(["fetch"]);
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
@@ -613,16 +615,36 @@ function deepCopy(dest, source) {
       }
       deepCopy(dest[prop], source[prop]);
     } else {
       Object.defineProperty(dest, prop, desc);
     }
   }
 }
 
+function getChild(map, key) {
+  let child = map.children.get(key);
+  if (!child) {
+    child = {
+      modules: new Set(),
+      children: new Map(),
+    };
+
+    map.children.set(key, child);
+  }
+  return child;
+}
+
+function getPath(map, path) {
+  for (let key of path) {
+    map = getChild(map, key);
+  }
+  return map;
+}
+
 /**
  * Manages loading and accessing a set of APIs for a specific extension
  * context.
  *
  * @param {BaseContext} context
  *        The context to manage APIs for.
  * @param {SchemaAPIManager} apiManager
  *        The API manager holding the APIs to manage.
@@ -707,17 +729,17 @@ class CanOfAPIs {
 
     let obj = this.root;
     let modules = this.apiManager.modulePaths;
 
     for (let key of path.split(".")) {
       if (!obj) {
         return;
       }
-      modules = modules.get(key);
+      modules = getChild(modules, key);
 
       for (let name of modules.modules) {
         if (!this.apis.has(name)) {
           this.loadAPI(name);
         }
       }
 
       obj = obj[key];
@@ -743,17 +765,17 @@ class CanOfAPIs {
 
     let obj = this.root;
     let modules = this.apiManager.modulePaths;
 
     for (let key of path.split(".")) {
       if (!obj) {
         return;
       }
-      modules = modules.get(key);
+      modules = getChild(modules, key);
 
       for (let name of modules.modules) {
         if (!this.apis.has(name)) {
           await this.asyncLoadAPI(name);
         }
       }
 
       if (typeof obj[key] === "function") {
@@ -763,28 +785,16 @@ class CanOfAPIs {
       }
     }
 
     this.apiPaths.set(path, obj);
     return obj;
   }
 }
 
-class DeepMap extends DefaultMap {
-  constructor() {
-    super(() => new DeepMap());
-
-    this.modules = new Set();
-  }
-
-  getPath(path) {
-    return path.reduce((map, key) => map.get(key), this);
-  }
-}
-
 /**
  * @class APIModule
  * @abstract
  *
  * @property {string} url
  *       The URL of the script which contains the module's
  *       implementation. This script must define a global property
  *       matching the modules name, which must be a class constructor
@@ -827,27 +837,64 @@ class SchemaAPIManager extends EventEmit
    *     "proxy" - A proxy script process.
    */
   constructor(processType) {
     super();
     this.processType = processType;
     this.global = this._createExtGlobal();
 
     this.modules = new Map();
-    this.modulePaths = new DeepMap();
+    this.modulePaths = {children: new Map(), modules: new Set()};
     this.manifestKeys = new Map();
     this.eventModules = new DefaultMap(() => new Set());
 
+    this._modulesJSONLoaded = false;
+
     this.schemaURLs = new Set();
 
     this.apis = new DefaultWeakMap(() => new Map());
 
     this._scriptScopes = [];
   }
 
+  async loadModuleJSON(urls) {
+    function fetchJSON(url) {
+      return fetch(url).then(resp => resp.json());
+    }
+
+    for (let json of await Promise.all(urls.map(fetchJSON))) {
+      this.registerModules(json);
+    }
+
+    this._modulesJSONLoaded = true;
+
+    return new StructuredCloneHolder({
+      modules: this.modules,
+      modulePaths: this.modulePaths,
+      manifestKeys: this.manifestKeys,
+      eventModules: this.eventModules,
+      schemaURLs: this.schemaURLs,
+    });
+  }
+
+  initModuleData(moduleData) {
+    if (!this._modulesJSONLoaded) {
+      let data = moduleData.deserialize({});
+
+      this.modules = data.modules;
+      this.modulePaths = data.modulePaths;
+      this.manifestKeys = data.manifestKeys;
+      this.eventModules = new DefaultMap(() => new Set(),
+                                         data.eventModules);
+      this.schemaURLs = data.schemaURLs;
+    }
+
+    this._modulesJSONLoaded = true;
+  }
+
   /**
    * Registers a set of ExtensionAPI modules to be lazily loaded and
    * managed by this manager.
    *
    * @param {object} obj
    *        An object containing property for eacy API module to be
    *        registered. Each value should be an object implementing the
    *        APIModule interface.
@@ -873,17 +920,17 @@ class SchemaAPIManager extends EventEmit
         if (this.manifestKeys.has(key)) {
           throw new Error(`Manifest key '${key}' already registered by '${this.manifestKeys.get(key)}'`);
         }
 
         this.manifestKeys.set(key, name);
       }
 
       for (let path of details.paths || []) {
-        this.modulePaths.getPath(path).modules.add(name);
+        getPath(this.modulePaths, path).modules.add(name);
       }
     }
   }
 
   /**
    * Emits an `onManifestEntry` event for the top-level manifest entry
    * on all relevant {@link ExtensionAPI} instances for the given
    * extension.
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -59,26 +59,28 @@ var {
   defineLazyGetter,
   promiseDocumentLoaded,
   promiseEvent,
   promiseFileContents,
   promiseObserved,
 } = ExtensionUtils;
 
 const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
+const CATEGORY_EXTENSION_MODULES = "webextension-modules";
 const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas";
 const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts";
 
 let schemaURLs = new Set();
 
 schemaURLs.add("chrome://extensions/content/schemas/experiments.json");
 
 let GlobalManager;
 let ParentAPIManager;
 let ProxyMessenger;
+let StartupCache;
 
 // This object loads the ext-*.js scripts that define the extension API.
 let apiManager = new class extends SchemaAPIManager {
   constructor() {
     super("main");
     this.initialized = null;
 
     this.on("startup", (event, extension) => { // eslint-disable-line mozilla/balanced-listeners
@@ -88,28 +90,41 @@ let apiManager = new class extends Schem
           api.onStartup(extension.startupReason);
         }));
       }
 
       return Promise.all(promises);
     });
   }
 
+  getModuleJSONURLs() {
+    return Array.from(XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_MODULES),
+                      ([name, url]) => url);
+  }
+
   // Loads all the ext-*.js scripts currently registered.
   lazyInit() {
     if (this.initialized) {
       return this.initialized;
     }
 
-    let scripts = [];
+    let modulesPromise = StartupCache.other.get(
+      ["parentModules"],
+      () => this.loadModuleJSON(this.getModuleJSONURLs()));
+
+    let scriptURLs = [];
     for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS)) {
-      scripts.push(value);
+      scriptURLs.push(value);
     }
 
-    let promise = Promise.all(scripts.map(url => ChromeUtils.compileScript(url))).then(scripts => {
+    let promise = (async () => {
+      let scripts = await Promise.all(scriptURLs.map(url => ChromeUtils.compileScript(url)));
+
+      this.initModuleData(await modulesPromise);
+
       for (let script of scripts) {
         script.executeInGlobal(this.global);
       }
 
       // Load order matters here. The base manifest defines types which are
       // extended by other schemas, so needs to be loaded first.
       return Schemas.load(BASE_SCHEMA).then(() => {
         let promises = [];
@@ -119,17 +134,17 @@ let apiManager = new class extends Schem
         for (let url of this.schemaURLs) {
           promises.push(Schemas.load(url));
         }
         for (let url of schemaURLs) {
           promises.push(Schemas.load(url));
         }
         return Promise.all(promises);
       });
-    });
+    })();
 
     /* eslint-disable mozilla/balanced-listeners */
     Services.mm.addMessageListener("Extension:GetTabAndWindowId", this);
     /* eslint-enable mozilla/balanced-listeners */
 
     this.initialized = promise;
     return this.initialized;
   }
@@ -1356,20 +1371,20 @@ let IconDetails = {
   // These URLs should already be properly escaped, but make doubly sure CSS
   // string escape characters are escaped here, since they could lead to a
   // sandbox break.
   escapeUrl(url) {
     return url.replace(/[\\\s"]/g, encodeURIComponent);
   },
 };
 
-let StartupCache = {
+StartupCache = {
   DB_NAME: "ExtensionStartupCache",
 
-  STORE_NAMES: Object.freeze(["locales", "manifests", "permissions", "schemas"]),
+  STORE_NAMES: Object.freeze(["general", "locales", "manifests", "other", "permissions", "schemas"]),
 
   get file() {
     return FileUtils.getFile("ProfLD", ["startupCache", "webext.sc.lz4"]);
   },
 
   get saver() {
     if (!this._saver) {
       this._saver = new DeferredSave(this.file.path,
@@ -1408,31 +1423,41 @@ let StartupCache = {
     if (!this._dataPromise) {
       this._dataPromise = this._readData();
     }
     return this._dataPromise;
   },
 
   clearAddonData(id) {
     return Promise.all([
+      this.general.delete(id),
       this.locales.delete(id),
       this.manifests.delete(id),
       this.permissions.delete(id),
     ]).catch(e => {
       // Ignore the error. It happens when we try to flush the add-on
       // data after the AddonManager has flushed the entire startup cache.
     });
   },
 
   observe(subject, topic, data) {
     if (topic === "startupcache-invalidate") {
       this._data = new Map();
       this._dataPromise = Promise.resolve(this._data);
     }
   },
+
+  get(extension, path, createFunc) {
+    return this.general.get([extension.id, extension.version, ...path],
+                            createFunc);
+  },
+
+  delete(extension, path) {
+    return this.general.delete([extension.id, extension.version, ...path]);
+  },
 };
 
 // void StartupCache.dataPromise;
 
 Services.obs.addObserver(StartupCache, "startupcache-invalidate");
 
 class CacheStore {
   constructor(storeName) {
--- a/toolkit/components/extensions/ext-downloads.js
+++ b/toolkit/components/extensions/ext-downloads.js
@@ -8,20 +8,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
                                   "resource://gre/modules/DownloadPaths.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
-                                  "resource://gre/modules/EventEmitter.jsm");
 
 var {
+  EventEmitter,
   normalizeTime,
 } = ExtensionUtils;
 
 var {
   ignoreEvent,
 } = ExtensionCommon;
 
 const DOWNLOAD_ITEM_FIELDS = ["id", "url", "referrer", "filename", "incognito",
@@ -137,29 +136,32 @@ class DownloadItem {
     }
   }
 }
 
 
 // DownloadMap maps back and forth betwen the numeric identifiers used in
 // the downloads WebExtension API and a Download object from the Downloads jsm.
 // todo: make id and extension info persistent (bug 1247794)
-const DownloadMap = {
-  currentId: 0,
-  loadPromise: null,
+const DownloadMap = new class extends EventEmitter {
+  constructor() {
+    super();
+
+    this.currentId = 0;
+    this.loadPromise = null;
 
-  // Maps numeric id -> DownloadItem
-  byId: new Map(),
+    // Maps numeric id -> DownloadItem
+    this.byId = new Map();
 
-  // Maps Download object -> DownloadItem
-  byDownload: new WeakMap(),
+    // Maps Download object -> DownloadItem
+    this.byDownload = new WeakMap();
+  }
 
   lazyInit() {
     if (this.loadPromise == null) {
-      EventEmitter.decorate(this);
       this.loadPromise = Downloads.getList(Downloads.ALL).then(list => {
         let self = this;
         return list.addView({
           onDownloadAdded(download) {
             const item = self.newFromDownload(download, null);
             self.emit("create", item);
             item._storePrechange();
           },
@@ -187,54 +189,54 @@ const DownloadMap = {
             downloads.forEach(download => {
               this.newFromDownload(download, null);
             });
           })
           .then(() => list);
       });
     }
     return this.loadPromise;
-  },
+  }
 
   getDownloadList() {
     return this.lazyInit();
-  },
+  }
 
   getAll() {
     return this.lazyInit().then(() => this.byId.values());
-  },
+  }
 
   fromId(id) {
     const download = this.byId.get(id);
     if (!download) {
       throw new Error(`Invalid download id ${id}`);
     }
     return download;
-  },
+  }
 
   newFromDownload(download, extension) {
     if (this.byDownload.has(download)) {
       return this.byDownload.get(download);
     }
 
     const id = ++this.currentId;
     let item = new DownloadItem(id, download, extension);
     this.byId.set(id, item);
     this.byDownload.set(download, item);
     return item;
-  },
+  }
 
   erase(item) {
     // This will need to get more complicated for bug 1255507 but for now we
     // only work with downloads in the DownloadList from getAll()
     return this.getDownloadList().then(list => {
       list.remove(item.download);
     });
-  },
-};
+  }
+}();
 
 // Create a callable function that filters a DownloadItem based on a
 // query object of the type passed to search() or erase().
 const downloadQuery = query => {
   let queryTerms = [];
   let queryNegativeTerms = [];
   if (query.query != null) {
     for (let term of query.query) {
--- a/toolkit/components/extensions/ext-idle.js
+++ b/toolkit/components/extensions/ext-idle.js
@@ -1,15 +1,13 @@
 "use strict";
 
 // The ext-* files are imported into the same scopes.
 /* import-globals-from ext-toolkit.js */
 
-XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
-                                  "resource://gre/modules/EventEmitter.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "idleService",
                                    "@mozilla.org/widget/idleservice;1",
                                    "nsIIdleService");
 
 // WeakMap[Extension -> Object]
 let observersMap = new WeakMap();
 
 const getIdleObserverInfo = (extension, context) => {
@@ -32,24 +30,23 @@ const getIdleObserverInfo = (extension, 
   }
   return observerInfo;
 };
 
 const getIdleObserver = (extension, context) => {
   let observerInfo = getIdleObserverInfo(extension, context);
   let {observer, detectionInterval} = observerInfo;
   if (!observer) {
-    observer = {
-      observe: function(subject, topic, data) {
+    observer = new class extends ExtensionUtils.EventEmitter {
+      observe(subject, topic, data) {
         if (topic == "idle" || topic == "active") {
           this.emit("stateChanged", topic);
         }
-      },
-    };
-    EventEmitter.decorate(observer);
+      }
+    }();
     idleService.addIdleObserver(observer, detectionInterval);
     observerInfo.observer = observer;
     observerInfo.detectionInterval = detectionInterval;
   }
   return observer;
 };
 
 const setDetectionInterval = (extension, context, newInterval) => {
--- a/toolkit/components/extensions/ext-management.js
+++ b/toolkit/components/extensions/ext-management.js
@@ -9,18 +9,16 @@ XPCOMUtils.defineLazyGetter(this, "strBu
   const stringSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
   return stringSvc.createBundle("chrome://global/locale/extensions.properties");
 });
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "promptService",
                                    "@mozilla.org/embedcomp/prompt-service;1",
                                    "nsIPromptService");
-XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
-                                  "resource://gre/modules/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "GlobalManager", () => {
   const {GlobalManager} = Cu.import("resource://gre/modules/Extension.jsm", {});
   return GlobalManager;
 });
 
 var {
   ExtensionError,
@@ -86,20 +84,20 @@ const getExtensionInfoForAddon = (extens
   }
   return extInfo;
 };
 
 const listenerMap = new WeakMap();
 // Some management APIs are intentionally limited.
 const allowedTypes = ["theme", "extension"];
 
-class AddonListener {
+class AddonListener extends ExtensionUtils.EventEmitter {
   constructor() {
+    super();
     AddonManager.addAddonListener(this);
-    EventEmitter.decorate(this);
   }
 
   release() {
     AddonManager.removeAddonListener(this);
   }
 
   getExtensionInfo(addon) {
     let ext = addon.isWebExtension && GlobalManager.extensionMap.get(addon.id);
--- a/toolkit/components/extensions/ext-notifications.js
+++ b/toolkit/components/extensions/ext-notifications.js
@@ -1,14 +1,16 @@
 "use strict";
 
 // The ext-* files are imported into the same scopes.
 /* import-globals-from ext-toolkit.js */
 
-XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
+const ToolkitModules = {};
+
+XPCOMUtils.defineLazyModuleGetter(ToolkitModules, "EventEmitter",
                                   "resource://gre/modules/EventEmitter.jsm");
 
 var {
   ignoreEvent,
 } = ExtensionCommon;
 
 // WeakMap[Extension -> Map[id -> Notification]]
 let notificationsMap = new WeakMap();
@@ -93,17 +95,17 @@ this.notifications = class extends Exten
       notificationsMap.delete(extension);
     }
   }
 
   getAPI(context) {
     let {extension} = context;
 
     let map = new Map();
-    EventEmitter.decorate(map);
+    ToolkitModules.EventEmitter.decorate(map);
     notificationsMap.set(extension, map);
 
     return {
       notifications: {
         create: (notificationId, options) => {
           if (!notificationId) {
             notificationId = String(this.nextId++);
           }
rename from toolkit/components/extensions/ExtensionTabs.jsm
rename to toolkit/components/extensions/ext-tabs-base.js
--- a/toolkit/components/extensions/ExtensionTabs.jsm
+++ b/toolkit/components/extensions/ext-tabs-base.js
@@ -1,34 +1,27 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-/* exported TabTrackerBase, TabManagerBase, TabBase, WindowTrackerBase, WindowManagerBase, WindowBase */
-
-var EXPORTED_SYMBOLS = ["TabTrackerBase", "TabManagerBase", "TabBase", "WindowTrackerBase", "WindowManagerBase", "WindowBase"];
+/* globals EventEmitter */
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
-Cu.import("resource://gre/modules/ExtensionUtils.jsm");
-
-const {
+var {
   DefaultMap,
   DefaultWeakMap,
-  EventEmitter,
   ExtensionError,
   defineLazyGetter,
   getWinUtils,
 } = ExtensionUtils;
 
 /**
  * The platform-specific type of native tab objects, which are wrapped by
  * TabBase instances.
@@ -1842,8 +1835,10 @@ class WindowManagerBase {
    * @protected
    * @abstract
    */
   wrapWindow(window) {
     throw new Error("Not implemented");
   }
   /* eslint-enable valid-jsdoc */
 }
+
+Object.assign(global, {TabTrackerBase, TabManagerBase, TabBase, WindowTrackerBase, WindowManagerBase, WindowBase});
--- a/toolkit/components/extensions/ext-toolkit.js
+++ b/toolkit/components/extensions/ext-toolkit.js
@@ -12,16 +12,17 @@
           isDefaultCookieStoreId: false, isPrivateCookieStoreId:false,
           EventManager: false, InputEventManager: false */
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                   "resource://gre/modules/ContextualIdentityService.jsm");
 
 Cu.import("resource://gre/modules/ExtensionCommon.jsm");
 
+global.EventEmitter = ExtensionUtils.EventEmitter;
 global.EventManager = ExtensionCommon.EventManager;
 global.InputEventManager = class extends EventManager {
   constructor(...args) {
     super(...args);
     this.inputHandling = true;
   }
 };
 
@@ -73,195 +74,16 @@ global.getContainerForCookieStoreId = fu
 };
 
 global.isValidCookieStoreId = function(storeId) {
   return isDefaultCookieStoreId(storeId) ||
          isPrivateCookieStoreId(storeId) ||
          isContainerCookieStoreId(storeId);
 };
 
-extensions.registerModules({
-  manifest: {
-    schema: "chrome://extensions/content/schemas/extension_types.json",
-    scopes: [],
-  },
-  alarms: {
-    url: "chrome://extensions/content/ext-alarms.js",
-    schema: "chrome://extensions/content/schemas/alarms.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["alarms"],
-    ],
-  },
-  backgroundPage: {
-    url: "chrome://extensions/content/ext-backgroundPage.js",
-    scopes: ["addon_parent"],
-    manifest: ["background"],
-  },
-  browserSettings: {
-    url: "chrome://extensions/content/ext-browserSettings.js",
-    schema: "chrome://extensions/content/schemas/browser_settings.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["browserSettings"],
-    ],
-  },
-  contextualIdentities: {
-    url: "chrome://extensions/content/ext-contextualIdentities.js",
-    schema: "chrome://extensions/content/schemas/contextual_identities.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["contextualIdentities"],
-    ],
-  },
-  cookies: {
-    url: "chrome://extensions/content/ext-cookies.js",
-    schema: "chrome://extensions/content/schemas/cookies.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["cookies"],
-    ],
-  },
-  downloads: {
-    url: "chrome://extensions/content/ext-downloads.js",
-    schema: "chrome://extensions/content/schemas/downloads.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["downloads"],
-    ],
-  },
-  extension: {
-    url: "chrome://extensions/content/ext-extension.js",
-    schema: "chrome://extensions/content/schemas/extension.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["extension"],
-    ],
-  },
-  geolocation: {
-    url: "chrome://extensions/content/ext-geolocation.js",
-    events: ["startup"],
-  },
-  i18n: {
-    url: "chrome://extensions/content/ext-i18n.js",
-    schema: "chrome://extensions/content/schemas/i18n.json",
-    scopes: ["addon_parent", "content_child", "devtools_child"],
-    paths: [
-      ["i18n"],
-    ],
-  },
-  idle: {
-    url: "chrome://extensions/content/ext-idle.js",
-    schema: "chrome://extensions/content/schemas/idle.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["idle"],
-    ],
-  },
-  management: {
-    url: "chrome://extensions/content/ext-management.js",
-    schema: "chrome://extensions/content/schemas/management.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["management"],
-    ],
-  },
-  notifications: {
-    url: "chrome://extensions/content/ext-notifications.js",
-    schema: "chrome://extensions/content/schemas/notifications.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["notifications"],
-    ],
-  },
-  permissions: {
-    url: "chrome://extensions/content/ext-permissions.js",
-    schema: "chrome://extensions/content/schemas/permissions.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["permissions"],
-    ],
-  },
-  privacy: {
-    url: "chrome://extensions/content/ext-privacy.js",
-    schema: "chrome://extensions/content/schemas/privacy.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["privacy"],
-    ],
-  },
-  protocolHandlers: {
-    url: "chrome://extensions/content/ext-protocolHandlers.js",
-    schema: "chrome://extensions/content/schemas/extension_protocol_handlers.json",
-    scopes: ["addon_parent"],
-    manifest: ["protocol_handlers"],
-  },
-  proxy: {
-    url: "chrome://extensions/content/ext-proxy.js",
-    schema: "chrome://extensions/content/schemas/proxy.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["proxy"],
-    ],
-  },
-  runtime: {
-    url: "chrome://extensions/content/ext-runtime.js",
-    schema: "chrome://extensions/content/schemas/runtime.json",
-    scopes: ["addon_parent", "content_parent", "devtools_parent"],
-    paths: [
-      ["runtime"],
-    ],
-  },
-  storage: {
-    url: "chrome://extensions/content/ext-storage.js",
-    schema: "chrome://extensions/content/schemas/storage.json",
-    scopes: ["addon_parent", "content_parent", "devtools_parent"],
-    paths: [
-      ["storage"],
-    ],
-  },
-  test: {
-    schema: "chrome://extensions/content/schemas/test.json",
-    scopes: [],
-  },
-  theme: {
-    url: "chrome://extensions/content/ext-theme.js",
-    schema: "chrome://extensions/content/schemas/theme.json",
-    scopes: ["addon_parent"],
-    manifest: ["theme"],
-    paths: [
-      ["theme"],
-    ],
-  },
-  topSites: {
-    url: "chrome://extensions/content/ext-topSites.js",
-    schema: "chrome://extensions/content/schemas/top_sites.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["topSites"],
-    ],
-  },
-  webNavigation: {
-    url: "chrome://extensions/content/ext-webNavigation.js",
-    schema: "chrome://extensions/content/schemas/web_navigation.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["webNavigation"],
-    ],
-  },
-  webRequest: {
-    url: "chrome://extensions/content/ext-webRequest.js",
-    schema: "chrome://extensions/content/schemas/web_request.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["webRequest"],
-    ],
-  },
-});
-
 if (AppConstants.MOZ_BUILD_APP === "browser") {
   extensions.registerModules({
     identity: {
       schema: "chrome://extensions/content/schemas/identity.json",
       scopes: ["addon_parent"],
     },
   });
 }
copy from toolkit/components/extensions/ext-toolkit.js
copy to toolkit/components/extensions/ext-toolkit.json
--- a/toolkit/components/extensions/ext-toolkit.js
+++ b/toolkit/components/extensions/ext-toolkit.json
@@ -1,267 +1,178 @@
-"use strict";
-
-// These are defined on "global" which is used for the same scopes as the other
-// ext-*.js files.
-/* exported getCookieStoreIdForTab, getCookieStoreIdForContainer,
-            getContainerForCookieStoreId,
-            isValidCookieStoreId, isContainerCookieStoreId,
-            EventManager, InputEventManager */
-/* global getCookieStoreIdForTab:false, getCookieStoreIdForContainer:false,
-          getContainerForCookieStoreId: false,
-          isValidCookieStoreId:false, isContainerCookieStoreId:false,
-          isDefaultCookieStoreId: false, isPrivateCookieStoreId:false,
-          EventManager: false, InputEventManager: false */
-
-XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource://gre/modules/ContextualIdentityService.jsm");
-
-Cu.import("resource://gre/modules/ExtensionCommon.jsm");
-
-global.EventManager = ExtensionCommon.EventManager;
-global.InputEventManager = class extends EventManager {
-  constructor(...args) {
-    super(...args);
-    this.inputHandling = true;
-  }
-};
-
-/* 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) {
-    return PRIVATE_STORE;
-  }
-
-  if (tab.userContextId) {
-    return getCookieStoreIdForContainer(tab.userContextId);
-  }
-
-  return DEFAULT_STORE;
-};
-
-global.isPrivateCookieStoreId = function(storeId) {
-  return storeId == PRIVATE_STORE;
-};
-
-global.isDefaultCookieStoreId = function(storeId) {
-  return storeId == DEFAULT_STORE;
-};
-
-global.isContainerCookieStoreId = function(storeId) {
-  return storeId !== null && storeId.startsWith(CONTAINER_STORE);
-};
-
-global.getCookieStoreIdForContainer = function(containerId) {
-  return CONTAINER_STORE + containerId;
-};
-
-global.getContainerForCookieStoreId = function(storeId) {
-  if (!isContainerCookieStoreId(storeId)) {
-    return null;
-  }
-
-  let containerId = storeId.substring(CONTAINER_STORE.length);
-  if (ContextualIdentityService.getPublicIdentityFromId(containerId)) {
-    return parseInt(containerId, 10);
-  }
-
-  return null;
-};
-
-global.isValidCookieStoreId = function(storeId) {
-  return isDefaultCookieStoreId(storeId) ||
-         isPrivateCookieStoreId(storeId) ||
-         isContainerCookieStoreId(storeId);
-};
-
-extensions.registerModules({
-  manifest: {
-    schema: "chrome://extensions/content/schemas/extension_types.json",
-    scopes: [],
+{
+  "manifest": {
+    "schema": "chrome://extensions/content/schemas/extension_types.json",
+    "scopes": []
+  },
+  "alarms": {
+    "url": "chrome://extensions/content/ext-alarms.js",
+    "schema": "chrome://extensions/content/schemas/alarms.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["alarms"]
+    ]
+  },
+  "backgroundPage": {
+    "url": "chrome://extensions/content/ext-backgroundPage.js",
+    "scopes": ["addon_parent"],
+    "manifest": ["background"]
+  },
+  "browserSettings": {
+    "url": "chrome://extensions/content/ext-browserSettings.js",
+    "schema": "chrome://extensions/content/schemas/browser_settings.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["browserSettings"]
+    ]
+  },
+  "contextualIdentities": {
+    "url": "chrome://extensions/content/ext-contextualIdentities.js",
+    "schema": "chrome://extensions/content/schemas/contextual_identities.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["contextualIdentities"]
+    ]
+  },
+  "cookies": {
+    "url": "chrome://extensions/content/ext-cookies.js",
+    "schema": "chrome://extensions/content/schemas/cookies.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["cookies"]
+    ]
   },
-  alarms: {
-    url: "chrome://extensions/content/ext-alarms.js",
-    schema: "chrome://extensions/content/schemas/alarms.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["alarms"],
-    ],
+  "downloads": {
+    "url": "chrome://extensions/content/ext-downloads.js",
+    "schema": "chrome://extensions/content/schemas/downloads.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["downloads"]
+    ]
   },
-  backgroundPage: {
-    url: "chrome://extensions/content/ext-backgroundPage.js",
-    scopes: ["addon_parent"],
-    manifest: ["background"],
+  "extension": {
+    "url": "chrome://extensions/content/ext-extension.js",
+    "schema": "chrome://extensions/content/schemas/extension.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["extension"]
+    ]
   },
-  browserSettings: {
-    url: "chrome://extensions/content/ext-browserSettings.js",
-    schema: "chrome://extensions/content/schemas/browser_settings.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["browserSettings"],
-    ],
+  "geolocation": {
+    "url": "chrome://extensions/content/ext-geolocation.js",
+    "events": ["startup"]
   },
-  contextualIdentities: {
-    url: "chrome://extensions/content/ext-contextualIdentities.js",
-    schema: "chrome://extensions/content/schemas/contextual_identities.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["contextualIdentities"],
-    ],
+  "i18n": {
+    "url": "chrome://extensions/content/ext-i18n.js",
+    "schema": "chrome://extensions/content/schemas/i18n.json",
+    "scopes": ["addon_parent", "content_child", "devtools_child"],
+    "paths": [
+      ["i18n"]
+    ]
   },
-  cookies: {
-    url: "chrome://extensions/content/ext-cookies.js",
-    schema: "chrome://extensions/content/schemas/cookies.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["cookies"],
-    ],
+  "idle": {
+    "url": "chrome://extensions/content/ext-idle.js",
+    "schema": "chrome://extensions/content/schemas/idle.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["idle"]
+    ]
   },
-  downloads: {
-    url: "chrome://extensions/content/ext-downloads.js",
-    schema: "chrome://extensions/content/schemas/downloads.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["downloads"],
-    ],
+  "management": {
+    "url": "chrome://extensions/content/ext-management.js",
+    "schema": "chrome://extensions/content/schemas/management.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["management"]
+    ]
   },
-  extension: {
-    url: "chrome://extensions/content/ext-extension.js",
-    schema: "chrome://extensions/content/schemas/extension.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["extension"],
-    ],
-  },
-  geolocation: {
-    url: "chrome://extensions/content/ext-geolocation.js",
-    events: ["startup"],
+  "notifications": {
+    "url": "chrome://extensions/content/ext-notifications.js",
+    "schema": "chrome://extensions/content/schemas/notifications.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["notifications"]
+    ]
   },
-  i18n: {
-    url: "chrome://extensions/content/ext-i18n.js",
-    schema: "chrome://extensions/content/schemas/i18n.json",
-    scopes: ["addon_parent", "content_child", "devtools_child"],
-    paths: [
-      ["i18n"],
-    ],
-  },
-  idle: {
-    url: "chrome://extensions/content/ext-idle.js",
-    schema: "chrome://extensions/content/schemas/idle.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["idle"],
-    ],
+  "permissions": {
+    "url": "chrome://extensions/content/ext-permissions.js",
+    "schema": "chrome://extensions/content/schemas/permissions.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["permissions"]
+    ]
   },
-  management: {
-    url: "chrome://extensions/content/ext-management.js",
-    schema: "chrome://extensions/content/schemas/management.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["management"],
-    ],
+  "privacy": {
+    "url": "chrome://extensions/content/ext-privacy.js",
+    "schema": "chrome://extensions/content/schemas/privacy.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["privacy"]
+    ]
   },
-  notifications: {
-    url: "chrome://extensions/content/ext-notifications.js",
-    schema: "chrome://extensions/content/schemas/notifications.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["notifications"],
-    ],
+  "protocolHandlers": {
+    "url": "chrome://extensions/content/ext-protocolHandlers.js",
+    "schema": "chrome://extensions/content/schemas/extension_protocol_handlers.json",
+    "scopes": ["addon_parent"],
+    "manifest": ["protocol_handlers"]
   },
-  permissions: {
-    url: "chrome://extensions/content/ext-permissions.js",
-    schema: "chrome://extensions/content/schemas/permissions.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["permissions"],
-    ],
+  "proxy": {
+    "url": "chrome://extensions/content/ext-proxy.js",
+    "schema": "chrome://extensions/content/schemas/proxy.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["proxy"]
+    ]
   },
-  privacy: {
-    url: "chrome://extensions/content/ext-privacy.js",
-    schema: "chrome://extensions/content/schemas/privacy.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["privacy"],
-    ],
-  },
-  protocolHandlers: {
-    url: "chrome://extensions/content/ext-protocolHandlers.js",
-    schema: "chrome://extensions/content/schemas/extension_protocol_handlers.json",
-    scopes: ["addon_parent"],
-    manifest: ["protocol_handlers"],
+  "runtime": {
+    "url": "chrome://extensions/content/ext-runtime.js",
+    "schema": "chrome://extensions/content/schemas/runtime.json",
+    "scopes": ["addon_parent", "content_parent", "devtools_parent"],
+    "paths": [
+      ["runtime"]
+    ]
   },
-  proxy: {
-    url: "chrome://extensions/content/ext-proxy.js",
-    schema: "chrome://extensions/content/schemas/proxy.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["proxy"],
-    ],
+  "storage": {
+    "url": "chrome://extensions/content/ext-storage.js",
+    "schema": "chrome://extensions/content/schemas/storage.json",
+    "scopes": ["addon_parent", "content_parent", "devtools_parent"],
+    "paths": [
+      ["storage"]
+    ]
   },
-  runtime: {
-    url: "chrome://extensions/content/ext-runtime.js",
-    schema: "chrome://extensions/content/schemas/runtime.json",
-    scopes: ["addon_parent", "content_parent", "devtools_parent"],
-    paths: [
-      ["runtime"],
-    ],
+  "test": {
+    "schema": "chrome://extensions/content/schemas/test.json",
+    "scopes": []
   },
-  storage: {
-    url: "chrome://extensions/content/ext-storage.js",
-    schema: "chrome://extensions/content/schemas/storage.json",
-    scopes: ["addon_parent", "content_parent", "devtools_parent"],
-    paths: [
-      ["storage"],
-    ],
-  },
-  test: {
-    schema: "chrome://extensions/content/schemas/test.json",
-    scopes: [],
+  "theme": {
+    "url": "chrome://extensions/content/ext-theme.js",
+    "schema": "chrome://extensions/content/schemas/theme.json",
+    "scopes": ["addon_parent"],
+    "manifest": ["theme"],
+    "paths": [
+      ["theme"]
+    ]
   },
-  theme: {
-    url: "chrome://extensions/content/ext-theme.js",
-    schema: "chrome://extensions/content/schemas/theme.json",
-    scopes: ["addon_parent"],
-    manifest: ["theme"],
-    paths: [
-      ["theme"],
-    ],
-  },
-  topSites: {
-    url: "chrome://extensions/content/ext-topSites.js",
-    schema: "chrome://extensions/content/schemas/top_sites.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["topSites"],
-    ],
+  "topSites": {
+    "url": "chrome://extensions/content/ext-topSites.js",
+    "schema": "chrome://extensions/content/schemas/top_sites.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["topSites"]
+    ]
   },
-  webNavigation: {
-    url: "chrome://extensions/content/ext-webNavigation.js",
-    schema: "chrome://extensions/content/schemas/web_navigation.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["webNavigation"],
-    ],
+  "webNavigation": {
+    "url": "chrome://extensions/content/ext-webNavigation.js",
+    "schema": "chrome://extensions/content/schemas/web_navigation.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["webNavigation"]
+    ]
   },
-  webRequest: {
-    url: "chrome://extensions/content/ext-webRequest.js",
-    schema: "chrome://extensions/content/schemas/web_request.json",
-    scopes: ["addon_parent"],
-    paths: [
-      ["webRequest"],
-    ],
-  },
-});
-
-if (AppConstants.MOZ_BUILD_APP === "browser") {
-  extensions.registerModules({
-    identity: {
-      schema: "chrome://extensions/content/schemas/identity.json",
-      scopes: ["addon_parent"],
-    },
-  });
+  "webRequest": {
+    "url": "chrome://extensions/content/ext-webRequest.js",
+    "schema": "chrome://extensions/content/schemas/web_request.json",
+    "scopes": ["addon_parent"],
+    "paths": [
+      ["webRequest"]
+    ]
+  }
 }
--- a/toolkit/components/extensions/extensions-toolkit.manifest
+++ b/toolkit/components/extensions/extensions-toolkit.manifest
@@ -1,10 +1,14 @@
 # scripts
-category webextension-scripts toolkit chrome://extensions/content/ext-toolkit.js
+category webextension-modules toolkit chrome://extensions/content/ext-toolkit.json
+
+category webextension-scripts a-toolkit chrome://extensions/content/ext-toolkit.js
+category webextension-scripts b-tabs-base chrome://extensions/content/ext-tabs-base.js
+
 category webextension-scripts-content toolkit chrome://extensions/content/ext-c-toolkit.js
 category webextension-scripts-devtools toolkit chrome://extensions/content/ext-c-toolkit.js
 category webextension-scripts-addon toolkit chrome://extensions/content/ext-c-toolkit.js
 
 category webextension-schemas events chrome://extensions/content/schemas/events.json
 category webextension-schemas native_host_manifest chrome://extensions/content/schemas/native_host_manifest.json
 category webextension-schemas types chrome://extensions/content/schemas/types.json
 
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -19,18 +19,20 @@ toolkit.jar:
     content/extensions/ext-management.js
     content/extensions/ext-notifications.js
     content/extensions/ext-permissions.js
     content/extensions/ext-privacy.js
     content/extensions/ext-protocolHandlers.js
     content/extensions/ext-proxy.js
     content/extensions/ext-runtime.js
     content/extensions/ext-storage.js
+    content/extensions/ext-tabs-base.js
     content/extensions/ext-theme.js
     content/extensions/ext-toolkit.js
+    content/extensions/ext-toolkit.json
     content/extensions/ext-topSites.js
     content/extensions/ext-webRequest.js
     content/extensions/ext-webNavigation.js
     # Below is a separate group using the naming convention ext-c-*.js that run
     # in the child process.
     content/extensions/ext-c-backgroundPage.js
     content/extensions/ext-c-extension.js
 #ifndef ANDROID
--- a/toolkit/components/extensions/moz.build
+++ b/toolkit/components/extensions/moz.build
@@ -16,17 +16,16 @@ EXTRA_JS_MODULES += [
     'ExtensionContent.jsm',
     'ExtensionPageChild.jsm',
     'ExtensionParent.jsm',
     'ExtensionPermissions.jsm',
     'ExtensionPreferencesManager.jsm',
     'ExtensionSettingsStore.jsm',
     'ExtensionStorage.jsm',
     'ExtensionStorageSync.jsm',
-    'ExtensionTabs.jsm',
     'ExtensionUtils.jsm',
     'LegacyExtensionsUtils.jsm',
     'MessageChannel.jsm',
     'NativeMessaging.jsm',
     'ProxyScriptContext.jsm',
     'Schemas.jsm',
 ]
 
--- a/widget/tests/chrome.ini
+++ b/widget/tests/chrome.ini
@@ -70,21 +70,23 @@ skip-if = true # Bug 1207190
 skip-if = toolkit != "cocoa"
 support-files = standalone_native_menu_window.xul
 [test_bug586713.xul]
 skip-if = toolkit != "cocoa"
 support-files = bug586713_window.xul
 [test_key_event_counts.xul]
 skip-if = toolkit != "cocoa"
 [test_bug596600.xul]
+support-files = file_bug596600.html
 skip-if = toolkit != "cocoa"
 [test_bug673301.xul]
 subsuite = clipboard
 skip-if = toolkit != "cocoa"
 [test_secure_input.html]
+support-files = file_secure_input.html
 skip-if = toolkit != "cocoa"
 [test_native_key_bindings_mac.html]
 skip-if = toolkit != "cocoa"
 [test_system_status_bar.xul]
 skip-if = toolkit != "cocoa"
 
 # Windows
 # taskbar_previews.xul
new file mode 100644
--- /dev/null
+++ b/widget/tests/file_bug596600.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+Content page
+</body>
new file mode 100644
--- /dev/null
+++ b/widget/tests/file_secure_input.html
@@ -0,0 +1,1 @@
+<input id="text" type"text"><input id="password" type"password">
--- a/widget/tests/test_bug596600.xul
+++ b/widget/tests/test_bug596600.xul
@@ -40,25 +40,26 @@ function openWindows() {
     SimpleTest.waitForFocus(attachIFrameToRightWindow, gRightWindow);
   }, gLeftWindow);
 }
 
 function attachIFrameToRightWindow() {
   gIFrame = gLeftWindow.document.createElementNS(XUL_NS, "iframe");
   gIFrame.setAttribute("type", "content");
   gIFrame.setAttribute("clickthrough", "never");
-  gIFrame.setAttribute("src", "data:text/html,<!DOCTYPE html>Content page");
+  gIFrame.setAttribute("src", "file_bug596600.html");
   gIFrame.style.width = "100px";
   gIFrame.style.height = "100px";
   gIFrame.style.margin = "50px";
   gLeftWindow.document.documentElement.appendChild(gIFrame);
-  gIFrame.contentWindow.addEventListener("load", function (e) {
-    gIFrame.removeEventListener("load", arguments.callee, false);
+  gIFrame.addEventListener("load", function (e) {
+    gIFrame.removeEventListener("load", arguments.callee, true);
     test1();
-  }, false);
+  }, true);
+
 }
 
 function test1() {
   // gRightWindow is active, gLeftWindow is inactive.
   moveMouseTo(0, 0, function () {
     var expectMouseOver = false, expectMouseOut = false;
     function mouseOverListener(e) {
       ok(expectMouseOver, "Got expected mouseover at " + e.screenX + ", " + e.screenY);
--- a/widget/tests/test_secure_input.html
+++ b/widget/tests/test_secure_input.html
@@ -76,17 +76,17 @@
     $("input_change").type = "password";
     sendAKeyEvent();
     ok(true, "Not crashed: input on <input type=\"password\"> changed from type=\"text\"");
     $("input_change").type = "text";
     sendAKeyEvent();
     ok(true, "Not crashed: input on <input type=\"text\"> changed from type=\"password\"");
 
     otherWindow =
-      window.open("data:text/html,<input id=\"text\" type\"text\"><input id=\"password\" type\"password\">",
+      window.open("file_secure_input.html",
                   "_blank", "chrome,width=100,height=100");
     ok(otherWindow, "failed to open other window");
     if (!otherWindow) {
       SimpleTest.finish();
       return;
     }
 
     $("input_text").focus();
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -369,18 +369,17 @@ interface nsIMemoryReporterManager : nsI
    * raster images in content.
    *
    * |storageSQLite| (UNITS_BYTES)  Memory used by SQLite.
    *
    * |lowMemoryEvents{Virtual,Physical}| (UNITS_COUNT_CUMULATIVE)  The number
    * of low-{virtual,physical}-memory events that have occurred since the
    * process started.
    *
-   * |ghostWindows| (UNITS_COUNT)  A cached value of the number of ghost
-   * windows. This should have been updated within the past 60s.
+   * |ghostWindows| (UNITS_COUNT)  The number of ghost windows.
    *
    * |pageFaultsHard| (UNITS_COUNT_CUMULATIVE)  The number of hard (a.k.a.
    * major) page faults that have occurred since the process started.
    */
   [must_use] readonly attribute int64_t vsize;
   [must_use] readonly attribute int64_t vsizeMaxContiguous;
   [must_use] readonly attribute int64_t resident;
   [must_use] readonly attribute int64_t residentFast;