Merge mozilla-central to autoland
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 02 Feb 2017 14:30:05 +0100
changeset 469875 bb1ad8ae88c31076be3cb06d673c258f1de52e32
parent 469874 9cd704d6900c90dbbfddb4b2fb3eabf1c521c1e4 (current diff)
parent 469617 823dc40ab5fe45344a611ff454212f8cf56e1435 (diff)
child 469876 ffd3ef23726e0027143ac948047ac63d41ae4700
push id43881
push userbmo:gps@mozilla.com
push dateThu, 02 Feb 2017 23:49:03 +0000
milestone54.0a1
Merge mozilla-central to autoland
layout/mathml/mathfontSymbol.properties
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoEventListener.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoRequest.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeEventListener.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeJSContainer.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/NativeJSObject.java
mobile/android/modules/TabMirror.jsm
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testGeckoRequest.java
mobile/android/tests/browser/robocop/testGeckoRequest.js
modules/libpref/init/all.js
toolkit/components/telemetry/Histograms.json
toolkit/content/tests/fennec-tile-testapp/application.ini
toolkit/content/tests/fennec-tile-testapp/chrome/chrome.manifest
toolkit/content/tests/fennec-tile-testapp/chrome/content/BrowserView.js
toolkit/content/tests/fennec-tile-testapp/chrome/content/FooScript.js
toolkit/content/tests/fennec-tile-testapp/chrome/content/TileManager.js
toolkit/content/tests/fennec-tile-testapp/chrome/content/WidgetStack.js
toolkit/content/tests/fennec-tile-testapp/chrome/content/firefoxOverlay.xul
toolkit/content/tests/fennec-tile-testapp/chrome/content/foo.xul
toolkit/content/tests/fennec-tile-testapp/chrome/content/main.xul
toolkit/content/tests/fennec-tile-testapp/chrome/content/overlay.js
toolkit/content/tests/fennec-tile-testapp/chrome/locale/en-US/tile.dtd
toolkit/content/tests/fennec-tile-testapp/chrome/locale/en-US/tile.properties
toolkit/content/tests/fennec-tile-testapp/chrome/skin/overlay.css
toolkit/content/tests/fennec-tile-testapp/defaults/preferences/prefs.js
toolkit/content/tests/fennec-tile-testapp/install.rdf
toolkit/content/tests/fennec-tile-testapp/logread.py
tools/profiler/core/ThreadProfile.cpp
tools/profiler/core/ThreadProfile.h
tools/profiler/gecko/SaveProfileTask.cpp
tools/profiler/gecko/SaveProfileTask.h
widget/android/NativeJSContainer.cpp
widget/android/NativeJSContainer.h
--- a/accessible/interfaces/gecko/IGeckoCustom.idl
+++ b/accessible/interfaces/gecko/IGeckoCustom.idl
@@ -8,16 +8,17 @@ import "objidl.idl";
 import "oaidl.idl";
 
 [object, uuid(7510360f-cdae-4de9-88c8-d167eda62afc)]
 interface IGeckoCustom : IUnknown
 {
   [propget] HRESULT ID([out, retval] unsigned __int64* aID);
   [propget] HRESULT anchorCount([out, retval] long* aCount);
   [propget] HRESULT DOMNodeID([out, retval] BSTR* aID);
+  [propget] HRESULT minimumIncrement([out, retval] double* aIncrement);
 }
 
 
 [
     uuid(55769d85-f830-4d76-9e39-3670914a28f7),
     helpstring("private custom gecko interfaces")
 ]
 library IGeckoCustom
--- a/accessible/ipc/win/ProxyAccessible.cpp
+++ b/accessible/ipc/win/ProxyAccessible.cpp
@@ -150,16 +150,33 @@ ProxyAccessible::Value(nsString& aValue)
   HRESULT hr = acc->get_accValue(kChildIdSelf, &result);
   _bstr_t resultWrap(result, false);
   if (FAILED(hr)) {
     return;
   }
   aValue = (wchar_t*)resultWrap;
 }
 
+double
+ProxyAccessible::Step()
+{
+  RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this);
+  if (!custom) {
+    return 0;
+  }
+
+  double increment;
+  HRESULT hr = custom->get_minimumIncrement(&increment);
+  if (FAILED(hr)) {
+    return 0;
+  }
+
+  return increment;
+}
+
 void
 ProxyAccessible::Description(nsString& aDesc) const
 {
   aDesc.Truncate();
   RefPtr<IAccessible> acc;
   if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
     return;
   }
--- a/accessible/windows/msaa/GeckoCustom.cpp
+++ b/accessible/windows/msaa/GeckoCustom.cpp
@@ -38,8 +38,15 @@ GeckoCustom::get_DOMNodeID(BSTR* aID)
 }
 
 STDMETHODIMP
 GeckoCustom::get_ID(uint64_t* aID)
 {
   *aID = mAcc->IsDoc() ? 0 : reinterpret_cast<uintptr_t>(mAcc.get());
   return S_OK;
 }
+
+STDMETHODIMP
+GeckoCustom::get_minimumIncrement(double* aIncrement)
+{
+  *aIncrement = mAcc->Step();
+  return S_OK;
+}
--- a/accessible/windows/msaa/GeckoCustom.h
+++ b/accessible/windows/msaa/GeckoCustom.h
@@ -24,16 +24,17 @@ public:
   explicit GeckoCustom(AccessibleWrap* aAcc) : mAcc(aAcc) {}
 
   // IUnknown
   DECL_IUNKNOWN
 
   virtual STDMETHODIMP get_anchorCount(long* aCount);
   virtual STDMETHODIMP get_DOMNodeID(BSTR* aID);
   virtual STDMETHODIMP get_ID(uint64_t* aID);
+  virtual STDMETHODIMP get_minimumIncrement(double* aIncrement);
 
 private:
   GeckoCustom() = delete;
   GeckoCustom& operator =(const GeckoCustom&) = delete;
   GeckoCustom(const GeckoCustom&) = delete;
   GeckoCustom(GeckoCustom&&) = delete;
   GeckoCustom& operator=(GeckoCustom&&) = delete;
 
--- a/accessible/xpcom/xpcAccessibleValue.cpp
+++ b/accessible/xpcom/xpcAccessibleValue.cpp
@@ -114,20 +114,16 @@ xpcAccessibleValue::GetMinimumIncrement(
 
   if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
     return NS_ERROR_FAILURE;
 
   double value;
   if (Intl().IsAccessible()) {
     value = Intl().AsAccessible()->Step();
   } else {
-#if defined(XP_WIN)
-    return NS_ERROR_NOT_IMPLEMENTED;
-#else
     value = Intl().AsProxy()->Step();
-#endif
   }
 
   if (!IsNaN(value))
     *aValue = value;
 
   return NS_OK;
 }
--- a/browser/base/content/test/referrer/browser.ini
+++ b/browser/base/content/test/referrer/browser.ini
@@ -14,13 +14,13 @@ skip-if = os == 'linux' # Bug 1145199
 [browser_referrer_open_link_in_tab.js]
 skip-if = os == 'linux' # Bug 1144816
 [browser_referrer_open_link_in_window.js]
 skip-if = os == 'linux' # Bug 1145199
 [browser_referrer_open_link_in_window_in_container.js]
 skip-if = os == 'linux' # Bug 1145199
 [browser_referrer_simple_click.js]
 [browser_referrer_open_link_in_container_tab.js]
-skip-if = os == 'linux' || e10s # Bug 1144816, Bug 1315042
+skip-if = os == 'linux' # Bug 1144816
 [browser_referrer_open_link_in_container_tab2.js]
 skip-if = os == 'linux' # Bug 1144816
 [browser_referrer_open_link_in_container_tab3.js]
 skip-if = os == 'linux' # Bug 1144816
--- a/browser/base/content/test/tabs/browser_allow_process_switches_despite_related_browser.js
+++ b/browser/base/content/test/tabs/browser_allow_process_switches_despite_related_browser.js
@@ -1,32 +1,33 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 
 const DUMMY_FILE = "dummy_page.html";
+const DATA_URI = "data:text/html,Hi";
+const DATA_URI_SOURCE = "view-source:" + DATA_URI;
 
 // Test for bug 1328829.
 add_task(function* () {
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
-                                                        "data:text/html,Hi");
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, DATA_URI);
   registerCleanupFunction(function* () {
     yield BrowserTestUtils.removeTab(tab);
   });
 
-  let promiseTab =
-    BrowserTestUtils.waitForNewTab(gBrowser, "view-source:data:text/html,Hi");
+  let promiseTab = BrowserTestUtils.waitForNewTab(gBrowser, DATA_URI_SOURCE);
   BrowserViewSource(tab.linkedBrowser);
   let viewSourceTab = yield promiseTab;
   registerCleanupFunction(function* () {
     yield BrowserTestUtils.removeTab(viewSourceTab);
   });
 
-
   let dummyPage = getChromeDir(getResolvedURI(gTestPath));
   dummyPage.append(DUMMY_FILE);
   const uriString = Services.io.newFileURI(dummyPage).spec;
 
   let viewSourceBrowser = viewSourceTab.linkedBrowser;
+  let promiseLoad =
+    BrowserTestUtils.browserLoaded(viewSourceBrowser, false, uriString);
   viewSourceBrowser.loadURI(uriString);
-  let href = yield BrowserTestUtils.browserLoaded(viewSourceBrowser);
+  let href = yield promiseLoad;
   is(href, uriString,
     "Check file:// URI loads in a browser that was previously for view-source");
 });
--- a/browser/components/extensions/.eslintrc.js
+++ b/browser/components/extensions/.eslintrc.js
@@ -1,23 +1,20 @@
 "use strict";
 
 module.exports = {  // eslint-disable-line no-undef
   "extends": "../../../toolkit/components/extensions/.eslintrc.js",
 
   "globals": {
-    "AllWindowEvents": true,
+    "EventEmitter": true,
+    "IconDetails": true,
+    "Tab": true,
+    "TabContext": true,
+    "Window": true,
+    "WindowEventManager": true,
     "browserActionFor": true,
-    "currentWindow": true,
-    "EventEmitter": true,
-    "getBrowserInfo": true,
     "getCookieStoreIdForTab": true,
-    "IconDetails": true,
     "makeWidgetId": true,
     "pageActionFor": true,
-    "PanelPopup": true,
-    "TabContext": true,
-    "ViewPopup": true,
-    "WindowEventManager": true,
-    "WindowListManager": true,
-    "WindowManager": true,
+    "tabTracker": true,
+    "windowTracker": true,
   },
 };
copy from browser/components/extensions/ext-utils.js
copy to browser/components/extensions/ExtensionPopups.jsm
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -1,51 +1,49 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+/* exported PanelPopup, ViewPopup */
+
+var EXPORTED_SYMBOLS = ["BasePopup", "PanelPopup", "ViewPopup"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
                                   "resource:///modules/E10SUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
-                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
+                                  "resource://gre/modules/ExtensionParent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                   "resource://gre/modules/Timer.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
-                                   "@mozilla.org/content/style-sheet-service;1",
-                                   "nsIStyleSheetService");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
-Cu.import("resource://gre/modules/ExtensionUtils.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
+var {
+  DefaultWeakMap,
+  promiseEvent,
+} = ExtensionUtils;
+
 
 const POPUP_LOAD_TIMEOUT_MS = 200;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
-var {
-  DefaultWeakMap,
-  promiseEvent,
-  SingletonEventManager,
-} = ExtensionUtils;
-
-// 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 => {
+function makeWidgetId(id) {
   id = id.toLowerCase();
   // FIXME: This allows for collisions.
   return id.replace(/[^a-z0-9_-]/g, "_");
-};
+}
 
 function promisePopupShown(popup) {
   return new Promise(resolve => {
     if (popup.state == "open") {
       resolve();
     } else {
       popup.addEventListener("popupshown", function(event) {
         resolve();
@@ -253,17 +251,17 @@ class BasePopup {
     if (this.extension.remote) {
       readyPromise = promiseEvent(browser, "XULFrameLoaderCreated");
     } else {
       readyPromise = promiseEvent(browser, "load");
     }
 
     viewNode.appendChild(browser);
 
-    extensions.emit("extension-browser-inserted", browser);
+    ExtensionParent.apiManager.emit("extension-browser-inserted", browser);
 
     let setupBrowser = browser => {
       let mm = browser.messageManager;
       mm.addMessageListener("DOMTitleChanged", this);
       mm.addMessageListener("Extension:BrowserBackgroundChanged", this);
       mm.addMessageListener("Extension:BrowserContentLoaded", this);
       mm.addMessageListener("Extension:BrowserResized", this);
       mm.addMessageListener("Extension:DOMWindowClose", this, true);
@@ -539,758 +537,8 @@ class ViewPopup extends BasePopup {
       CustomizableUI.hidePanelForNode(this.viewNode);
     } else if (this.attached) {
       this.destroyed = true;
     } else {
       this.destroy();
     }
   }
 }
-
-Object.assign(global, {PanelPopup, ViewPopup});
-
-// 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();
-
-  AllWindowEvents.addListener("progress", this);
-  AllWindowEvents.addListener("TabSelect", this);
-
-  EventEmitter.decorate(this);
-};
-
-TabContext.prototype = {
-  get(tab) {
-    if (!this.tabData.has(tab)) {
-      this.tabData.set(tab, this.getDefaults(tab));
-    }
-
-    return this.tabData.get(tab);
-  },
-
-  clear(tab) {
-    this.tabData.delete(tab);
-  },
-
-  handleEvent(event) {
-    if (event.type == "TabSelect") {
-      let tab = event.target;
-      this.emit("tab-select", tab);
-      this.emit("location-change", tab);
-    }
-  },
-
-  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 tab = gBrowser.getTabForBrowser(browser);
-      this.emit("location-change", tab, true);
-    }
-    this.lastLocation.set(browser, browser.currentURI);
-  },
-
-  shutdown() {
-    AllWindowEvents.removeListener("progress", this);
-    AllWindowEvents.removeListener("TabSelect", this);
-  },
-};
-
-// Manages tab mappings and permissions for a specific extension.
-function ExtensionTabManager(extension) {
-  this.extension = extension;
-
-  // A mapping of tab objects to the inner window ID the extension currently has
-  // the active tab permission for. The active permission for a given tab is
-  // valid only for the inner window that was active when the permission was
-  // granted. If the tab navigates, the inner window ID changes, and the
-  // permission automatically becomes stale.
-  //
-  // WeakMap[tab => inner-window-id<int>]
-  this.hasTabPermissionFor = new WeakMap();
-}
-
-ExtensionTabManager.prototype = {
-  addActiveTabPermission(tab = TabManager.activeTab) {
-    if (this.extension.hasPermission("activeTab")) {
-      // Note that, unlike Chrome, we don't currently clear this permission with
-      // the tab navigates. If the inner window is revived from BFCache before
-      // we've granted this permission to a new inner window, the extension
-      // maintains its permissions for it.
-      this.hasTabPermissionFor.set(tab, tab.linkedBrowser.innerWindowID);
-    }
-  },
-
-  revokeActiveTabPermission(tab = TabManager.activeTab) {
-    this.hasTabPermissionFor.delete(tab);
-  },
-
-  // Returns true if the extension has the "activeTab" permission for this tab.
-  // This is somewhat more permissive than the generic "tabs" permission, as
-  // checked by |hasTabPermission|, in that it also allows programmatic script
-  // injection without an explicit host permission.
-  hasActiveTabPermission(tab) {
-    // This check is redundant with addTabPermission, but cheap.
-    if (this.extension.hasPermission("activeTab")) {
-      return (this.hasTabPermissionFor.has(tab) &&
-              this.hasTabPermissionFor.get(tab) === tab.linkedBrowser.innerWindowID);
-    }
-    return false;
-  },
-
-  hasTabPermission(tab) {
-    return this.extension.hasPermission("tabs") || this.hasActiveTabPermission(tab);
-  },
-
-  convert(tab) {
-    let window = tab.ownerGlobal;
-    let browser = tab.linkedBrowser;
-
-    let mutedInfo = {muted: tab.muted};
-    if (tab.muteReason === null) {
-      mutedInfo.reason = "user";
-    } else if (tab.muteReason) {
-      mutedInfo.reason = "extension";
-      mutedInfo.extensionId = tab.muteReason;
-    }
-
-    let result = {
-      id: TabManager.getId(tab),
-      index: tab._tPos,
-      windowId: WindowManager.getId(window),
-      selected: tab.selected,
-      highlighted: tab.selected,
-      active: tab.selected,
-      pinned: tab.pinned,
-      status: TabManager.getStatus(tab),
-      incognito: WindowManager.isBrowserPrivate(browser),
-      width: browser.frameLoader.lazyWidth || browser.clientWidth,
-      height: browser.frameLoader.lazyHeight || browser.clientHeight,
-      audible: tab.soundPlaying,
-      mutedInfo,
-    };
-    if (this.extension.hasPermission("cookies")) {
-      result.cookieStoreId = getCookieStoreIdForTab(result, tab);
-    }
-
-    if (this.hasTabPermission(tab)) {
-      result.url = browser.currentURI.spec;
-      let title = browser.contentTitle || tab.label;
-      if (title) {
-        result.title = title;
-      }
-      let icon = window.gBrowser.getIcon(tab);
-      if (icon) {
-        result.favIconUrl = icon;
-      }
-    }
-
-    return result;
-  },
-
-  // Converts tabs returned from SessionStore.getClosedTabData and
-  // SessionStore.getClosedWindowData into API tab objects
-  convertFromSessionStoreClosedData(tab, window) {
-    let result = {
-      sessionId: String(tab.closedId),
-      index: tab.pos ? tab.pos : 0,
-      windowId: WindowManager.getId(window),
-      selected: false,
-      highlighted: false,
-      active: false,
-      pinned: false,
-      incognito: Boolean(tab.state && tab.state.isPrivate),
-    };
-
-    if (this.hasTabPermission(tab)) {
-      let entries = tab.state ? tab.state.entries : tab.entries;
-      result.url = entries[0].url;
-      result.title = entries[0].title;
-      if (tab.image) {
-        result.favIconUrl = tab.image;
-      }
-    }
-
-    return result;
-  },
-
-  getTabs(window) {
-    return Array.from(window.gBrowser.tabs)
-                .filter(tab => !tab.closing)
-                .map(tab => this.convert(tab));
-  },
-};
-
-function getBrowserInfo(browser) {
-  if (!browser.ownerGlobal.gBrowser) {
-    // 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;
-
-    if (!browser) {
-      return {};
-    }
-  }
-
-  let result = {};
-
-  let window = browser.ownerGlobal;
-  if (window.gBrowser) {
-    let tab = window.gBrowser.getTabForBrowser(browser);
-    if (tab) {
-      result.tabId = TabManager.getId(tab);
-    }
-
-    result.windowId = WindowManager.getId(window);
-  }
-
-  return result;
-}
-global.getBrowserInfo = getBrowserInfo;
-
-// Sends the tab and windowId upon request. This is primarily used to support
-// the synchronous `browser.extension.getViews` API.
-let onGetTabAndWindowId = {
-  receiveMessage({name, target, sync}) {
-    let result = getBrowserInfo(target);
-
-    if (result.tabId) {
-      if (sync) {
-        return result;
-      }
-      target.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", result);
-    }
-  },
-};
-/* eslint-disable mozilla/balanced-listeners */
-Services.mm.addMessageListener("Extension:GetTabAndWindowId", onGetTabAndWindowId);
-/* eslint-enable mozilla/balanced-listeners */
-
-
-// Manages global mappings between XUL tabs and extension tab IDs.
-global.TabManager = {
-  _tabs: new WeakMap(),
-  _nextId: 1,
-  _initialized: false,
-
-  // We begin listening for TabOpen and TabClose events once we've started
-  // assigning IDs to tabs, so that we can remap the IDs of tabs which are moved
-  // between windows.
-  initListener() {
-    if (this._initialized) {
-      return;
-    }
-
-    AllWindowEvents.addListener("TabOpen", this);
-    AllWindowEvents.addListener("TabClose", this);
-    WindowListManager.addOpenListener(this.handleWindowOpen.bind(this));
-
-    this._initialized = true;
-  },
-
-  handleEvent(event) {
-    if (event.type == "TabOpen") {
-      let {adoptedTab} = event.detail;
-      if (adoptedTab) {
-        // This tab is being created to adopt a tab from a different window.
-        // Copy the ID from the old tab to the new.
-        let tab = event.target;
-        this._tabs.set(tab, this.getId(adoptedTab));
-
-        tab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
-          windowId: WindowManager.getId(tab.ownerGlobal),
-        });
-      }
-    } else if (event.type == "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._tabs.set(adoptedBy, this.getId(event.target));
-
-        adoptedBy.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
-          windowId: WindowManager.getId(adoptedBy),
-        });
-      }
-    }
-  },
-
-  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.
-      let adoptedTab = window.arguments[0];
-
-      this._tabs.set(window.gBrowser.tabs[0], this.getId(adoptedTab));
-    }
-  },
-
-  getId(tab) {
-    if (this._tabs.has(tab)) {
-      return this._tabs.get(tab);
-    }
-    this.initListener();
-
-    let id = this._nextId++;
-    this._tabs.set(tab, id);
-    return id;
-  },
-
-  getBrowserId(browser) {
-    let gBrowser = browser.ownerGlobal.gBrowser;
-    // Some non-browser windows have gBrowser but not
-    // getTabForBrowser!
-    if (gBrowser && gBrowser.getTabForBrowser) {
-      let tab = gBrowser.getTabForBrowser(browser);
-      if (tab) {
-        return this.getId(tab);
-      }
-    }
-    return -1;
-  },
-
-  /**
-   * 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 {ExtensionContext} context
-   *        The context of the caller.
-   *        This value may be omitted if `default_` is not `undefined`.
-   * @param {*} default_
-   *        The value to return if no tab exists with the given ID.
-   * @returns {Element<tab>}
-   *        A XUL <tab> element.
-   */
-  getTab(tabId, context, default_ = undefined) {
-    // FIXME: Speed this up without leaking memory somehow.
-    for (let window of WindowListManager.browserWindows()) {
-      if (!window.gBrowser) {
-        continue;
-      }
-      for (let tab of window.gBrowser.tabs) {
-        if (this.getId(tab) == tabId) {
-          return tab;
-        }
-      }
-    }
-    if (default_ !== undefined) {
-      return default_;
-    }
-    throw new context.cloneScope.Error(`Invalid tab ID: ${tabId}`);
-  },
-
-  get activeTab() {
-    let window = WindowManager.topWindow;
-    if (window && window.gBrowser) {
-      return window.gBrowser.selectedTab;
-    }
-    return null;
-  },
-
-  getStatus(tab) {
-    return tab.getAttribute("busy") == "true" ? "loading" : "complete";
-  },
-
-  convert(extension, tab) {
-    return TabManager.for(extension).convert(tab);
-  },
-};
-
-// WeakMap[Extension -> ExtensionTabManager]
-let tabManagers = new WeakMap();
-
-// Returns the extension-specific tab manager for the given extension, or
-// creates one if it doesn't already exist.
-TabManager.for = function(extension) {
-  if (!tabManagers.has(extension)) {
-    tabManagers.set(extension, new ExtensionTabManager(extension));
-  }
-  return tabManagers.get(extension);
-};
-
-/* eslint-disable mozilla/balanced-listeners */
-extensions.on("shutdown", (type, extension) => {
-  tabManagers.delete(extension);
-});
-/* eslint-enable mozilla/balanced-listeners */
-
-function memoize(fn) {
-  let weakMap = new DefaultWeakMap(fn);
-  return weakMap.get.bind(weakMap);
-}
-
-// Manages mapping between XUL windows and extension window IDs.
-global.WindowManager = {
-  // Note: These must match the values in windows.json.
-  WINDOW_ID_NONE: -1,
-  WINDOW_ID_CURRENT: -2,
-
-  get topWindow() {
-    return Services.wm.getMostRecentWindow("navigator:browser");
-  },
-
-  windowType(window) {
-    // TODO: Make this work.
-
-    let {chromeFlags} = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIDocShell)
-                              .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIXULWindow);
-
-    if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
-      return "popup";
-    }
-
-    return "normal";
-  },
-
-  updateGeometry(window, options) {
-    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);
-    }
-  },
-
-  isBrowserPrivate: memoize(browser => {
-    return PrivateBrowsingUtils.isBrowserPrivate(browser);
-  }),
-
-  getId: memoize(window => {
-    if (window instanceof Ci.nsIInterfaceRequestor) {
-      return window.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
-    }
-    return null;
-  }),
-
-  getWindow(id, context) {
-    if (id == this.WINDOW_ID_CURRENT) {
-      return currentWindow(context);
-    }
-
-    for (let window of WindowListManager.browserWindows(true)) {
-      if (this.getId(window) == id) {
-        return window;
-      }
-    }
-    return null;
-  },
-
-  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;
-  },
-
-  setState(window, state) {
-    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}`);
-    }
-  },
-
-  convert(extension, window, getInfo) {
-    let xulWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                          .getInterface(Ci.nsIDocShell)
-                          .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
-                          .getInterface(Ci.nsIXULWindow);
-
-    let result = {
-      id: this.getId(window),
-      focused: window.document.hasFocus(),
-      top: window.screenY,
-      left: window.screenX,
-      width: window.outerWidth,
-      height: window.outerHeight,
-      incognito: PrivateBrowsingUtils.isWindowPrivate(window),
-      type: this.windowType(window),
-      state: this.getState(window),
-      alwaysOnTop: xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ,
-    };
-
-    if (getInfo && getInfo.populate) {
-      result.tabs = TabManager.for(extension).getTabs(window);
-    }
-
-    return result;
-  },
-
-  // Converts windows returned from SessionStore.getClosedWindowData
-  // into API window objects
-  convertFromSessionStoreClosedData(window, extension) {
-    let result = {
-      sessionId: String(window.closedId),
-      focused: false,
-      incognito: false,
-      type: "normal", // this is always "normal" for a closed window
-      state: this.getState(window),
-      alwaysOnTop: false,
-    };
-
-    if (window.tabs.length) {
-      result.tabs = [];
-      window.tabs.forEach((tab, index) => {
-        result.tabs.push(TabManager.for(extension).convertFromSessionStoreClosedData(tab, window, index));
-      });
-    }
-
-    return result;
-  },
-};
-
-// Manages listeners for window opening and closing. A window is
-// considered open when the "load" event fires on it. A window is
-// closed when a "domwindowclosed" notification fires for it.
-global.WindowListManager = {
-  _openListeners: new Set(),
-  _closeListeners: new Set(),
-
-  // Returns an iterator for all browser windows. Unless |includeIncomplete| is
-  // true, only fully-loaded windows are returned.
-  * browserWindows(includeIncomplete = false) {
-    // The window type parameter is only available once the window's document
-    // element has been created. This means that, when looking for incomplete
-    // browser windows, we need to ignore the type entirely for windows which
-    // haven't finished loading, since we would otherwise skip browser windows
-    // in their early loading stages.
-    // This is particularly important given that the "domwindowcreated" event
-    // fires for browser windows when they're in that in-between state, and just
-    // before we register our own "domwindowcreated" listener.
-
-    let e = Services.wm.getEnumerator("");
-    while (e.hasMoreElements()) {
-      let window = e.getNext();
-
-      let ok = includeIncomplete;
-      if (window.document.readyState == "complete") {
-        ok = window.document.documentElement.getAttribute("windowtype") == "navigator:browser";
-      }
-
-      if (ok) {
-        yield window;
-      }
-    }
-  },
-
-  addOpenListener(listener) {
-    if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
-      Services.ww.registerNotification(this);
-    }
-    this._openListeners.add(listener);
-
-    for (let window of this.browserWindows(true)) {
-      if (window.document.readyState != "complete") {
-        window.addEventListener("load", this);
-      }
-    }
-  },
-
-  removeOpenListener(listener) {
-    this._openListeners.delete(listener);
-    if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
-      Services.ww.unregisterNotification(this);
-    }
-  },
-
-  addCloseListener(listener) {
-    if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
-      Services.ww.registerNotification(this);
-    }
-    this._closeListeners.add(listener);
-  },
-
-  removeCloseListener(listener) {
-    this._closeListeners.delete(listener);
-    if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
-      Services.ww.unregisterNotification(this);
-    }
-  },
-
-  handleEvent(event) {
-    event.currentTarget.removeEventListener(event.type, this);
-    let window = event.target.defaultView;
-    if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
-      return;
-    }
-
-    for (let listener of this._openListeners) {
-      listener(window);
-    }
-  },
-
-  observe(window, topic, data) {
-    if (topic == "domwindowclosed") {
-      if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
-        return;
-      }
-
-      window.removeEventListener("load", this);
-      for (let listener of this._closeListeners) {
-        listener(window);
-      }
-    } else {
-      window.addEventListener("load", this);
-    }
-  },
-};
-
-// Provides a facility to listen for DOM events across all XUL windows.
-global.AllWindowEvents = {
-  _listeners: new Map(),
-
-  // If |type| is a normal event type, invoke |listener| each time
-  // that event fires in any open window. If |type| is "progress", add
-  // a web progress listener that covers all open windows.
-  addListener(type, listener) {
-    if (type == "domwindowopened") {
-      return WindowListManager.addOpenListener(listener);
-    } else if (type == "domwindowclosed") {
-      return WindowListManager.addCloseListener(listener);
-    }
-
-    if (this._listeners.size == 0) {
-      WindowListManager.addOpenListener(this.openListener);
-    }
-
-    if (!this._listeners.has(type)) {
-      this._listeners.set(type, new Set());
-    }
-    let list = this._listeners.get(type);
-    list.add(listener);
-
-    // Register listener on all existing windows.
-    for (let window of WindowListManager.browserWindows()) {
-      this.addWindowListener(window, type, listener);
-    }
-  },
-
-  removeListener(eventType, listener) {
-    if (eventType == "domwindowopened") {
-      return WindowListManager.removeOpenListener(listener);
-    } else if (eventType == "domwindowclosed") {
-      return WindowListManager.removeCloseListener(listener);
-    }
-
-    let listeners = this._listeners.get(eventType);
-    listeners.delete(listener);
-    if (listeners.size == 0) {
-      this._listeners.delete(eventType);
-      if (this._listeners.size == 0) {
-        WindowListManager.removeOpenListener(this.openListener);
-      }
-    }
-
-    // Unregister listener from all existing windows.
-    let useCapture = eventType === "focus" || eventType === "blur";
-    for (let window of WindowListManager.browserWindows()) {
-      if (eventType == "progress") {
-        window.gBrowser.removeTabsProgressListener(listener);
-      } else {
-        window.removeEventListener(eventType, listener, useCapture);
-      }
-    }
-  },
-
-  /* eslint-disable mozilla/balanced-listeners */
-  addWindowListener(window, eventType, listener) {
-    let useCapture = eventType === "focus" || eventType === "blur";
-
-    if (eventType == "progress") {
-      window.gBrowser.addTabsProgressListener(listener);
-    } else {
-      window.addEventListener(eventType, listener, useCapture);
-    }
-  },
-  /* eslint-enable mozilla/balanced-listeners */
-
-  // Runs whenever the "load" event fires for a new window.
-  openListener(window) {
-    for (let [eventType, listeners] of AllWindowEvents._listeners) {
-      for (let listener of listeners) {
-        this.addWindowListener(window, eventType, listener);
-      }
-    }
-  },
-};
-
-AllWindowEvents.openListener = AllWindowEvents.openListener.bind(AllWindowEvents);
-
-// Subclass of SingletonEventManager where we just need to call
-// add/removeEventListener on each XUL window.
-global.WindowEventManager = class extends SingletonEventManager {
-  constructor(context, name, event, listener) {
-    super(context, name, fire => {
-      let listener2 = (...args) => listener(fire, ...args);
-      AllWindowEvents.addListener(event, listener2);
-      return () => {
-        AllWindowEvents.removeListener(event, listener2);
-      };
-    });
-  }
-};
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -3,28 +3,30 @@
 "use strict";
 
 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, "ViewPopup",
+                                  "resource:///modules/ExtensionPopups.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
                                    "@mozilla.org/inspector/dom-utils;1",
                                    "inIDOMUtils");
 
 Cu.import("resource://devtools/shared/event-emitter.js");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {
+  IconDetails,
   SingletonEventManager,
-  IconDetails,
 } = ExtensionUtils;
 
 const POPUP_PRELOAD_TIMEOUT_MS = 200;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 function isAncestorOrSelf(target, node) {
   for (; node; node = node.parentNode) {
@@ -46,17 +48,17 @@ function BrowserAction(options, extensio
   let widgetId = makeWidgetId(extension.id);
   this.id = `${widgetId}-browser-action`;
   this.viewId = `PanelUI-webext-${widgetId}-browser-action-view`;
   this.widget = null;
 
   this.pendingPopup = null;
   this.pendingPopupTimeout = null;
 
-  this.tabManager = TabManager.for(extension);
+  this.tabManager = extension.tabManager;
 
   this.defaults = {
     enabled: true,
     title: options.default_title || extension.name,
     badgeText: "",
     badgeBackgroundColor: null,
     icon: IconDetails.normalize({path: options.default_icon}, extension),
     popup: options.default_popup || "",
@@ -381,17 +383,17 @@ BrowserAction.prototype = {
   // title, badge, etc. If it only changes a parameter for a single
   // tab, |tab| will be that tab. Otherwise it will be null.
   updateOnChange(tab) {
     if (tab) {
       if (tab.selected) {
         this.updateWindow(tab.ownerGlobal);
       }
     } else {
-      for (let window of WindowListManager.browserWindows()) {
+      for (let window of windowTracker.browserWindows()) {
         this.updateWindow(window);
       }
     }
   },
 
   // tab is allowed to be null.
   // prop should be one of "icon", "title", "badgeText", "popup", or "badgeBackgroundColor".
   setProperty(tab, prop, value) {
@@ -439,107 +441,116 @@ extensions.on("shutdown", (type, extensi
     browserActionMap.get(extension).shutdown();
     browserActionMap.delete(extension);
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 extensions.registerSchemaAPI("browserAction", "addon_parent", context => {
   let {extension} = context;
+
+  let {tabManager} = extension;
+
+  function getTab(tabId) {
+    if (tabId !== null) {
+      return tabTracker.getTab(tabId);
+    }
+    return null;
+  }
+
   return {
     browserAction: {
       onClicked: new SingletonEventManager(context, "browserAction.onClicked", fire => {
         let listener = () => {
-          let tab = TabManager.activeTab;
-          fire.async(TabManager.convert(extension, tab));
+          fire.async(tabManager.convert(tabTracker.activeTab));
         };
         BrowserAction.for(extension).on("click", listener);
         return () => {
           BrowserAction.for(extension).off("click", listener);
         };
       }).api(),
 
       enable: function(tabId) {
-        let tab = tabId !== null ? TabManager.getTab(tabId, context) : null;
+        let tab = getTab(tabId);
         BrowserAction.for(extension).setProperty(tab, "enabled", true);
       },
 
       disable: function(tabId) {
-        let tab = tabId !== null ? TabManager.getTab(tabId, context) : null;
+        let tab = getTab(tabId);
         BrowserAction.for(extension).setProperty(tab, "enabled", false);
       },
 
       setTitle: function(details) {
-        let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+        let tab = getTab(details.tabId);
 
         let title = details.title;
         // Clear the tab-specific title when given a null string.
         if (tab && title == "") {
           title = null;
         }
         BrowserAction.for(extension).setProperty(tab, "title", title);
       },
 
       getTitle: function(details) {
-        let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+        let tab = getTab(details.tabId);
 
         let title = BrowserAction.for(extension).getProperty(tab, "title");
         return Promise.resolve(title);
       },
 
       setIcon: function(details) {
-        let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+        let tab = getTab(details.tabId);
 
         let icon = IconDetails.normalize(details, extension, context);
         BrowserAction.for(extension).setProperty(tab, "icon", icon);
       },
 
       setBadgeText: function(details) {
-        let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+        let tab = getTab(details.tabId);
 
         BrowserAction.for(extension).setProperty(tab, "badgeText", details.text);
       },
 
       getBadgeText: function(details) {
-        let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+        let tab = getTab(details.tabId);
 
         let text = BrowserAction.for(extension).getProperty(tab, "badgeText");
         return Promise.resolve(text);
       },
 
       setPopup: function(details) {
-        let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+        let tab = getTab(details.tabId);
 
         // Note: Chrome resolves arguments to setIcon relative to the calling
         // context, but resolves arguments to setPopup relative to the extension
         // root.
         // For internal consistency, we currently resolve both relative to the
         // calling context.
         let url = details.popup && context.uri.resolve(details.popup);
         BrowserAction.for(extension).setProperty(tab, "popup", url);
       },
 
       getPopup: function(details) {
-        let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+        let tab = getTab(details.tabId);
 
         let popup = BrowserAction.for(extension).getProperty(tab, "popup");
         return Promise.resolve(popup);
       },
 
       setBadgeBackgroundColor: function(details) {
-        let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+        let tab = getTab(details.tabId);
         let color = details.color;
         if (!Array.isArray(color)) {
           let col = DOMUtils.colorToRGBA(color);
           color = col && [col.r, col.g, col.b, Math.round(col.a * 255)];
         }
         BrowserAction.for(extension).setProperty(tab, "badgeBackgroundColor", color);
       },
 
       getBadgeBackgroundColor: function(details, callback) {
-        let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+        let tab = getTab(details.tabId);
 
         let color = BrowserAction.for(extension).getProperty(tab, "badgeBackgroundColor");
         return Promise.resolve(color || [0xd9, 0, 0, 255]);
       },
     },
   };
 });
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -31,41 +31,41 @@ function CommandList(manifest, extension
 }
 
 CommandList.prototype = {
   /**
    * Registers the commands to all open windows and to any which
    * are later created.
    */
   register() {
-    for (let window of WindowListManager.browserWindows()) {
+    for (let window of windowTracker.browserWindows()) {
       this.registerKeysToDocument(window);
     }
 
     this.windowOpenListener = (window) => {
       if (!this.keysetsMap.has(window)) {
         this.registerKeysToDocument(window);
       }
     };
 
-    WindowListManager.addOpenListener(this.windowOpenListener);
+    windowTracker.addOpenListener(this.windowOpenListener);
   },
 
   /**
    * Unregisters the commands from all open windows and stops commands
    * from being registered to windows which are later created.
    */
   unregister() {
-    for (let window of WindowListManager.browserWindows()) {
+    for (let window of windowTracker.browserWindows()) {
       if (this.keysetsMap.has(window)) {
         this.keysetsMap.get(window).remove();
       }
     }
 
-    WindowListManager.removeOpenListener(this.windowOpenListener);
+    windowTracker.removeOpenListener(this.windowOpenListener);
   },
 
   /**
    * Creates a Map from commands for each command in the manifest.commands object.
    *
    * @param {Object} manifest The manifest JSON object.
    * @returns {Map<string, object>}
    */
@@ -127,18 +127,18 @@ CommandList.prototype = {
     keyElement.addEventListener("command", (event) => {
       if (name == "_execute_page_action") {
         let win = event.target.ownerGlobal;
         pageActionFor(this.extension).triggerAction(win);
       } else if (name == "_execute_browser_action") {
         let win = event.target.ownerGlobal;
         browserActionFor(this.extension).triggerAction(win);
       } else {
-        TabManager.for(this.extension)
-                  .addActiveTabPermission(TabManager.activeTab);
+        this.extension.tabManager
+            .addActiveTabPermission();
         this.emit("command", name);
       }
     });
     /* eslint-enable mozilla/balanced-listeners */
 
     return keyElement;
   },
 
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -85,17 +85,17 @@ var gMenuBuilder = {
       this.itemsToCleanUp.add(rootElement);
     }
   },
 
   // Builds a context menu for browserAction and pageAction buttons.
   buildActionContextMenu(contextData) {
     const {menu} = contextData;
 
-    contextData.tab = TabManager.activeTab;
+    contextData.tab = tabTracker.activeTab;
     contextData.pageUrl = contextData.tab.linkedBrowser.currentURI.spec;
 
     const root = gRootItems.get(contextData.extension);
     const children = this.buildChildren(root, contextData);
     const visible = children.slice(0, ACTION_MENU_TOP_LEVEL_LIMIT);
 
     if (visible.length) {
       this.xulMenu = menu;
@@ -323,17 +323,17 @@ function getContexts(contextData) {
 
   return contexts;
 }
 
 function MenuItem(extension, createProperties, isRoot = false) {
   this.extension = extension;
   this.children = [];
   this.parent = null;
-  this.tabManager = TabManager.for(extension);
+  this.tabManager = extension.tabManager;
 
   this.setDefaults();
   this.setProps(createProperties);
 
   if (!this.hasOwnProperty("_id")) {
     this.id = gNextMenuItemID++;
   }
   // If the item is not the root and has no parent
@@ -541,46 +541,46 @@ MenuItem.prototype = {
   },
 };
 
 // While any extensions are active, this Tracker registers to observe/listen
 // for contex-menu events from both content and chrome.
 const contextMenuTracker = {
   register() {
     Services.obs.addObserver(this, "on-build-contextmenu", false);
-    for (const window of WindowListManager.browserWindows()) {
+    for (const window of windowTracker.browserWindows()) {
       this.onWindowOpen(window);
     }
-    WindowListManager.addOpenListener(this.onWindowOpen);
+    windowTracker.addOpenListener(this.onWindowOpen);
   },
 
   unregister() {
     Services.obs.removeObserver(this, "on-build-contextmenu");
-    for (const window of WindowListManager.browserWindows()) {
+    for (const window of windowTracker.browserWindows()) {
       const menu = window.document.getElementById("tabContextMenu");
       menu.removeEventListener("popupshowing", this);
     }
-    WindowListManager.removeOpenListener(this.onWindowOpen);
+    windowTracker.removeOpenListener(this.onWindowOpen);
   },
 
   observe(subject, topic, data) {
     subject = subject.wrappedJSObject;
     gMenuBuilder.build(subject);
   },
 
   onWindowOpen(window) {
     const menu = window.document.getElementById("tabContextMenu");
     menu.addEventListener("popupshowing", contextMenuTracker);
   },
 
   handleEvent(event) {
     const menu = event.target;
     if (menu.id === "tabContextMenu") {
       const trigger = menu.triggerNode;
-      const tab = trigger.localName === "tab" ? trigger : TabManager.activeTab;
+      const tab = trigger.localName === "tab" ? trigger : tabTracker.activeTab;
       const pageUrl = tab.linkedBrowser.currentURI.spec;
       gMenuBuilder.build({menu, tab, pageUrl, onTab: true});
     }
   },
 };
 
 var gExtensionCount = 0;
 /* eslint-disable mozilla/balanced-listeners */
--- a/browser/components/extensions/ext-desktop-runtime.js
+++ b/browser/components/extensions/ext-desktop-runtime.js
@@ -1,20 +1,20 @@
 "use strict";
 
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("uninstall", (msg, extension) => {
   if (extension.uninstallURL) {
-    let browser = WindowManager.topWindow.gBrowser;
+    let browser = windowTracker.topWindow.gBrowser;
     browser.addTab(extension.uninstallURL, {relatedToCurrent: true});
   }
 });
 
 global.openOptionsPage = (extension) => {
-  let window = WindowManager.topWindow;
+  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);
     return Promise.resolve();
   }
--- a/browser/components/extensions/ext-devtools.js
+++ b/browser/components/extensions/ext-devtools.js
@@ -76,17 +76,17 @@ global.getTargetTabIdForToolbox = (toolb
 
   if (!target.isLocalTab) {
     throw new Error("Unexpected target type: only local tabs are currently supported.");
   }
 
   let parentWindow = target.tab.linkedBrowser.ownerGlobal;
   let tab = parentWindow.gBrowser.getTabForBrowser(target.tab.linkedBrowser);
 
-  return TabManager.getId(tab);
+  return tabTracker.getId(tab);
 };
 
 /**
  * The DevToolsPage represents the "devtools_page" related to a particular
  * Toolbox and WebExtension.
  *
  * The devtools_page contexts are invisible WebExtensions contexts, similar to the
  * background page, associated to a single developer toolbox (e.g. If an add-on
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -1,29 +1,32 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+XPCOMUtils.defineLazyModuleGetter(this, "PanelPopup",
+                                  "resource:///modules/ExtensionPopups.jsm");
+
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   SingletonEventManager,
   IconDetails,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> PageAction]
 var pageActionMap = new WeakMap();
 
 // Handles URL bar icons, including the |page_action| manifest entry
 // and associated API.
 function PageAction(options, extension) {
   this.extension = extension;
   this.id = makeWidgetId(extension.id) + "-page-action";
 
-  this.tabManager = TabManager.for(extension);
+  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 || "",
   };
 
@@ -206,17 +209,17 @@ PageAction.prototype = {
       this.tabContext.clear(tab);
     }
     this.updateButton(tab.ownerGlobal);
   },
 
   shutdown() {
     this.tabContext.shutdown();
 
-    for (let window of WindowListManager.browserWindows()) {
+    for (let window of windowTracker.browserWindows()) {
       if (this.buttons.has(window)) {
         this.buttons.get(window).remove();
         window.removeEventListener("popupshowing", this);
       }
     }
   },
 };
 
@@ -237,74 +240,77 @@ extensions.on("shutdown", (type, extensi
 PageAction.for = extension => {
   return pageActionMap.get(extension);
 };
 
 global.pageActionFor = PageAction.for;
 
 extensions.registerSchemaAPI("pageAction", "addon_parent", context => {
   let {extension} = context;
+
+  const {tabManager} = extension;
+
   return {
     pageAction: {
       onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => {
         let listener = (evt, tab) => {
-          fire.async(TabManager.convert(extension, tab));
+          fire.async(tabManager.convert(tab));
         };
         let pageAction = PageAction.for(extension);
 
         pageAction.on("click", listener);
         return () => {
           pageAction.off("click", listener);
         };
       }).api(),
 
       show(tabId) {
-        let tab = TabManager.getTab(tabId, context);
+        let tab = tabTracker.getTab(tabId);
         PageAction.for(extension).setProperty(tab, "show", true);
       },
 
       hide(tabId) {
-        let tab = TabManager.getTab(tabId, context);
+        let tab = tabTracker.getTab(tabId);
         PageAction.for(extension).setProperty(tab, "show", false);
       },
 
       setTitle(details) {
-        let tab = TabManager.getTab(details.tabId, context);
+        let tab = tabTracker.getTab(details.tabId);
 
         // Clear the tab-specific title when given a null string.
         PageAction.for(extension).setProperty(tab, "title", details.title || null);
       },
 
       getTitle(details) {
-        let tab = TabManager.getTab(details.tabId, context);
+        let tab = tabTracker.getTab(details.tabId);
 
         let title = PageAction.for(extension).getProperty(tab, "title");
         return Promise.resolve(title);
       },
 
       setIcon(details) {
-        let tab = TabManager.getTab(details.tabId, context);
+        let tab = tabTracker.getTab(details.tabId);
 
         let icon = IconDetails.normalize(details, extension, context);
         PageAction.for(extension).setProperty(tab, "icon", icon);
       },
 
       setPopup(details) {
-        let tab = TabManager.getTab(details.tabId, context);
+        let tab = tabTracker.getTab(details.tabId);
 
         // Note: Chrome resolves arguments to setIcon relative to the calling
         // context, but resolves arguments to setPopup relative to the extension
         // root.
         // For internal consistency, we currently resolve both relative to the
         // calling context.
         let url = details.popup && context.uri.resolve(details.popup);
         PageAction.for(extension).setProperty(tab, "popup", url);
       },
 
       getPopup(details) {
-        let tab = TabManager.getTab(details.tabId, context);
+        let tab = tabTracker.getTab(details.tabId);
 
         let popup = PageAction.for(extension).getProperty(tab, "popup");
         return Promise.resolve(popup);
       },
     },
   };
 });
--- a/browser/components/extensions/ext-sessions.js
+++ b/browser/components/extensions/ext-sessions.js
@@ -16,46 +16,46 @@ const SS_ON_CLOSED_OBJECTS_CHANGED = "se
 function getRecentlyClosed(maxResults, extension) {
   let recentlyClosed = [];
 
   // Get closed windows
   let closedWindowData = SessionStore.getClosedWindowData(false);
   for (let window of closedWindowData) {
     recentlyClosed.push({
       lastModified: window.closedAt,
-      window: WindowManager.convertFromSessionStoreClosedData(window, extension)});
+      window: Window.convertFromSessionStoreClosedData(extension, window)});
   }
 
   // Get closed tabs
-  for (let window of WindowListManager.browserWindows()) {
+  for (let window of windowTracker.browserWindows()) {
     let closedTabData = SessionStore.getClosedTabData(window, false);
     for (let tab of closedTabData) {
       recentlyClosed.push({
         lastModified: tab.closedAt,
-        tab: TabManager.for(extension).convertFromSessionStoreClosedData(tab, window)});
+        tab: Tab.convertFromSessionStoreClosedData(extension, tab, window)});
     }
   }
 
   // Sort windows and tabs
   recentlyClosed.sort((a, b) => b.lastModified - a.lastModified);
   return recentlyClosed.slice(0, maxResults);
 }
 
 function createSession(restored, extension, sessionId) {
   if (!restored) {
     return Promise.reject({message: `Could not restore object using sessionId ${sessionId}.`});
   }
   let sessionObj = {lastModified: Date.now()};
   if (restored instanceof Ci.nsIDOMChromeWindow) {
     return promiseObserved("sessionstore-single-window-restored", subject => subject == restored).then(() => {
-      sessionObj.window = WindowManager.convert(extension, restored, {populate: true});
+      sessionObj.window = extension.windowManager.convert(restored, {populate: true});
       return Promise.resolve([sessionObj]);
     });
   }
-  sessionObj.tab = TabManager.for(extension).convert(restored);
+  sessionObj.tab = extension.tabManager.convert(restored);
   return Promise.resolve([sessionObj]);
 }
 
 extensions.registerSchemaAPI("sessions", "addon_parent", context => {
   let {extension} = context;
   return {
     sessions: {
       getRecentlyClosed: function(filter) {
@@ -70,17 +70,17 @@ extensions.registerSchemaAPI("sessions",
           session = SessionStore.undoCloseById(closedId);
         } else if (SessionStore.lastClosedObjectType == "window") {
           // If the most recently closed object is a window, just undo closing the most recent window.
           session = SessionStore.undoCloseWindow(0);
         } else {
           // It is a tab, and we cannot call SessionStore.undoCloseTab without a window,
           // so we must find the tab in which case we can just use its closedId.
           let recentlyClosedTabs = [];
-          for (let window of WindowListManager.browserWindows()) {
+          for (let window of windowTracker.browserWindows()) {
             let closedTabData = SessionStore.getClosedTabData(window, false);
             for (let tab of closedTabData) {
               recentlyClosedTabs.push(tab);
             }
           }
 
           // Sort the tabs.
           recentlyClosedTabs.sort((a, b) => b.closedAt - a.closedAt);
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -28,32 +28,31 @@ function getSender(extension, target, se
   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 = getBrowserInfo(target).tabId;
+    tabId = tabTracker.getBrowserData(target).tabId;
   }
 
   if (tabId) {
-    let tab = TabManager.getTab(tabId, null, null);
+    let tab = extension.tabManager.get(tabId, null);
     if (tab) {
-      sender.tab = TabManager.convert(extension, tab);
+      sender.tab = tab.convert();
     }
   }
 }
 
 // Used by Extension.jsm
 global.tabGetSender = getSender;
 
 /* eslint-disable mozilla/balanced-listeners */
-
 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;
     }
@@ -61,181 +60,34 @@ extensions.on("page-shutdown", (type, co
     if (gBrowser) {
       let tab = gBrowser.getTabForBrowser(context.xulBrowser);
       if (tab) {
         gBrowser.removeTab(tab);
       }
     }
   }
 });
-
-extensions.on("fill-browser-data", (type, browser, data) => {
-  let tabId, windowId;
-  if (browser) {
-    ({tabId, windowId} = getBrowserInfo(browser));
-  }
-
-  data.tabId = tabId || -1;
-  data.windowId = windowId || -1;
-});
 /* eslint-enable mozilla/balanced-listeners */
 
-global.currentWindow = function(context) {
-  let {xulWindow} = context;
-  if (xulWindow && context.viewType != "background") {
-    return xulWindow;
-  }
-  return WindowManager.topWindow;
-};
-
 let tabListener = {
-  init() {
-    if (this.initialized) {
-      return;
-    }
-
-    this.adoptedTabs = new WeakMap();
-
-    this.handleWindowOpen = this.handleWindowOpen.bind(this);
-    this.handleWindowClose = this.handleWindowClose.bind(this);
-
-    AllWindowEvents.addListener("TabClose", this);
-    AllWindowEvents.addListener("TabOpen", this);
-    WindowListManager.addOpenListener(this.handleWindowOpen);
-    WindowListManager.addCloseListener(this.handleWindowClose);
-
-    EventEmitter.decorate(this);
-
-    this.initialized = true;
-  },
-
-  handleEvent(event) {
-    switch (event.type) {
-      case "TabOpen":
-        if (event.detail.adoptedTab) {
-          this.adoptedTabs.set(event.detail.adoptedTab, event.target);
-        }
-
-        // 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);
-          }
-        });
-        break;
-
-      case "TabClose":
-        let tab = event.originalTarget;
-
-        if (event.detail.adoptedBy) {
-          this.emitDetached(tab, event.detail.adoptedBy);
-        } else {
-          this.emitRemoved(tab, false);
-        }
-        break;
-    }
-  },
-
-  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 tab = window.arguments[0];
-      this.adoptedTabs.set(tab, window.gBrowser.tabs[0]);
-
-      // We need to be sure to fire this event after the onDetached event
-      // for the original tab.
-      let listener = (event, details) => {
-        if (details.tab == tab) {
-          this.off("tab-detached", listener);
-
-          Promise.resolve().then(() => {
-            this.emitAttached(details.adoptedBy);
-          });
-        }
-      };
-
-      this.on("tab-detached", listener);
-    } else {
-      for (let tab of window.gBrowser.tabs) {
-        this.emitCreated(tab);
-      }
-    }
-  },
-
-  handleWindowClose(window) {
-    for (let tab of window.gBrowser.tabs) {
-      if (this.adoptedTabs.has(tab)) {
-        this.emitDetached(tab, this.adoptedTabs.get(tab));
-      } else {
-        this.emitRemoved(tab, true);
-      }
-    }
-  },
-
-  emitAttached(tab) {
-    let newWindowId = WindowManager.getId(tab.ownerGlobal);
-    let tabId = TabManager.getId(tab);
-
-    this.emit("tab-attached", {tab, tabId, newWindowId, newPosition: tab._tPos});
-  },
-
-  emitDetached(tab, adoptedBy) {
-    let oldWindowId = WindowManager.getId(tab.ownerGlobal);
-    let tabId = TabManager.getId(tab);
-
-    this.emit("tab-detached", {tab, adoptedBy, tabId, oldWindowId, oldPosition: tab._tPos});
-  },
-
-  emitCreated(tab) {
-    this.emit("tab-created", {tab});
-  },
-
-  emitRemoved(tab, isWindowClosing) {
-    let windowId = WindowManager.getId(tab.ownerGlobal);
-    let tabId = TabManager.getId(tab);
-
-    // 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.mainThread.dispatch(() => {
-      this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
-    }, Ci.nsIThread.DISPATCH_NORMAL);
-  },
-
   tabReadyInitialized: false,
   tabReadyPromises: new WeakMap(),
   initializingTabs: new WeakSet(),
 
   initTabReady() {
     if (!this.tabReadyInitialized) {
-      AllWindowEvents.addListener("progress", this);
+      windowTracker.addListener("progress", this);
 
       this.tabReadyInitialized = true;
     }
   },
 
   onLocationChange(browser, webProgress, request, locationURI, flags) {
     if (webProgress.isTopLevel) {
-      let gBrowser = browser.ownerGlobal.gBrowser;
+      let {gBrowser} = browser.ownerGlobal;
       let tab = gBrowser.getTabForBrowser(browser);
 
       // Now we are certain that the first page in the tab was loaded.
       this.initializingTabs.delete(tab);
 
       // browser.innerWindowID is now set, resolve the promises if any.
       let deferred = this.tabReadyPromises.get(tab);
       if (deferred) {
@@ -253,98 +105,116 @@ let tabListener = {
    *
    * @param {XULElement} tab The <tab> element.
    * @returns {Promise} Resolves with the given tab once ready.
    */
   awaitTabReady(tab) {
     let deferred = this.tabReadyPromises.get(tab);
     if (!deferred) {
       deferred = PromiseUtils.defer();
-      if (!this.initializingTabs.has(tab) && tab.linkedBrowser.innerWindowID) {
+      if (!this.initializingTabs.has(tab) && (tab.linkedBrowser.innerWindowID ||
+                                              tab.linkedBrowser.currentURI.spec === "about:blank")) {
         deferred.resolve(tab);
       } else {
         this.initTabReady();
         this.tabReadyPromises.set(tab, deferred);
       }
     }
     return deferred.promise;
   },
 };
 
-/* eslint-disable mozilla/balanced-listeners */
-extensions.on("startup", () => {
-  tabListener.init();
-});
-/* eslint-enable mozilla/balanced-listeners */
-
 extensions.registerSchemaAPI("tabs", "addon_parent", context => {
   let {extension} = context;
+
+  let {tabManager} = extension;
+
+  function getTabOrActive(tabId) {
+    if (tabId !== null) {
+      return tabTracker.getTab(tabId);
+    }
+    return tabTracker.activeTab;
+  }
+
+  async function promiseTabWhenReady(tabId) {
+    let tab;
+    if (tabId !== null) {
+      tab = tabManager.get(tabId);
+    } else {
+      tab = tabManager.getWrapper(tabTracker.activeTab);
+    }
+
+    await tabListener.awaitTabReady(tab.tab);
+
+    return tab;
+  }
+
   let self = {
     tabs: {
       onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
-        let tabId = TabManager.getId(tab);
-        let windowId = WindowManager.getId(tab.ownerGlobal);
+        let tabId = tabTracker.getId(tab);
+        let windowId = windowTracker.getId(tab.ownerGlobal);
         fire.async({tabId, windowId});
       }).api(),
 
       onCreated: new SingletonEventManager(context, "tabs.onCreated", fire => {
         let listener = (eventName, event) => {
-          fire.async(TabManager.convert(extension, event.tab));
+          fire.async(tabManager.convert(event.tab));
         };
 
-        tabListener.on("tab-created", listener);
+        tabTracker.on("tab-created", listener);
         return () => {
-          tabListener.off("tab-created", listener);
+          tabTracker.off("tab-created", listener);
         };
       }).api(),
 
       /**
        * Since multiple tabs currently can't be highlighted, onHighlighted
        * essentially acts an alias for self.tabs.onActivated but returns
        * the tabId in an array to match the API.
        * @see  https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
       */
       onHighlighted: new WindowEventManager(context, "tabs.onHighlighted", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
-        let tabIds = [TabManager.getId(tab)];
-        let windowId = WindowManager.getId(tab.ownerGlobal);
+        let tabIds = [tabTracker.getId(tab)];
+        let windowId = windowTracker.getId(tab.ownerGlobal);
         fire.async({tabIds, windowId});
       }).api(),
 
       onAttached: new SingletonEventManager(context, "tabs.onAttached", fire => {
         let listener = (eventName, event) => {
           fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
         };
 
-        tabListener.on("tab-attached", listener);
+        tabTracker.on("tab-attached", listener);
         return () => {
-          tabListener.off("tab-attached", listener);
+          tabTracker.off("tab-attached", listener);
         };
       }).api(),
 
       onDetached: new SingletonEventManager(context, "tabs.onDetached", fire => {
         let listener = (eventName, event) => {
           fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
         };
 
-        tabListener.on("tab-detached", listener);
+        tabTracker.on("tab-detached", listener);
         return () => {
-          tabListener.off("tab-detached", listener);
+          tabTracker.off("tab-detached", listener);
         };
       }).api(),
 
       onRemoved: new SingletonEventManager(context, "tabs.onRemoved", fire => {
         let listener = (eventName, event) => {
           fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
         };
 
-        tabListener.on("tab-removed", listener);
+        tabTracker.on("tab-removed", listener);
         return () => {
-          tabListener.off("tab-removed", listener);
+          tabTracker.off("tab-removed", listener);
         };
       }).api(),
 
       onReplaced: ignoreEvent(context, "tabs.onReplaced"),
 
       onMoved: new SingletonEventManager(context, "tabs.onMoved", fire => {
         // There are certain circumstances where we need to ignore a move event.
         //
@@ -368,28 +238,28 @@ extensions.registerSchemaAPI("tabs", "ad
         let moveListener = event => {
           let tab = event.originalTarget;
 
           if (ignoreNextMove.has(tab)) {
             ignoreNextMove.delete(tab);
             return;
           }
 
-          fire.async(TabManager.getId(tab), {
-            windowId: WindowManager.getId(tab.ownerGlobal),
+          fire.async(tabTracker.getId(tab), {
+            windowId: windowTracker.getId(tab.ownerGlobal),
             fromIndex: event.detail,
             toIndex: tab._tPos,
           });
         };
 
-        AllWindowEvents.addListener("TabMove", moveListener);
-        AllWindowEvents.addListener("TabOpen", openListener);
+        windowTracker.addListener("TabMove", moveListener);
+        windowTracker.addListener("TabOpen", openListener);
         return () => {
-          AllWindowEvents.removeListener("TabMove", moveListener);
-          AllWindowEvents.removeListener("TabOpen", openListener);
+          windowTracker.removeListener("TabMove", moveListener);
+          windowTracker.removeListener("TabOpen", openListener);
         };
       }).api(),
 
       onUpdated: new SingletonEventManager(context, "tabs.onUpdated", fire => {
         const restricted = ["url", "favIconUrl", "title"];
 
         function sanitize(extension, changeInfo) {
           let result = {};
@@ -398,24 +268,20 @@ extensions.registerSchemaAPI("tabs", "ad
             if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
               nonempty = true;
               result[prop] = changeInfo[prop];
             }
           }
           return [nonempty, result];
         }
 
-        let fireForBrowser = (browser, changed) => {
+        let fireForTab = (tab, changed) => {
           let [needed, changeInfo] = sanitize(extension, changed);
           if (needed) {
-            let gBrowser = browser.ownerGlobal.gBrowser;
-            let tabElem = gBrowser.getTabForBrowser(browser);
-
-            let tab = TabManager.convert(extension, tabElem);
-            fire.async(tab.id, changeInfo, tab);
+            fire.async(tab.id, changeInfo, tab.convert());
           }
         };
 
         let listener = event => {
           let needed = [];
           if (event.type == "TabAttrModified") {
             let changed = event.detail.changed;
             if (changed.includes("image")) {
@@ -431,81 +297,57 @@ extensions.registerSchemaAPI("tabs", "ad
               needed.push("title");
             }
           } else if (event.type == "TabPinned") {
             needed.push("pinned");
           } else if (event.type == "TabUnpinned") {
             needed.push("pinned");
           }
 
-          if (needed.length && !extension.hasPermission("tabs")) {
-            needed = needed.filter(attr => !restricted.includes(attr));
+          let tab = tabManager.getWrapper(event.originalTarget);
+          let changeInfo = {};
+          for (let prop of needed) {
+            changeInfo[prop] = tab[prop];
           }
 
-          if (needed.length) {
-            let tab = TabManager.convert(extension, event.originalTarget);
+          fireForTab(tab, changeInfo);
+        };
 
-            let changeInfo = {};
-            for (let prop of needed) {
-              changeInfo[prop] = tab[prop];
-            }
-            fire.async(tab.id, changeInfo, tab);
-          }
-        };
-        let progressListener = {
-          onStateChange(browser, webProgress, request, stateFlags, statusCode) {
-            if (!webProgress.isTopLevel) {
-              return;
+        let statusListener = ({browser, status, url}) => {
+          let {gBrowser} = browser.ownerGlobal;
+          let tabElem = gBrowser.getTabForBrowser(browser);
+          if (tabElem) {
+            let changed = {status};
+            if (url) {
+              changed.url = url;
             }
 
-            let status;
-            if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
-              if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
-                status = "loading";
-              } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
-                status = "complete";
-              }
-            } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
-                       statusCode == Cr.NS_BINDING_ABORTED) {
-              status = "complete";
-            }
-
-            fireForBrowser(browser, {status});
-          },
-
-          onLocationChange(browser, webProgress, request, locationURI, flags) {
-            if (!webProgress.isTopLevel) {
-              return;
-            }
-
-            fireForBrowser(browser, {
-              status: webProgress.isLoadingDocument ? "loading" : "complete",
-              url: locationURI.spec,
-            });
-          },
+            fireForTab(tabManager.wrapTab(tabElem), changed);
+          }
         };
 
-        AllWindowEvents.addListener("progress", progressListener);
-        AllWindowEvents.addListener("TabAttrModified", listener);
-        AllWindowEvents.addListener("TabPinned", listener);
-        AllWindowEvents.addListener("TabUnpinned", listener);
+        windowTracker.addListener("status", statusListener);
+        windowTracker.addListener("TabAttrModified", listener);
+        windowTracker.addListener("TabPinned", listener);
+        windowTracker.addListener("TabUnpinned", listener);
 
         return () => {
-          AllWindowEvents.removeListener("progress", progressListener);
-          AllWindowEvents.removeListener("TabAttrModified", listener);
-          AllWindowEvents.removeListener("TabPinned", listener);
-          AllWindowEvents.removeListener("TabUnpinned", listener);
+          windowTracker.removeListener("status", statusListener);
+          windowTracker.removeListener("TabAttrModified", listener);
+          windowTracker.removeListener("TabPinned", listener);
+          windowTracker.removeListener("TabUnpinned", listener);
         };
       }).api(),
 
-      create: function(createProperties) {
+      create(createProperties) {
         return new Promise((resolve, reject) => {
           let window = createProperties.windowId !== null ?
-            WindowManager.getWindow(createProperties.windowId, context) :
-            WindowManager.topWindow;
+            windowTracker.getWindow(createProperties.windowId, context) :
+            windowTracker.topWindow;
+
           if (!window.gBrowser) {
             let obs = (finishedWindow, topic, data) => {
               if (finishedWindow != window) {
                 return;
               }
               Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
               resolve(window);
             };
@@ -531,21 +373,21 @@ extensions.registerSchemaAPI("tabs", "ad
           let options = {};
           if (createProperties.cookieStoreId) {
             if (!global.isValidCookieStoreId(createProperties.cookieStoreId)) {
               return Promise.reject({message: `Illegal cookieStoreId: ${createProperties.cookieStoreId}`});
             }
 
             let privateWindow = PrivateBrowsingUtils.isBrowserPrivate(window.gBrowser);
             if (privateWindow && !global.isPrivateCookieStoreId(createProperties.cookieStoreId)) {
-              return Promise.reject({message: `Illegal to set non-private cookieStorageId in a private window`});
+              return Promise.reject({message: `Illegal to set non-private cookieStoreId in a private window`});
             }
 
             if (!privateWindow && global.isPrivateCookieStoreId(createProperties.cookieStoreId)) {
-              return Promise.reject({message: `Illegal to set private cookieStorageId in a non-private window`});
+              return Promise.reject({message: `Illegal to set private cookieStoreId in a non-private window`});
             }
 
             if (global.isContainerCookieStoreId(createProperties.cookieStoreId)) {
               let containerId = global.getContainerForCookieStoreId(createProperties.cookieStoreId);
               if (!containerId) {
                 return Promise.reject({message: `No cookie store exists with ID ${createProperties.cookieStoreId}`});
               }
 
@@ -571,47 +413,45 @@ extensions.registerSchemaAPI("tabs", "ad
           if (createProperties.index !== null) {
             window.gBrowser.moveTabTo(tab, createProperties.index);
           }
 
           if (createProperties.pinned) {
             window.gBrowser.pinTab(tab);
           }
 
-          if (createProperties.url && !createProperties.url.startsWith("about:")) {
+          if (createProperties.url && createProperties.url !== window.BROWSER_NEW_TAB_URL) {
             // We can't wait for a location change event for about:newtab,
             // since it may be pre-rendered, in which case its initial
             // location change event has already fired.
 
             // Mark the tab as initializing, so that operations like
             // `executeScript` wait until the requested URL is loaded in
             // the tab before dispatching messages to the inner window
             // that contains the URL we're attempting to load.
             tabListener.initializingTabs.add(tab);
           }
 
-          return TabManager.convert(extension, tab);
+          return tabManager.convert(tab);
         });
       },
 
-      remove: function(tabs) {
+      async remove(tabs) {
         if (!Array.isArray(tabs)) {
           tabs = [tabs];
         }
 
         for (let tabId of tabs) {
-          let tab = TabManager.getTab(tabId, context);
+          let tab = tabTracker.getTab(tabId);
           tab.ownerGlobal.gBrowser.removeTab(tab);
         }
-
-        return Promise.resolve();
       },
 
-      update: function(tabId, updateProperties) {
-        let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+      async update(tabId, updateProperties) {
+        let tab = getTabOrActive(tabId);
 
         let tabbrowser = tab.ownerGlobal.gBrowser;
 
         if (updateProperties.url !== null) {
           let url = context.uri.resolve(updateProperties.url);
 
           if (!context.checkLoadURL(url, {dontReportErrors: true})) {
             return Promise.reject({message: `Illegal URL: ${url}`});
@@ -636,134 +476,65 @@ extensions.registerSchemaAPI("tabs", "ad
           if (updateProperties.pinned) {
             tabbrowser.pinTab(tab);
           } else {
             tabbrowser.unpinTab(tab);
           }
         }
         // FIXME: highlighted/selected, openerTabId
 
-        return Promise.resolve(TabManager.convert(extension, tab));
+        return tabManager.convert(tab);
       },
 
-      reload: function(tabId, reloadProperties) {
-        let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+      async reload(tabId, reloadProperties) {
+        let tab = getTabOrActive(tabId);
 
         let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
         if (reloadProperties && reloadProperties.bypassCache) {
           flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
         }
         tab.linkedBrowser.reloadWithFlags(flags);
-
-        return Promise.resolve();
       },
 
-      get: function(tabId) {
-        let tab = TabManager.getTab(tabId, context);
+      async get(tabId) {
+        let tab = tabTracker.getTab(tabId);
 
-        return Promise.resolve(TabManager.convert(extension, tab));
+        return tabManager.convert(tab);
       },
 
       getCurrent() {
         let tab;
         if (context.tabId) {
-          tab = TabManager.convert(extension, TabManager.getTab(context.tabId, context));
+          tab = tabManager.get(context.tabId).convert();
         }
         return Promise.resolve(tab);
       },
 
-      query: function(queryInfo) {
-        let pattern = null;
+      async query(queryInfo) {
         if (queryInfo.url !== null) {
           if (!extension.hasPermission("tabs")) {
             return Promise.reject({message: 'The "tabs" permission is required to use the query API with the "url" parameter'});
           }
 
-          pattern = new MatchPattern(queryInfo.url);
-        }
-
-        function matches(tab) {
-          let props = ["active", "pinned", "highlighted", "status", "title", "index"];
-          for (let prop of props) {
-            if (queryInfo[prop] !== null && queryInfo[prop] != tab[prop]) {
-              return false;
-            }
-          }
-
-          if (queryInfo.audible !== null) {
-            if (queryInfo.audible != tab.audible) {
-              return false;
-            }
-          }
-
-          if (queryInfo.muted !== null) {
-            if (queryInfo.muted != tab.mutedInfo.muted) {
-              return false;
-            }
-          }
-
-          if (queryInfo.cookieStoreId !== null &&
-              tab.cookieStoreId != queryInfo.cookieStoreId) {
-            return false;
-          }
-
-          if (pattern && !pattern.matches(Services.io.newURI(tab.url))) {
-            return false;
-          }
-
-          return true;
+          queryInfo = Object.assign({}, queryInfo);
+          queryInfo.url = new MatchPattern(queryInfo.url);
         }
 
-        let result = [];
-        for (let window of WindowListManager.browserWindows()) {
-          let lastFocused = window === WindowManager.topWindow;
-          if (queryInfo.lastFocusedWindow !== null && queryInfo.lastFocusedWindow !== lastFocused) {
-            continue;
-          }
-
-          let windowType = WindowManager.windowType(window);
-          if (queryInfo.windowType !== null && queryInfo.windowType !== windowType) {
-            continue;
-          }
-
-          if (queryInfo.windowId !== null) {
-            if (queryInfo.windowId === WindowManager.WINDOW_ID_CURRENT) {
-              if (currentWindow(context) !== window) {
-                continue;
-              }
-            } else if (queryInfo.windowId !== WindowManager.getId(window)) {
-              continue;
-            }
-          }
-
-          if (queryInfo.currentWindow !== null) {
-            let eq = window === currentWindow(context);
-            if (queryInfo.currentWindow != eq) {
-              continue;
-            }
-          }
-
-          let tabs = TabManager.for(extension).getTabs(window);
-          for (let tab of tabs) {
-            if (matches(tab)) {
-              result.push(tab);
-            }
-          }
-        }
-        return Promise.resolve(result);
+        return Array.from(tabManager.query(queryInfo, context),
+                          tab => tab.convert());
       },
 
-      captureVisibleTab: function(windowId, options) {
+      captureVisibleTab(windowId, options) {
         if (!extension.hasPermission("<all_urls>")) {
           return Promise.reject({message: "The <all_urls> permission is required to use the captureVisibleTab API"});
         }
 
         let window = windowId == null ?
-          WindowManager.topWindow :
-          WindowManager.getWindow(windowId, context);
+          windowTracker.topWindow :
+          windowTracker.getWindow(windowId, context);
 
         let tab = window.gBrowser.selectedTab;
         return tabListener.awaitTabReady(tab).then(() => {
           let browser = tab.linkedBrowser;
           let recipient = {
             innerWindowID: browser.innerWindowID,
           };
 
@@ -783,133 +554,72 @@ extensions.registerSchemaAPI("tabs", "ad
             height: browser.clientHeight,
           };
 
           return context.sendMessage(browser.messageManager, "Extension:Capture",
                                      message, {recipient});
         });
       },
 
-      detectLanguage: function(tabId) {
-        let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+      async detectLanguage(tabId) {
+        let tab = getTabOrActive(tabId);
 
         return tabListener.awaitTabReady(tab).then(() => {
           let browser = tab.linkedBrowser;
           let recipient = {innerWindowID: browser.innerWindowID};
 
           return context.sendMessage(browser.messageManager, "Extension:DetectLanguage",
                                      {}, {recipient});
         });
       },
 
-      // Used to executeScript, insertCSS and removeCSS.
-      _execute: function(tabId, details, kind, method) {
-        let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
-
-        let options = {
-          js: [],
-          css: [],
-          remove_css: method == "removeCSS",
-        };
-
-        // We require a `code` or a `file` property, but we can't accept both.
-        if ((details.code === null) == (details.file === null)) {
-          return Promise.reject({message: `${method} requires either a 'code' or a 'file' property, but not both`});
-        }
-
-        if (details.frameId !== null && details.allFrames) {
-          return Promise.reject({message: `'frameId' and 'allFrames' are mutually exclusive`});
-        }
-
-        if (TabManager.for(extension).hasActiveTabPermission(tab)) {
-          // If we have the "activeTab" permission for this tab, ignore
-          // the host whitelist.
-          options.matchesHost = ["<all_urls>"];
-        } else {
-          options.matchesHost = extension.whiteListedHosts.serialize();
-        }
+      async executeScript(tabId, details) {
+        let tab = await promiseTabWhenReady(tabId);
 
-        if (details.code !== null) {
-          options[kind + "Code"] = details.code;
-        }
-        if (details.file !== null) {
-          let url = context.uri.resolve(details.file);
-          if (!extension.isExtensionURL(url)) {
-            return Promise.reject({message: "Files to be injected must be within the extension"});
-          }
-          options[kind].push(url);
-        }
-        if (details.allFrames) {
-          options.all_frames = details.allFrames;
-        }
-        if (details.frameId !== null) {
-          options.frame_id = details.frameId;
-        }
-        if (details.matchAboutBlank) {
-          options.match_about_blank = details.matchAboutBlank;
-        }
-        if (details.runAt !== null) {
-          options.run_at = details.runAt;
-        } else {
-          options.run_at = "document_idle";
-        }
-        if (details.cssOrigin !== null) {
-          options.css_origin = details.cssOrigin;
-        } else {
-          options.css_origin = "author";
-        }
-
-        return tabListener.awaitTabReady(tab).then(() => {
-          let browser = tab.linkedBrowser;
-          let recipient = {
-            innerWindowID: browser.innerWindowID,
-          };
-
-          return context.sendMessage(browser.messageManager, "Extension:Execute", {options}, {recipient});
-        });
+        return tab.executeScript(context, details);
       },
 
-      executeScript: function(tabId, details) {
-        return self.tabs._execute(tabId, details, "js", "executeScript");
+      async insertCSS(tabId, details) {
+        let tab = await promiseTabWhenReady(tabId);
+
+        return tab.insertCSS(context, details);
       },
 
-      insertCSS: function(tabId, details) {
-        return self.tabs._execute(tabId, details, "css", "insertCSS").then(() => {});
+      async removeCSS(tabId, details) {
+        let tab = await promiseTabWhenReady(tabId);
+
+        return tab.removeCSS(context, details);
       },
 
-      removeCSS: function(tabId, details) {
-        return self.tabs._execute(tabId, details, "css", "removeCSS").then(() => {});
-      },
-
-      move: function(tabIds, moveProperties) {
+      async move(tabIds, moveProperties) {
         let index = moveProperties.index;
         let tabsMoved = [];
         if (!Array.isArray(tabIds)) {
           tabIds = [tabIds];
         }
 
         let destinationWindow = null;
         if (moveProperties.windowId !== null) {
-          destinationWindow = WindowManager.getWindow(moveProperties.windowId, context);
+          destinationWindow = windowTracker.getWindow(moveProperties.windowId);
           // Fail on an invalid window.
           if (!destinationWindow) {
             return Promise.reject({message: `Invalid window ID: ${moveProperties.windowId}`});
           }
         }
 
         /*
           Indexes are maintained on a per window basis so that a call to
             move([tabA, tabB], {index: 0})
               -> tabA to 0, tabB to 1 if tabA and tabB are in the same window
             move([tabA, tabB], {index: 0})
               -> tabA to 0, tabB to 0 if tabA and tabB are in different windows
         */
         let indexMap = new Map();
 
-        let tabs = tabIds.map(tabId => TabManager.getTab(tabId, context));
+        let tabs = tabIds.map(tabId => tabTracker.getTab(tabId));
         for (let tab of tabs) {
           // If the window is not specified, use the window from the tab.
           let window = destinationWindow || tab.ownerGlobal;
           let gBrowser = window.gBrowser;
 
           let insertionPoint = indexMap.get(window) || index;
           // If the index is -1 it should go to the end of the tabs.
           if (insertionPoint == -1) {
@@ -934,58 +644,60 @@ extensions.registerSchemaAPI("tabs", "ad
             tab = gBrowser.adoptTab(tab, insertionPoint, false);
           } else {
             // If the window we are moving is the same, just move the tab.
             gBrowser.moveTabTo(tab, insertionPoint);
           }
           tabsMoved.push(tab);
         }
 
-        return Promise.resolve(tabsMoved.map(tab => TabManager.convert(extension, tab)));
+        return tabsMoved.map(tab => tabManager.convert(tab));
       },
 
-      duplicate: function(tabId) {
-        let tab = TabManager.getTab(tabId, context);
+      duplicate(tabId) {
+        let tab = tabTracker.getTab(tabId);
 
         let gBrowser = tab.ownerGlobal.gBrowser;
         let newTab = gBrowser.duplicateTab(tab);
 
         return new Promise(resolve => {
           // We need to use SSTabRestoring because any attributes set before
           // are ignored. SSTabRestored is too late and results in a jump in
           // the UI. See http://bit.ly/session-store-api for more information.
           newTab.addEventListener("SSTabRestoring", function() {
             // As the tab is restoring, move it to the correct position.
+
             // Pinned tabs that are duplicated are inserted
             // after the existing pinned tab and pinned.
             if (tab.pinned) {
               gBrowser.pinTab(newTab);
             }
             gBrowser.moveTabTo(newTab, tab._tPos + 1);
           }, {once: true});
 
           newTab.addEventListener("SSTabRestored", function() {
             // Once it has been restored, select it and return the promise.
             gBrowser.selectedTab = newTab;
-            return resolve(TabManager.convert(extension, newTab));
+
+            resolve(tabManager.convert(newTab));
           }, {once: true});
         });
       },
 
       getZoom(tabId) {
-        let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+        let tab = getTabOrActive(tabId);
 
         let {ZoomManager} = tab.ownerGlobal;
         let zoom = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
 
         return Promise.resolve(zoom);
       },
 
       setZoom(tabId, zoom) {
-        let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+        let tab = getTabOrActive(tabId);
 
         let {FullZoom, ZoomManager} = tab.ownerGlobal;
 
         if (zoom === 0) {
           // A value of zero means use the default zoom factor.
           return FullZoom.reset(tab.linkedBrowser);
         } else if (zoom >= ZoomManager.MIN && zoom <= ZoomManager.MAX) {
           FullZoom.setZoom(zoom, tab.linkedBrowser);
@@ -994,33 +706,33 @@ extensions.registerSchemaAPI("tabs", "ad
             message: `Zoom value ${zoom} out of range (must be between ${ZoomManager.MIN} and ${ZoomManager.MAX})`,
           });
         }
 
         return Promise.resolve();
       },
 
       _getZoomSettings(tabId) {
-        let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+        let tab = getTabOrActive(tabId);
 
         let {FullZoom} = tab.ownerGlobal;
 
         return {
           mode: "automatic",
           scope: FullZoom.siteSpecific ? "per-origin" : "per-tab",
           defaultZoomFactor: 1,
         };
       },
 
       getZoomSettings(tabId) {
         return Promise.resolve(this._getZoomSettings(tabId));
       },
 
       setZoomSettings(tabId, settings) {
-        let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+        let tab = getTabOrActive(tabId);
 
         let currentSettings = this._getZoomSettings(tab.id);
 
         if (!Object.keys(settings).every(key => settings[key] === currentSettings[key])) {
           return Promise.reject(`Unsupported zoom settings: ${JSON.stringify(settings)}`);
         }
         return Promise.resolve();
       },
@@ -1032,17 +744,17 @@ extensions.registerSchemaAPI("tabs", "ad
           return ZoomManager.getZoomForBrowser(browser);
         };
 
         // Stores the last known zoom level for each tab's browser.
         // WeakMap[<browser> -> number]
         let zoomLevels = new WeakMap();
 
         // Store the zoom level for all existing tabs.
-        for (let window of WindowListManager.browserWindows()) {
+        for (let window of windowTracker.browserWindows()) {
           for (let tab of window.gBrowser.tabs) {
             let browser = tab.linkedBrowser;
             zoomLevels.set(browser, getZoomLevel(browser));
           }
         }
 
         let tabCreated = (eventName, event) => {
           let browser = event.tab.linkedBrowser;
@@ -1069,35 +781,35 @@ extensions.registerSchemaAPI("tabs", "ad
           }
 
           let oldZoomFactor = zoomLevels.get(browser);
           let newZoomFactor = getZoomLevel(browser);
 
           if (oldZoomFactor != newZoomFactor) {
             zoomLevels.set(browser, newZoomFactor);
 
-            let tabId = TabManager.getId(tab);
+            let tabId = tabTracker.getId(tab);
             fire.async({
               tabId,
               oldZoomFactor,
               newZoomFactor,
               zoomSettings: self.tabs._getZoomSettings(tabId),
             });
           }
         };
 
-        tabListener.on("tab-attached", tabCreated);
-        tabListener.on("tab-created", tabCreated);
+        tabTracker.on("tab-attached", tabCreated);
+        tabTracker.on("tab-created", tabCreated);
 
-        AllWindowEvents.addListener("FullZoomChange", zoomListener);
-        AllWindowEvents.addListener("TextZoomChange", zoomListener);
+        windowTracker.addListener("FullZoomChange", zoomListener);
+        windowTracker.addListener("TextZoomChange", zoomListener);
         return () => {
-          tabListener.off("tab-attached", tabCreated);
-          tabListener.off("tab-created", tabCreated);
+          tabTracker.off("tab-attached", tabCreated);
+          tabTracker.off("tab-created", tabCreated);
 
-          AllWindowEvents.removeListener("FullZoomChange", zoomListener);
-          AllWindowEvents.removeListener("TextZoomChange", zoomListener);
+          windowTracker.removeListener("FullZoomChange", zoomListener);
+          windowTracker.removeListener("TextZoomChange", zoomListener);
         };
       }).api(),
     },
   };
   return self;
 });
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -1,568 +1,54 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
-                                  "resource:///modules/CustomizableUI.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
-                                  "resource:///modules/E10SUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-                                  "resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
-                                  "resource://gre/modules/Timer.jsm");
+
+/* globals TabBase, WindowBase, TabTrackerBase, WindowTrackerBase, TabManagerBase, WindowManagerBase */
+Cu.import("resource://gre/modules/ExtensionTabs.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                    "@mozilla.org/content/style-sheet-service;1",
                                    "nsIStyleSheetService");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
-
-const POPUP_LOAD_TIMEOUT_MS = 200;
-
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 var {
-  DefaultWeakMap,
-  promiseEvent,
+  ExtensionError,
   SingletonEventManager,
+  defineLazyGetter,
 } = ExtensionUtils;
 
+let tabTracker;
+let windowTracker;
+
 // This file provides some useful code for the |tabs| and |windows|
 // modules. All of the code is installed on |global|, which is a scope
 // 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, "_");
 };
 
-function promisePopupShown(popup) {
-  return new Promise(resolve => {
-    if (popup.state == "open") {
-      resolve();
-    } else {
-      popup.addEventListener("popupshown", function(event) {
-        resolve();
-      }, {once: true});
-    }
-  });
-}
-
-XPCOMUtils.defineLazyGetter(this, "popupStylesheets", () => {
-  let stylesheets = ["chrome://browser/content/extension.css"];
-
-  if (AppConstants.platform === "macosx") {
-    stylesheets.push("chrome://browser/content/extension-mac.css");
-  }
-  return stylesheets;
-});
-
-XPCOMUtils.defineLazyGetter(this, "standaloneStylesheets", () => {
-  let stylesheets = [];
-
-  if (AppConstants.platform === "macosx") {
-    stylesheets.push("chrome://browser/content/extension-mac-panel.css");
-  }
-  if (AppConstants.platform === "win") {
-    stylesheets.push("chrome://browser/content/extension-win-panel.css");
-  }
-  return stylesheets;
-});
-
-class BasePopup {
-  constructor(extension, viewNode, popupURL, browserStyle, fixedWidth = false) {
-    this.extension = extension;
-    this.popupURL = popupURL;
-    this.viewNode = viewNode;
-    this.browserStyle = browserStyle;
-    this.window = viewNode.ownerGlobal;
-    this.destroyed = false;
-    this.fixedWidth = fixedWidth;
-
-    extension.callOnClose(this);
-
-    this.contentReady = new Promise(resolve => {
-      this._resolveContentReady = resolve;
-    });
-
-    this.viewNode.addEventListener(this.DESTROY_EVENT, this);
-
-    let doc = viewNode.ownerDocument;
-    let arrowContent = doc.getAnonymousElementByAttribute(this.panel, "class", "panel-arrowcontent");
-    this.borderColor = doc.defaultView.getComputedStyle(arrowContent).borderTopColor;
-
-    this.browser = null;
-    this.browserLoaded = new Promise((resolve, reject) => {
-      this.browserLoadedDeferred = {resolve, reject};
-    });
-    this.browserReady = this.createBrowser(viewNode, popupURL);
-
-    BasePopup.instances.get(this.window).set(extension, this);
-  }
-
-  static for(extension, window) {
-    return BasePopup.instances.get(window).get(extension);
-  }
-
-  close() {
-    this.closePopup();
-  }
-
-  destroy() {
-    this.extension.forgetOnClose(this);
-
-    this.destroyed = true;
-    this.browserLoadedDeferred.reject(new Error("Popup destroyed"));
-    return this.browserReady.then(() => {
-      this.destroyBrowser(this.browser, true);
-      this.browser.remove();
-
-      if (this.viewNode) {
-        this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
-        this.viewNode.style.maxHeight = "";
-      }
-
-      if (this.panel) {
-        this.panel.style.removeProperty("--arrowpanel-background");
-        this.panel.style.removeProperty("--panel-arrow-image-vertical");
-      }
-
-      BasePopup.instances.get(this.window).delete(this.extension);
-
-      this.browser = null;
-      this.viewNode = null;
-    });
-  }
-
-  destroyBrowser(browser, finalize = false) {
-    let mm = browser.messageManager;
-    // If the browser has already been removed from the document, because the
-    // popup was closed externally, there will be no message manager here, so
-    // just replace our receiveMessage method with a stub.
-    if (mm) {
-      mm.removeMessageListener("DOMTitleChanged", this);
-      mm.removeMessageListener("Extension:BrowserBackgroundChanged", this);
-      mm.removeMessageListener("Extension:BrowserContentLoaded", this);
-      mm.removeMessageListener("Extension:BrowserResized", this);
-      mm.removeMessageListener("Extension:DOMWindowClose", this);
-    } else if (finalize) {
-      this.receiveMessage = () => {};
-    }
-  }
-
-  // Returns the name of the event fired on `viewNode` when the popup is being
-  // destroyed. This must be implemented by every subclass.
-  get DESTROY_EVENT() {
-    throw new Error("Not implemented");
-  }
-
-  get STYLESHEETS() {
-    let sheets = [];
-
-    if (this.browserStyle) {
-      sheets.push(...popupStylesheets);
-    }
-    if (!this.fixedWidth) {
-      sheets.push(...standaloneStylesheets);
-    }
-
-    return sheets;
-  }
-
-  get panel() {
-    let panel = this.viewNode;
-    while (panel && panel.localName != "panel") {
-      panel = panel.parentNode;
-    }
-    return panel;
-  }
-
-  receiveMessage({name, data}) {
-    switch (name) {
-      case "DOMTitleChanged":
-        this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
-        break;
-
-      case "Extension:BrowserBackgroundChanged":
-        this.setBackground(data.background);
-        break;
-
-      case "Extension:BrowserContentLoaded":
-        this.browserLoadedDeferred.resolve();
-        break;
-
-      case "Extension:BrowserResized":
-        this._resolveContentReady();
-        if (this.ignoreResizes) {
-          this.dimensions = data;
-        } else {
-          this.resizeBrowser(data);
-        }
-        break;
-
-      case "Extension:DOMWindowClose":
-        this.closePopup();
-        break;
-    }
-  }
-
-  handleEvent(event) {
-    switch (event.type) {
-      case this.DESTROY_EVENT:
-        if (!this.destroyed) {
-          this.destroy();
-        }
-        break;
-    }
-  }
-
-  createBrowser(viewNode, popupURL = null) {
-    let document = viewNode.ownerDocument;
-    let browser = document.createElementNS(XUL_NS, "browser");
-    browser.setAttribute("type", "content");
-    browser.setAttribute("disableglobalhistory", "true");
-    browser.setAttribute("transparent", "true");
-    browser.setAttribute("class", "webextension-popup-browser");
-    browser.setAttribute("webextension-view-type", "popup");
-    browser.setAttribute("tooltip", "aHTMLTooltip");
-
-    if (this.extension.remote) {
-      browser.setAttribute("remote", "true");
-      browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
-    }
-
-    // We only need flex sizing for the sake of the slide-in sub-views of the
-    // main menu panel, so that the browser occupies the full width of the view,
-    // and also takes up any extra height that's available to it.
-    browser.setAttribute("flex", "1");
-
-    // Note: When using noautohide panels, the popup manager will add width and
-    // height attributes to the panel, breaking our resize code, if the browser
-    // starts out smaller than 30px by 10px. This isn't an issue now, but it
-    // will be if and when we popup debugging.
-
-    this.browser = browser;
-
-    let readyPromise;
-    if (this.extension.remote) {
-      readyPromise = promiseEvent(browser, "XULFrameLoaderCreated");
-    } else {
-      readyPromise = promiseEvent(browser, "load");
-    }
-
-    viewNode.appendChild(browser);
-
-    extensions.emit("extension-browser-inserted", browser);
-
-    let setupBrowser = browser => {
-      let mm = browser.messageManager;
-      mm.addMessageListener("DOMTitleChanged", this);
-      mm.addMessageListener("Extension:BrowserBackgroundChanged", this);
-      mm.addMessageListener("Extension:BrowserContentLoaded", this);
-      mm.addMessageListener("Extension:BrowserResized", this);
-      mm.addMessageListener("Extension:DOMWindowClose", this, true);
-      return browser;
-    };
-
-    if (!popupURL) {
-      // For remote browsers, we can't do any setup until the frame loader is
-      // created. Non-remote browsers get a message manager immediately, so
-      // there's no need to wait for the load event.
-      if (this.extension.remote) {
-        return readyPromise.then(() => setupBrowser(browser));
-      }
-      return setupBrowser(browser);
-    }
-
-    return readyPromise.then(() => {
-      setupBrowser(browser);
-      let mm = browser.messageManager;
-
-      mm.loadFrameScript(
-        "chrome://extensions/content/ext-browser-content.js", false);
-
-      mm.sendAsyncMessage("Extension:InitBrowser", {
-        allowScriptsToClose: true,
-        fixedWidth: this.fixedWidth,
-        maxWidth: 800,
-        maxHeight: 600,
-        stylesheets: this.STYLESHEETS,
-      });
-
-      browser.loadURI(popupURL);
-    });
-  }
-
-  resizeBrowser({width, height, detail}) {
-    if (this.fixedWidth) {
-      // Figure out how much extra space we have on the side of the panel
-      // opposite the arrow.
-      let side = this.panel.getAttribute("side") == "top" ? "bottom" : "top";
-      let maxHeight = this.viewHeight + this.extraHeight[side];
-
-      height = Math.min(height, maxHeight);
-      this.browser.style.height = `${height}px`;
-
-      // Set a maximum height on the <panelview> element to our preferred
-      // maximum height, so that the PanelUI resizing code can make an accurate
-      // calculation. If we don't do this, the flex sizing logic will prevent us
-      // from ever reporting a preferred size smaller than the height currently
-      // available to us in the panel.
-      height = Math.max(height, this.viewHeight);
-      this.viewNode.style.maxHeight = `${height}px`;
-    } else {
-      this.browser.style.width = `${width}px`;
-      this.browser.style.minWidth = `${width}px`;
-      this.browser.style.height = `${height}px`;
-      this.browser.style.minHeight = `${height}px`;
-    }
-
-    let event = new this.window.CustomEvent("WebExtPopupResized", {detail});
-    this.browser.dispatchEvent(event);
-  }
-
-  setBackground(background) {
-    let panelBackground = "";
-    let panelArrow = "";
-
-    if (background) {
-      let borderColor = this.borderColor || background;
-
-      panelBackground = background;
-      panelArrow = `url("data:image/svg+xml,${encodeURIComponent(`<?xml version="1.0" encoding="UTF-8"?>
-        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">
-          <path d="M 0,10 L 10,0 20,10 z" fill="${borderColor}"/>
-          <path d="M 1,10 L 10,1 19,10 z" fill="${background}"/>
-        </svg>
-      `)}")`;
-    }
-
-    this.panel.style.setProperty("--arrowpanel-background", panelBackground);
-    this.panel.style.setProperty("--panel-arrow-image-vertical", panelArrow);
-    this.background = background;
-  }
-}
-
-/**
- * A map of active popups for a given browser window.
- *
- * WeakMap[window -> WeakMap[Extension -> BasePopup]]
- */
-BasePopup.instances = new DefaultWeakMap(() => new WeakMap());
-
-class PanelPopup extends BasePopup {
-  constructor(extension, imageNode, popupURL, browserStyle) {
-    let document = imageNode.ownerDocument;
-
-    let panel = document.createElement("panel");
-    panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
-    panel.setAttribute("class", "browser-extension-panel");
-    panel.setAttribute("tabspecific", "true");
-    panel.setAttribute("type", "arrow");
-    panel.setAttribute("role", "group");
-
-    document.getElementById("mainPopupSet").appendChild(panel);
-
-    super(extension, panel, popupURL, browserStyle);
-
-    this.contentReady.then(() => {
-      panel.openPopup(imageNode, "bottomcenter topright", 0, 0, false, false);
-
-      let event = new this.window.CustomEvent("WebExtPopupLoaded", {
-        bubbles: true,
-        detail: {extension},
-      });
-      this.browser.dispatchEvent(event);
-    });
-  }
-
-  get DESTROY_EVENT() {
-    return "popuphidden";
-  }
-
-  destroy() {
-    super.destroy();
-    this.viewNode.remove();
-    this.viewNode = null;
-  }
-
-  closePopup() {
-    promisePopupShown(this.viewNode).then(() => {
-      // Make sure we're not already destroyed, or removed from the DOM.
-      if (this.viewNode && this.viewNode.hidePopup) {
-        this.viewNode.hidePopup();
-      }
-    });
-  }
-}
-
-class ViewPopup extends BasePopup {
-  constructor(extension, window, popupURL, browserStyle, fixedWidth) {
-    let document = window.document;
-
-    // Create a temporary panel to hold the browser while it pre-loads its
-    // content. This panel will never be shown, but the browser's docShell will
-    // be swapped with the browser in the real panel when it's ready.
-    let panel = document.createElement("panel");
-    panel.setAttribute("type", "arrow");
-    document.getElementById("mainPopupSet").appendChild(panel);
-
-    super(extension, panel, popupURL, browserStyle, fixedWidth);
-
-    this.ignoreResizes = true;
-
-    this.attached = false;
-    this.shown = false;
-    this.tempPanel = panel;
-
-    this.browser.classList.add("webextension-preload-browser");
-  }
-
-  /**
-   * Attaches the pre-loaded browser to the given view node, and reserves a
-   * promise which resolves when the browser is ready.
-   *
-   * @param {Element} viewNode
-   *        The node to attach the browser to.
-   * @returns {Promise<boolean>}
-   *        Resolves when the browser is ready. Resolves to `false` if the
-   *        browser was destroyed before it was fully loaded, and the popup
-   *        should be closed, or `true` otherwise.
-   */
-  attach(viewNode) {
-    return Task.spawn(function* () {
-      this.viewNode = viewNode;
-      this.viewNode.addEventListener(this.DESTROY_EVENT, this);
-
-      // Wait until the browser element is fully initialized, and give it at least
-      // a short grace period to finish loading its initial content, if necessary.
-      //
-      // In practice, the browser that was created by the mousdown handler should
-      // nearly always be ready by this point.
-      yield Promise.all([
-        this.browserReady,
-        Promise.race([
-          // This promise may be rejected if the popup calls window.close()
-          // before it has fully loaded.
-          this.browserLoaded.catch(() => {}),
-          new Promise(resolve => setTimeout(resolve, POPUP_LOAD_TIMEOUT_MS)),
-        ]),
-      ]);
-
-      if (!this.destroyed && !this.panel) {
-        this.destroy();
-      }
-
-      if (this.destroyed) {
-        CustomizableUI.hidePanelForNode(viewNode);
-        return false;
-      }
-
-      this.attached = true;
-
-
-      // Store the initial height of the view, so that we never resize menu panel
-      // sub-views smaller than the initial height of the menu.
-      this.viewHeight = this.viewNode.boxObject.height;
-
-      // Calculate the extra height available on the screen above and below the
-      // menu panel. Use that to calculate the how much the sub-view may grow.
-      let popupRect = this.panel.getBoundingClientRect();
-
-      this.setBackground(this.background);
-
-      let win = this.window;
-      let popupBottom = win.mozInnerScreenY + popupRect.bottom;
-      let popupTop = win.mozInnerScreenY + popupRect.top;
-
-      let screenBottom = win.screen.availTop + win.screen.availHeight;
-      this.extraHeight = {
-        bottom: Math.max(0, screenBottom - popupBottom),
-        top:  Math.max(0, popupTop - win.screen.availTop),
-      };
-
-      // Create a new browser in the real popup.
-      let browser = this.browser;
-      yield this.createBrowser(this.viewNode);
-
-      this.ignoreResizes = false;
-
-      this.browser.swapDocShells(browser);
-      this.destroyBrowser(browser);
-
-      if (this.dimensions && !this.fixedWidth) {
-        this.resizeBrowser(this.dimensions);
-      }
-
-      this.tempPanel.remove();
-      this.tempPanel = null;
-
-      this.shown = true;
-
-      if (this.destroyed) {
-        this.closePopup();
-        this.destroy();
-        return false;
-      }
-
-      let event = new this.window.CustomEvent("WebExtPopupLoaded", {
-        bubbles: true,
-        detail: {extension: this.extension},
-      });
-      this.browser.dispatchEvent(event);
-
-      return true;
-    }.bind(this));
-  }
-
-  destroy() {
-    return super.destroy().then(() => {
-      if (this.tempPanel) {
-        this.tempPanel.remove();
-        this.tempPanel = null;
-      }
-    });
-  }
-
-  get DESTROY_EVENT() {
-    return "ViewHiding";
-  }
-
-  closePopup() {
-    if (this.shown) {
-      CustomizableUI.hidePanelForNode(this.viewNode);
-    } else if (this.attached) {
-      this.destroyed = true;
-    } else {
-      this.destroy();
-    }
-  }
-}
-
-Object.assign(global, {PanelPopup, ViewPopup});
-
 // 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();
 
-  AllWindowEvents.addListener("progress", this);
-  AllWindowEvents.addListener("TabSelect", this);
+  windowTracker.addListener("progress", this);
+  windowTracker.addListener("TabSelect", this);
 
   EventEmitter.decorate(this);
 };
 
 TabContext.prototype = {
   get(tab) {
     if (!this.tabData.has(tab)) {
       this.tabData.set(tab, this.getDefaults(tab));
@@ -599,430 +85,468 @@ TabContext.prototype = {
         !(lastLocation && lastLocation.equalsExceptRef(browser.currentURI))) {
       let tab = gBrowser.getTabForBrowser(browser);
       this.emit("location-change", tab, true);
     }
     this.lastLocation.set(browser, browser.currentURI);
   },
 
   shutdown() {
-    AllWindowEvents.removeListener("progress", this);
-    AllWindowEvents.removeListener("TabSelect", this);
+    windowTracker.removeListener("progress", this);
+    windowTracker.removeListener("TabSelect", this);
   },
 };
 
-// Manages tab mappings and permissions for a specific extension.
-function ExtensionTabManager(extension) {
-  this.extension = extension;
 
-  // A mapping of tab objects to the inner window ID the extension currently has
-  // the active tab permission for. The active permission for a given tab is
-  // valid only for the inner window that was active when the permission was
-  // granted. If the tab navigates, the inner window ID changes, and the
-  // permission automatically becomes stale.
-  //
-  // WeakMap[tab => inner-window-id<int>]
-  this.hasTabPermissionFor = new WeakMap();
+class WindowTracker extends WindowTrackerBase {
+  addProgressListener(window, listener) {
+    window.gBrowser.addTabsProgressListener(listener);
+  }
+
+  removeProgressListener(window, listener) {
+    window.gBrowser.removeTabsProgressListener(listener);
+  }
 }
 
-ExtensionTabManager.prototype = {
-  addActiveTabPermission(tab = TabManager.activeTab) {
-    if (this.extension.hasPermission("activeTab")) {
-      // Note that, unlike Chrome, we don't currently clear this permission with
-      // the tab navigates. If the inner window is revived from BFCache before
-      // we've granted this permission to a new inner window, the extension
-      // maintains its permissions for it.
-      this.hasTabPermissionFor.set(tab, tab.linkedBrowser.innerWindowID);
+global.WindowEventManager = class extends SingletonEventManager {
+  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.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(tab) {
+    if (this._tabs.has(tab)) {
+      return this._tabs.get(tab);
+    }
+
+    this.init();
+
+    let id = this._nextId++;
+    this.setId(tab, id);
+    return id;
+  }
+
+  setId(tab, id) {
+    this._tabs.set(tab, id);
+    this._tabIds.set(id, tab);
+  }
+
+  _handleTabDestroyed(event, {tab}) {
+    let id = this._tabs.get(tab);
+    if (id) {
+      this._tabs.delete(tab);
+      if (this._tabIds.get(id) === tab) {
+        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 tab = this._tabIds.get(tabId);
+    if (tab) {
+      return tab;
+    }
+    if (default_ !== undefined) {
+      return default_;
+    }
+    throw new ExtensionError(`Invalid tab ID: ${tabId}`);
+  }
+
+  handleEvent(event) {
+    let tab = event.target;
+
+    switch (event.type) {
+      case "TabOpen":
+        let {adoptedTab} = event.detail;
+        if (adoptedTab) {
+          this.adoptedTabs.set(adoptedTab, event.target);
 
-  revokeActiveTabPermission(tab = TabManager.activeTab) {
-    this.hasTabPermissionFor.delete(tab);
-  },
+          // 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(tab, this.getId(adoptedTab));
+
+          adoptedTab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
+            windowId: windowTracker.getId(tab.ownerGlobal),
+          });
+        }
+
+        // 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);
+          }
+        });
+        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(tab));
+
+          this.emitDetached(tab, adoptedBy);
+        } else {
+          this.emitRemoved(tab, false);
+        }
+        break;
+    }
+  }
 
-  // Returns true if the extension has the "activeTab" permission for this tab.
-  // This is somewhat more permissive than the generic "tabs" permission, as
-  // checked by |hasTabPermission|, in that it also allows programmatic script
-  // injection without an explicit host permission.
-  hasActiveTabPermission(tab) {
-    // This check is redundant with addTabPermission, but cheap.
-    if (this.extension.hasPermission("activeTab")) {
-      return (this.hasTabPermissionFor.has(tab) &&
-              this.hasTabPermissionFor.get(tab) === tab.linkedBrowser.innerWindowID);
+  _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 tab = window.arguments[0];
+      let adoptedBy = window.gBrowser.tabs[0];
+
+      this.adoptedTabs.set(tab, adoptedBy);
+      this.setId(adoptedBy, this.getId(tab));
+
+      // We need to be sure to fire this event after the onDetached event
+      // for the original tab.
+      let listener = (event, details) => {
+        if (details.tab === tab) {
+          this.off("tab-detached", listener);
+
+          Promise.resolve().then(() => {
+            this.emitAttached(details.adoptedBy);
+          });
+        }
+      };
+
+      this.on("tab-detached", listener);
+    } else {
+      for (let tab of window.gBrowser.tabs) {
+        this.emitCreated(tab);
+      }
+    }
+  }
+
+  _handleWindowClose(window) {
+    for (let tab of window.gBrowser.tabs) {
+      if (this.adoptedTabs.has(tab)) {
+        this.emitDetached(tab, this.adoptedTabs.get(tab));
+      } else {
+        this.emitRemoved(tab, true);
+      }
     }
-    return false;
-  },
+  }
+
+  emitAttached(tab) {
+    let newWindowId = windowTracker.getId(tab.ownerGlobal);
+    let tabId = this.getId(tab);
+
+    this.emit("tab-attached", {tab, tabId, newWindowId, newPosition: tab._tPos});
+  }
+
+  emitDetached(tab, adoptedBy) {
+    let oldWindowId = windowTracker.getId(tab.ownerGlobal);
+    let tabId = this.getId(tab);
+
+    this.emit("tab-detached", {tab, adoptedBy, tabId, oldWindowId, oldPosition: tab._tPos});
+  }
+
+  emitCreated(tab) {
+    this.emit("tab-created", {tab});
+  }
+
+  emitRemoved(tab, isWindowClosing) {
+    let windowId = windowTracker.getId(tab.ownerGlobal);
+    let tabId = this.getId(tab);
+
+    // 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.mainThread.dispatch(() => {
+      this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
+    }, Ci.nsIThread.DISPATCH_NORMAL);
+  }
+
+  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,
+    };
 
-  hasTabPermission(tab) {
-    return this.extension.hasPermission("tabs") || this.hasActiveTabPermission(tab);
-  },
+    let {gBrowser} = browser.ownerGlobal;
+    // Some non-browser windows have gBrowser but not
+    // getTabForBrowser!
+    if (gBrowser && gBrowser.getTabForBrowser) {
+      result.windowId = windowTracker.getId(browser.ownerGlobal);
+
+      let tab = gBrowser.getTabForBrowser(browser);
+      if (tab) {
+        result.tabId = this.getId(tab);
+      }
+    }
+
+    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});
 
-  convert(tab) {
-    let window = tab.ownerGlobal;
-    let browser = tab.linkedBrowser;
+class Tab extends TabBase {
+  get _favIconUrl() {
+    return this.window.gBrowser.getIcon(this.tab);
+  }
+
+  get audible() {
+    return this.tab.soundPlaying;
+  }
+
+  get browser() {
+    return this.tab.linkedBrowser;
+  }
+
+  get cookieStoreId() {
+    return getCookieStoreIdForTab(this, this.tab);
+  }
+
+  get height() {
+    return this.browser.clientHeight;
+  }
+
+  get index() {
+    return this.tab._tPos;
+  }
+
+  get innerWindowID() {
+    return this.browser.innerWindowID;
+  }
+
+  get mutedInfo() {
+    let tab = this.tab;
 
     let mutedInfo = {muted: tab.muted};
     if (tab.muteReason === null) {
       mutedInfo.reason = "user";
     } else if (tab.muteReason) {
       mutedInfo.reason = "extension";
       mutedInfo.extensionId = tab.muteReason;
     }
 
-    let result = {
-      id: TabManager.getId(tab),
-      index: tab._tPos,
-      windowId: WindowManager.getId(window),
-      selected: tab.selected,
-      highlighted: tab.selected,
-      active: tab.selected,
-      pinned: tab.pinned,
-      status: TabManager.getStatus(tab),
-      incognito: WindowManager.isBrowserPrivate(browser),
-      width: browser.frameLoader.lazyWidth || browser.clientWidth,
-      height: browser.frameLoader.lazyHeight || browser.clientHeight,
-      audible: tab.soundPlaying,
-      mutedInfo,
-    };
-    if (this.extension.hasPermission("cookies")) {
-      result.cookieStoreId = getCookieStoreIdForTab(result, tab);
-    }
+    return mutedInfo;
+  }
+
+  get pinned() {
+    return this.tab.pinned;
+  }
+
+  get active() {
+    return this.tab.selected;
+  }
+
+  get selected() {
+    return this.tab.selected;
+  }
 
-    if (this.hasTabPermission(tab)) {
-      result.url = browser.currentURI.spec;
-      let title = browser.contentTitle || tab.label;
-      if (title) {
-        result.title = title;
-      }
-      let icon = window.gBrowser.getIcon(tab);
-      if (icon) {
-        result.favIconUrl = icon;
-      }
+  get status() {
+    if (this.tab.getAttribute("busy") === "true") {
+      return "loading";
     }
+    return "complete";
+  }
 
-    return result;
-  },
+  get width() {
+    return this.browser.clientWidth;
+  }
 
-  // Converts tabs returned from SessionStore.getClosedTabData and
-  // SessionStore.getClosedWindowData into API tab objects
-  convertFromSessionStoreClosedData(tab, window) {
+  get window() {
+    return this.tab.ownerGlobal;
+  }
+
+  get windowId() {
+    return windowTracker.getId(this.window);
+  }
+
+  static convertFromSessionStoreClosedData(extension, tab, window = null) {
     let result = {
       sessionId: String(tab.closedId),
       index: tab.pos ? tab.pos : 0,
-      windowId: WindowManager.getId(window),
+      windowId: window && windowTracker.getId(window),
       selected: false,
       highlighted: false,
       active: false,
       pinned: false,
       incognito: Boolean(tab.state && tab.state.isPrivate),
     };
 
-    if (this.hasTabPermission(tab)) {
+    if (extension.tabManager.hasTabPermission(tab)) {
       let entries = tab.state ? tab.state.entries : tab.entries;
       result.url = entries[0].url;
       result.title = entries[0].title;
       if (tab.image) {
         result.favIconUrl = tab.image;
       }
     }
 
     return result;
-  },
-
-  getTabs(window) {
-    return Array.from(window.gBrowser.tabs)
-                .filter(tab => !tab.closing)
-                .map(tab => this.convert(tab));
-  },
-};
-
-function getBrowserInfo(browser) {
-  if (!browser.ownerGlobal.gBrowser) {
-    // 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;
-
-    if (!browser) {
-      return {};
-    }
   }
-
-  let result = {};
-
-  let window = browser.ownerGlobal;
-  if (window.gBrowser) {
-    let tab = window.gBrowser.getTabForBrowser(browser);
-    if (tab) {
-      result.tabId = TabManager.getId(tab);
-    }
-
-    result.windowId = WindowManager.getId(window);
-  }
-
-  return result;
-}
-global.getBrowserInfo = getBrowserInfo;
-
-// Sends the tab and windowId upon request. This is primarily used to support
-// the synchronous `browser.extension.getViews` API.
-let onGetTabAndWindowId = {
-  receiveMessage({name, target, sync}) {
-    let result = getBrowserInfo(target);
-
-    if (result.tabId) {
-      if (sync) {
-        return result;
-      }
-      target.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", result);
-    }
-  },
-};
-/* eslint-disable mozilla/balanced-listeners */
-Services.mm.addMessageListener("Extension:GetTabAndWindowId", onGetTabAndWindowId);
-/* eslint-enable mozilla/balanced-listeners */
-
-
-// Manages global mappings between XUL tabs and extension tab IDs.
-global.TabManager = {
-  _tabs: new WeakMap(),
-  _nextId: 1,
-  _initialized: false,
-
-  // We begin listening for TabOpen and TabClose events once we've started
-  // assigning IDs to tabs, so that we can remap the IDs of tabs which are moved
-  // between windows.
-  initListener() {
-    if (this._initialized) {
-      return;
-    }
-
-    AllWindowEvents.addListener("TabOpen", this);
-    AllWindowEvents.addListener("TabClose", this);
-    WindowListManager.addOpenListener(this.handleWindowOpen.bind(this));
-
-    this._initialized = true;
-  },
-
-  handleEvent(event) {
-    if (event.type == "TabOpen") {
-      let {adoptedTab} = event.detail;
-      if (adoptedTab) {
-        // This tab is being created to adopt a tab from a different window.
-        // Copy the ID from the old tab to the new.
-        let tab = event.target;
-        this._tabs.set(tab, this.getId(adoptedTab));
-
-        tab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
-          windowId: WindowManager.getId(tab.ownerGlobal),
-        });
-      }
-    } else if (event.type == "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._tabs.set(adoptedBy, this.getId(event.target));
-
-        adoptedBy.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
-          windowId: WindowManager.getId(adoptedBy),
-        });
-      }
-    }
-  },
-
-  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.
-      let adoptedTab = window.arguments[0];
-
-      this._tabs.set(window.gBrowser.tabs[0], this.getId(adoptedTab));
-    }
-  },
-
-  getId(tab) {
-    if (this._tabs.has(tab)) {
-      return this._tabs.get(tab);
-    }
-    this.initListener();
-
-    let id = this._nextId++;
-    this._tabs.set(tab, id);
-    return id;
-  },
-
-  getBrowserId(browser) {
-    let gBrowser = browser.ownerGlobal.gBrowser;
-    // Some non-browser windows have gBrowser but not
-    // getTabForBrowser!
-    if (gBrowser && gBrowser.getTabForBrowser) {
-      let tab = gBrowser.getTabForBrowser(browser);
-      if (tab) {
-        return this.getId(tab);
-      }
-    }
-    return -1;
-  },
-
-  /**
-   * 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 {ExtensionContext} context
-   *        The context of the caller.
-   *        This value may be omitted if `default_` is not `undefined`.
-   * @param {*} default_
-   *        The value to return if no tab exists with the given ID.
-   * @returns {Element<tab>}
-   *        A XUL <tab> element.
-   */
-  getTab(tabId, context, default_ = undefined) {
-    // FIXME: Speed this up without leaking memory somehow.
-    for (let window of WindowListManager.browserWindows()) {
-      if (!window.gBrowser) {
-        continue;
-      }
-      for (let tab of window.gBrowser.tabs) {
-        if (this.getId(tab) == tabId) {
-          return tab;
-        }
-      }
-    }
-    if (default_ !== undefined) {
-      return default_;
-    }
-    throw new context.cloneScope.Error(`Invalid tab ID: ${tabId}`);
-  },
-
-  get activeTab() {
-    let window = WindowManager.topWindow;
-    if (window && window.gBrowser) {
-      return window.gBrowser.selectedTab;
-    }
-    return null;
-  },
-
-  getStatus(tab) {
-    return tab.getAttribute("busy") == "true" ? "loading" : "complete";
-  },
-
-  convert(extension, tab) {
-    return TabManager.for(extension).convert(tab);
-  },
-};
-
-// WeakMap[Extension -> ExtensionTabManager]
-let tabManagers = new WeakMap();
-
-// Returns the extension-specific tab manager for the given extension, or
-// creates one if it doesn't already exist.
-TabManager.for = function(extension) {
-  if (!tabManagers.has(extension)) {
-    tabManagers.set(extension, new ExtensionTabManager(extension));
-  }
-  return tabManagers.get(extension);
-};
-
-/* eslint-disable mozilla/balanced-listeners */
-extensions.on("shutdown", (type, extension) => {
-  tabManagers.delete(extension);
-});
-/* eslint-enable mozilla/balanced-listeners */
-
-function memoize(fn) {
-  let weakMap = new DefaultWeakMap(fn);
-  return weakMap.get.bind(weakMap);
 }
 
-// Manages mapping between XUL windows and extension window IDs.
-global.WindowManager = {
-  // Note: These must match the values in windows.json.
-  WINDOW_ID_NONE: -1,
-  WINDOW_ID_CURRENT: -2,
-
-  get topWindow() {
-    return Services.wm.getMostRecentWindow("navigator:browser");
-  },
-
-  windowType(window) {
-    // TODO: Make this work.
+class Window extends WindowBase {
+  updateGeometry(options) {
+    let {window} = this;
 
-    let {chromeFlags} = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIDocShell)
-                              .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIXULWindow);
-
-    if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
-      return "popup";
-    }
-
-    return "normal";
-  },
-
-  updateGeometry(window, options) {
     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);
     }
-  },
+  }
 
-  isBrowserPrivate: memoize(browser => {
-    return PrivateBrowsingUtils.isBrowserPrivate(browser);
-  }),
+  get focused() {
+    return this.window.document.hasFocus();
+  }
 
-  getId: memoize(window => {
-    if (window instanceof Ci.nsIInterfaceRequestor) {
-      return window.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
-    }
-    return null;
-  }),
+  get top() {
+    return this.window.screenY;
+  }
+
+  get left() {
+    return this.window.screenX;
+  }
+
+  get width() {
+    return this.window.outerWidth;
+  }
 
-  getWindow(id, context) {
-    if (id == this.WINDOW_ID_CURRENT) {
-      return currentWindow(context);
-    }
+  get height() {
+    return this.window.outerHeight;
+  }
+
+  get incognito() {
+    return PrivateBrowsingUtils.isWindowPrivate(this.window);
+  }
 
-    for (let window of WindowListManager.browserWindows(true)) {
-      if (this.getId(window) == id) {
-        return window;
-      }
-    }
-    return null;
-  },
+  get alwaysOnTop() {
+    return this.xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ;
+  }
 
-  getState(window) {
+  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;
-  },
+  }
 
-  setState(window, state) {
-    if (state != "fullscreen" && window.fullScreen) {
+  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;
 
@@ -1031,266 +555,106 @@ global.WindowManager = {
         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) {
+        if (window.windowState !== window.STATE_NORMAL) {
           window.restore();
         }
-        if (window.windowState != window.STATE_NORMAL) {
+        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}`);
     }
-  },
+  }
 
-  convert(extension, window, getInfo) {
-    let xulWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                          .getInterface(Ci.nsIDocShell)
-                          .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
-                          .getInterface(Ci.nsIXULWindow);
+  * getTabs() {
+    let {tabManager} = this.extension;
 
-    let result = {
-      id: this.getId(window),
-      focused: window.document.hasFocus(),
-      top: window.screenY,
-      left: window.screenX,
-      width: window.outerWidth,
-      height: window.outerHeight,
-      incognito: PrivateBrowsingUtils.isWindowPrivate(window),
-      type: this.windowType(window),
-      state: this.getState(window),
-      alwaysOnTop: xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ,
-    };
+    for (let tab of this.window.gBrowser.tabs) {
+      yield tabManager.getWrapper(tab);
+    }
+  }
 
-    if (getInfo && getInfo.populate) {
-      result.tabs = TabManager.for(extension).getTabs(window);
-    }
-
-    return result;
-  },
-
-  // Converts windows returned from SessionStore.getClosedWindowData
-  // into API window objects
-  convertFromSessionStoreClosedData(window, extension) {
+  static convertFromSessionStoreClosedData(extension, window) {
     let result = {
       sessionId: String(window.closedId),
       focused: false,
       incognito: false,
       type: "normal", // this is always "normal" for a closed window
+      // Surely this does not actually work?
       state: this.getState(window),
       alwaysOnTop: false,
     };
 
     if (window.tabs.length) {
-      result.tabs = [];
-      window.tabs.forEach((tab, index) => {
-        result.tabs.push(TabManager.for(extension).convertFromSessionStoreClosedData(tab, window, index));
+      result.tabs = window.tabs.map(tab => {
+        return Tab.convertFromSessionStoreClosedData(extension, tab);
       });
     }
 
     return result;
-  },
-};
-
-// Manages listeners for window opening and closing. A window is
-// considered open when the "load" event fires on it. A window is
-// closed when a "domwindowclosed" notification fires for it.
-global.WindowListManager = {
-  _openListeners: new Set(),
-  _closeListeners: new Set(),
+  }
+}
 
-  // Returns an iterator for all browser windows. Unless |includeIncomplete| is
-  // true, only fully-loaded windows are returned.
-  * browserWindows(includeIncomplete = false) {
-    // The window type parameter is only available once the window's document
-    // element has been created. This means that, when looking for incomplete
-    // browser windows, we need to ignore the type entirely for windows which
-    // haven't finished loading, since we would otherwise skip browser windows
-    // in their early loading stages.
-    // This is particularly important given that the "domwindowcreated" event
-    // fires for browser windows when they're in that in-between state, and just
-    // before we register our own "domwindowcreated" listener.
-
-    let e = Services.wm.getEnumerator("");
-    while (e.hasMoreElements()) {
-      let window = e.getNext();
-
-      let ok = includeIncomplete;
-      if (window.document.readyState == "complete") {
-        ok = window.document.documentElement.getAttribute("windowtype") == "navigator:browser";
-      }
-
-      if (ok) {
-        yield window;
-      }
-    }
-  },
-
-  addOpenListener(listener) {
-    if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
-      Services.ww.registerNotification(this);
-    }
-    this._openListeners.add(listener);
+Object.assign(global, {Tab, Window});
 
-    for (let window of this.browserWindows(true)) {
-      if (window.document.readyState != "complete") {
-        window.addEventListener("load", this);
-      }
-    }
-  },
-
-  removeOpenListener(listener) {
-    this._openListeners.delete(listener);
-    if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
-      Services.ww.unregisterNotification(this);
-    }
-  },
-
-  addCloseListener(listener) {
-    if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
-      Services.ww.registerNotification(this);
-    }
-    this._closeListeners.add(listener);
-  },
+class TabManager extends TabManagerBase {
+  get(tabId, default_ = undefined) {
+    let tab = tabTracker.getTab(tabId, default_);
 
-  removeCloseListener(listener) {
-    this._closeListeners.delete(listener);
-    if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
-      Services.ww.unregisterNotification(this);
-    }
-  },
-
-  handleEvent(event) {
-    event.currentTarget.removeEventListener(event.type, this);
-    let window = event.target.defaultView;
-    if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
-      return;
-    }
-
-    for (let listener of this._openListeners) {
-      listener(window);
-    }
-  },
-
-  observe(window, topic, data) {
-    if (topic == "domwindowclosed") {
-      if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
-        return;
-      }
-
-      window.removeEventListener("load", this);
-      for (let listener of this._closeListeners) {
-        listener(window);
-      }
-    } else {
-      window.addEventListener("load", this);
+    if (tab) {
+      return this.getWrapper(tab);
     }
-  },
-};
-
-// Provides a facility to listen for DOM events across all XUL windows.
-global.AllWindowEvents = {
-  _listeners: new Map(),
+    return default_;
+  }
 
-  // If |type| is a normal event type, invoke |listener| each time
-  // that event fires in any open window. If |type| is "progress", add
-  // a web progress listener that covers all open windows.
-  addListener(type, listener) {
-    if (type == "domwindowopened") {
-      return WindowListManager.addOpenListener(listener);
-    } else if (type == "domwindowclosed") {
-      return WindowListManager.addCloseListener(listener);
-    }
-
-    if (this._listeners.size == 0) {
-      WindowListManager.addOpenListener(this.openListener);
-    }
+  addActiveTabPermission(tab = tabTracker.activeTab) {
+    return super.addActiveTabPermission(tab);
+  }
 
-    if (!this._listeners.has(type)) {
-      this._listeners.set(type, new Set());
-    }
-    let list = this._listeners.get(type);
-    list.add(listener);
-
-    // Register listener on all existing windows.
-    for (let window of WindowListManager.browserWindows()) {
-      this.addWindowListener(window, type, listener);
-    }
-  },
+  revokeActiveTabPermission(tab = tabTracker.activeTab) {
+    return super.revokeActiveTabPermission(tab);
+  }
 
-  removeListener(eventType, listener) {
-    if (eventType == "domwindowopened") {
-      return WindowListManager.removeOpenListener(listener);
-    } else if (eventType == "domwindowclosed") {
-      return WindowListManager.removeCloseListener(listener);
-    }
+  wrapTab(tab) {
+    return new Tab(this.extension, tab, tabTracker.getId(tab));
+  }
+}
 
-    let listeners = this._listeners.get(eventType);
-    listeners.delete(listener);
-    if (listeners.size == 0) {
-      this._listeners.delete(eventType);
-      if (this._listeners.size == 0) {
-        WindowListManager.removeOpenListener(this.openListener);
-      }
-    }
+class WindowManager extends WindowManagerBase {
+  get(windowId, context) {
+    let window = windowTracker.getWindow(windowId, context);
 
-    // Unregister listener from all existing windows.
-    let useCapture = eventType === "focus" || eventType === "blur";
-    for (let window of WindowListManager.browserWindows()) {
-      if (eventType == "progress") {
-        window.gBrowser.removeTabsProgressListener(listener);
-      } else {
-        window.removeEventListener(eventType, listener, useCapture);
-      }
-    }
-  },
+    return this.getWrapper(window);
+  }
 
-  /* eslint-disable mozilla/balanced-listeners */
-  addWindowListener(window, eventType, listener) {
-    let useCapture = eventType === "focus" || eventType === "blur";
-
-    if (eventType == "progress") {
-      window.gBrowser.addTabsProgressListener(listener);
-    } else {
-      window.addEventListener(eventType, listener, useCapture);
+  * getAll() {
+    for (let window of windowTracker.browserWindows()) {
+      yield this.getWrapper(window);
     }
-  },
-  /* eslint-enable mozilla/balanced-listeners */
+  }
 
-  // Runs whenever the "load" event fires for a new window.
-  openListener(window) {
-    for (let [eventType, listeners] of AllWindowEvents._listeners) {
-      for (let listener of listeners) {
-        this.addWindowListener(window, eventType, listener);
-      }
-    }
-  },
-};
+  wrapWindow(window) {
+    return new Window(this.extension, window, windowTracker.getId(window));
+  }
+}
 
-AllWindowEvents.openListener = AllWindowEvents.openListener.bind(AllWindowEvents);
 
-// Subclass of SingletonEventManager where we just need to call
-// add/removeEventListener on each XUL window.
-global.WindowEventManager = class extends SingletonEventManager {
-  constructor(context, name, event, listener) {
-    super(context, name, fire => {
-      let listener2 = (...args) => listener(fire, ...args);
-      AllWindowEvents.addListener(event, listener2);
-      return () => {
-        AllWindowEvents.removeListener(event, listener2);
-      };
-    });
-  }
-};
+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
@@ -17,73 +17,76 @@ var {
 } = ExtensionUtils;
 
 function onXULFrameLoaderCreated({target}) {
   target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
 }
 
 extensions.registerSchemaAPI("windows", "addon_parent", context => {
   let {extension} = context;
+
+  const {windowManager} = extension;
+
   return {
     windows: {
       onCreated:
       new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
-        fire.async(WindowManager.convert(extension, window));
+        fire.async(windowManager.convert(window));
       }).api(),
 
       onRemoved:
       new WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
-        fire.async(WindowManager.getId(window));
+        fire.async(windowTracker.getId(window));
       }).api(),
 
       onFocusChanged: new SingletonEventManager(context, "windows.onFocusChanged", fire => {
         // Keep track of the last windowId used to fire an onFocusChanged event
         let lastOnFocusChangedWindowId;
 
         let listener = event => {
           // Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
           // event when switching focus between two Firefox windows.
           Promise.resolve().then(() => {
             let window = Services.focus.activeWindow;
-            let windowId = window ? WindowManager.getId(window) : WindowManager.WINDOW_ID_NONE;
+            let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE;
             if (windowId !== lastOnFocusChangedWindowId) {
               fire.async(windowId);
               lastOnFocusChangedWindowId = windowId;
             }
           });
         };
-        AllWindowEvents.addListener("focus", listener);
-        AllWindowEvents.addListener("blur", listener);
+        windowTracker.addListener("focus", listener);
+        windowTracker.addListener("blur", listener);
         return () => {
-          AllWindowEvents.removeListener("focus", listener);
-          AllWindowEvents.removeListener("blur", listener);
+          windowTracker.removeListener("focus", listener);
+          windowTracker.removeListener("blur", listener);
         };
       }).api(),
 
       get: function(windowId, getInfo) {
-        let window = WindowManager.getWindow(windowId, context);
+        let window = windowTracker.getWindow(windowId, context);
         if (!window) {
           return Promise.reject({message: `Invalid window ID: ${windowId}`});
         }
-        return Promise.resolve(WindowManager.convert(extension, window, getInfo));
+        return Promise.resolve(windowManager.convert(window, getInfo));
       },
 
       getCurrent: function(getInfo) {
-        let window = currentWindow(context);
-        return Promise.resolve(WindowManager.convert(extension, window, getInfo));
+        let window = context.currentWindow || windowTracker.topWindow;
+        return Promise.resolve(windowManager.convert(window, getInfo));
       },
 
       getLastFocused: function(getInfo) {
-        let window = WindowManager.topWindow;
-        return Promise.resolve(WindowManager.convert(extension, window, getInfo));
+        let window = windowTracker.topWindow;
+        return Promise.resolve(windowManager.convert(window, getInfo));
       },
 
       getAll: function(getInfo) {
-        let windows = Array.from(WindowListManager.browserWindows(),
-                                 window => WindowManager.convert(extension, window, getInfo));
+        let windows = Array.from(windowManager.getAll(), win => win.convert(getInfo));
+
         return Promise.resolve(windows);
       },
 
       create: function(createData) {
         let needResize = (createData.left !== null || createData.top !== null ||
                           createData.width !== null || createData.height !== null);
 
         if (needResize) {
@@ -105,17 +108,17 @@ extensions.registerSchemaAPI("windows", 
           if (createData.url !== null) {
             return Promise.reject({message: "`tabId` may not be used in conjunction with `url`"});
           }
 
           if (createData.allowScriptsToClose) {
             return Promise.reject({message: "`tabId` may not be used in conjunction with `allowScriptsToClose`"});
           }
 
-          let tab = TabManager.getTab(createData.tabId, context);
+          let tab = tabTracker.getTab(createData.tabId);
 
           // Private browsing tabs can only be moved to private browsing
           // windows.
           let incognito = PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser);
           if (createData.incognito !== null && createData.incognito != incognito) {
             return Promise.reject({message: "`incognito` property must match the incognito state of tab"});
           }
           createData.incognito = incognito;
@@ -155,79 +158,80 @@ extensions.registerSchemaAPI("windows", 
         let {allowScriptsToClose, url} = createData;
         if (allowScriptsToClose === null) {
           allowScriptsToClose = typeof url === "string" && url.startsWith("moz-extension://");
         }
 
         let window = Services.ww.openWindow(null, "chrome://browser/content/browser.xul", "_blank",
                                             features.join(","), args);
 
-        WindowManager.updateGeometry(window, createData);
+        let win = windowManager.getWrapper(window);
+        win.updateGeometry(createData);
 
         // TODO: focused, type
 
         return new Promise(resolve => {
           window.addEventListener("load", function() {
             if (["maximized", "normal"].includes(createData.state)) {
               window.document.documentElement.setAttribute("sizemode", createData.state);
             }
             resolve(promiseObserved("browser-delayed-startup-finished", win => win == window));
           }, {once: true});
         }).then(() => {
           // Some states only work after delayed-startup-finished
           if (["minimized", "fullscreen", "docked"].includes(createData.state)) {
-            WindowManager.setState(window, createData.state);
+            win.state = createData.state;
           }
           if (allowScriptsToClose) {
             for (let {linkedBrowser} of window.gBrowser.tabs) {
               onXULFrameLoaderCreated({target: linkedBrowser});
               linkedBrowser.addEventListener( // eslint-disable-line mozilla/balanced-listeners
                                              "XULFrameLoaderCreated", onXULFrameLoaderCreated);
             }
           }
-          return WindowManager.convert(extension, window, {populate: true});
+          return win.convert({populate: true});
         });
       },
 
       update: function(windowId, updateInfo) {
         if (updateInfo.state !== null && updateInfo.state != "normal") {
           if (updateInfo.left !== null || updateInfo.top !== null ||
               updateInfo.width !== null || updateInfo.height !== null) {
             return Promise.reject({message: `"state": "${updateInfo.state}" may not be combined with "left", "top", "width", or "height"`});
           }
         }
 
-        let window = WindowManager.getWindow(windowId, context);
+        let win = windowManager.get(windowId, context);
         if (updateInfo.focused) {
-          Services.focus.activeWindow = window;
+          Services.focus.activeWindow = win.window;
         }
 
         if (updateInfo.state !== null) {
-          WindowManager.setState(window, updateInfo.state);
+          win.state = updateInfo.state;
         }
 
         if (updateInfo.drawAttention) {
           // Bug 1257497 - Firefox can't cancel attention actions.
-          window.getAttention();
+          win.window.getAttention();
         }
 
-        WindowManager.updateGeometry(window, updateInfo);
+        win.updateGeometry(updateInfo);
 
         // TODO: All the other properties, focused=false...
 
-        return Promise.resolve(WindowManager.convert(extension, window));
+        return Promise.resolve(win.convert());
       },
 
       remove: function(windowId) {
-        let window = WindowManager.getWindow(windowId, context);
+        let window = windowTracker.getWindow(windowId, context);
         window.close();
 
         return new Promise(resolve => {
           let listener = () => {
-            AllWindowEvents.removeListener("domwindowclosed", listener);
+            windowTracker.removeListener("domwindowclosed", listener);
             resolve();
           };
-          AllWindowEvents.addListener("domwindowclosed", listener);
+          windowTracker.addListener("domwindowclosed", listener);
         });
       },
     },
   };
 });
--- a/browser/components/extensions/moz.build
+++ b/browser/components/extensions/moz.build
@@ -5,16 +5,20 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 JAR_MANIFESTS += ['jar.mn']
 
 EXTRA_COMPONENTS += [
     'extensions-browser.manifest',
 ]
 
+EXTRA_JS_MODULES += [
+    'ExtensionPopups.jsm',
+]
+
 DIRS += ['schemas']
 
 BROWSER_CHROME_MANIFESTS += [
     'test/browser/browser-remote.ini',
     'test/browser/browser.ini',
 ]
 
 MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
--- a/browser/components/extensions/test/browser/browser_ext_currentWindow.js
+++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
@@ -85,20 +85,20 @@ add_task(function* () {
       "popup.js": genericChecker,
     },
 
     background: genericChecker,
   });
 
   yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
 
-  let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
-  let winId1 = WindowManager.getId(win1);
-  let winId2 = WindowManager.getId(win2);
+  let winId1 = windowTracker.getId(win1);
+  let winId2 = windowTracker.getId(win2);
 
   function* checkWindow(kind, winId, name) {
     extension.sendMessage(kind + "-check-current1");
     is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 1) [${kind}]`);
     extension.sendMessage(kind + "-check-current2");
     is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 2) [${kind}]`);
     extension.sendMessage(kind + "-check-current3");
     is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 3) [${kind}]`);
--- a/browser/components/extensions/test/browser/browser_ext_getViews.js
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -99,20 +99,20 @@ add_task(function* () {
 
     background: genericChecker,
   });
 
   yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
 
   info("started");
 
-  let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
-  let winId1 = WindowManager.getId(win1);
-  let winId2 = WindowManager.getId(win2);
+  let winId1 = windowTracker.getId(win1);
+  let winId2 = windowTracker.getId(win2);
 
   function* openTab(winId) {
     extension.sendMessage("background-open-tab", winId);
     yield extension.awaitMessage("tab-ready");
   }
 
   function* checkViews(kind, tabCount, popupCount, kindCount, windowId = undefined, windowCount = 0) {
     extension.sendMessage(kind + "-check-views", windowId);
--- a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js
@@ -27,18 +27,18 @@ add_task(function* test_sessions_get_rec
     background,
   });
 
   // Open a private browsing window.
   let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
 
   yield extension.startup();
 
-  let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
-  let privateWinId = WindowManager.getId(privateWin);
+  let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  let privateWinId = windowTracker.getId(privateWin);
 
   extension.sendMessage("check-sessions");
   let recentlyClosed = yield extension.awaitMessage("recentlyClosed");
   recordInitialTimestamps(recentlyClosed.map(item => item.lastModified));
 
   // Open and close two tabs in the private window
   let tab = yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, "http://example.com");
   yield BrowserTestUtils.removeTab(tab);
--- a/browser/components/extensions/test/browser/browser_ext_sessions_restore.js
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_restore.js
@@ -50,20 +50,20 @@ add_task(function* test_sessions_restore
 
   function* assertNotificationCount(expected) {
     let notificationCount = yield extension.awaitMessage("notificationCount");
     is(notificationCount, expected, "the expected number of notifications was fired");
   }
 
   yield extension.startup();
 
-  let {Management: {global: {WindowManager, TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  let {Management: {global: {windowTracker, tabTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
   function checkLocalTab(tab, expectedUrl) {
-    let realTab = TabManager.getTab(tab.id);
+    let realTab = tabTracker.getTab(tab.id);
     let tabState = JSON.parse(SessionStore.getTabState(realTab));
     is(tabState.entries[0].url, expectedUrl, "restored tab has the expected url");
   }
 
   yield extension.awaitMessage("ready");
 
   let win = yield BrowserTestUtils.openNewBrowserWindow();
   yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "about:config");
@@ -87,17 +87,17 @@ add_task(function* test_sessions_restore
 
   is(restored.length, 1, "restore returned the expected number of sessions");
   is(restored[0].window.tabs.length, 3, "restore returned a window with the expected number of tabs");
   checkLocalTab(restored[0].window.tabs[0], "about:config");
   checkLocalTab(restored[0].window.tabs[1], "about:robots");
   checkLocalTab(restored[0].window.tabs[2], "about:mozilla");
 
   // Close the window again.
-  let window = WindowManager.getWindow(restored[0].window.id);
+  let window = windowTracker.getWindow(restored[0].window.id);
   yield BrowserTestUtils.closeWindow(window);
   yield assertNotificationCount(3);
 
   // Restore the window using the sessionId.
   extension.sendMessage("check-sessions");
   recentlyClosed = yield extension.awaitMessage("recentlyClosed");
   extension.sendMessage("restore", recentlyClosed[0].window.sessionId);
   yield assertNotificationCount(4);
@@ -105,17 +105,17 @@ add_task(function* test_sessions_restore
 
   is(restored.length, 1, "restore returned the expected number of sessions");
   is(restored[0].window.tabs.length, 3, "restore returned a window with the expected number of tabs");
   checkLocalTab(restored[0].window.tabs[0], "about:config");
   checkLocalTab(restored[0].window.tabs[1], "about:robots");
   checkLocalTab(restored[0].window.tabs[2], "about:mozilla");
 
   // Close the window again.
-  window = WindowManager.getWindow(restored[0].window.id);
+  window = windowTracker.getWindow(restored[0].window.id);
   yield BrowserTestUtils.closeWindow(window);
   // notificationCount = yield extension.awaitMessage("notificationCount");
   yield assertNotificationCount(5);
 
   // Open and close a tab.
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
   yield TabStateFlusher.flush(tab.linkedBrowser);
   yield BrowserTestUtils.removeTab(tab);
@@ -127,34 +127,34 @@ add_task(function* test_sessions_restore
   restored = yield extension.awaitMessage("restored");
 
   is(restored.length, 1, "restore returned the expected number of sessions");
   tab = restored[0].tab;
   ok(tab, "restore returned a tab");
   checkLocalTab(tab, "about:robots");
 
   // Close the tab again.
-  let realTab = TabManager.getTab(tab.id);
+  let realTab = tabTracker.getTab(tab.id);
   yield BrowserTestUtils.removeTab(realTab);
   yield assertNotificationCount(8);
 
   // Restore the tab using the sessionId.
   extension.sendMessage("check-sessions");
   recentlyClosed = yield extension.awaitMessage("recentlyClosed");
   extension.sendMessage("restore", recentlyClosed[0].tab.sessionId);
   yield assertNotificationCount(9);
   restored = yield extension.awaitMessage("restored");
 
   is(restored.length, 1, "restore returned the expected number of sessions");
   tab = restored[0].tab;
   ok(tab, "restore returned a tab");
   checkLocalTab(tab, "about:robots");
 
   // Close the tab again.
-  realTab = TabManager.getTab(tab.id);
+  realTab = tabTracker.getTab(tab.id);
   yield BrowserTestUtils.removeTab(realTab);
   yield assertNotificationCount(10);
 
   // Try to restore something with an invalid sessionId.
   extension.sendMessage("restore-reject");
   restored = yield extension.awaitMessage("restore-rejected");
 
   yield extension.unload();
--- a/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
@@ -156,19 +156,19 @@ add_task(function* () {
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
   });
 
   extension.onMessage("change-tab", (tabId, attr, on) => {
-    let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+    let {Management: {global: {tabTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
-    let tab = TabManager.getTab(tabId);
+    let tab = tabTracker.getTab(tabId);
 
     if (attr == "muted") {
       // Ideally we'd simulate a click on the tab audio icon for this, but the
       // handler relies on CSS :hover states, which are complicated and fragile
       // to simulate.
       if (tab.muted != on) {
         tab.toggleMuteAudio();
       }
@@ -179,17 +179,17 @@ add_task(function* () {
       } else {
         browser.audioPlaybackStopped();
       }
     } else if (attr == "duplicate") {
       // This is a bit of a hack. It won't be necessary once we have
       // `tabs.duplicate`.
       let newTab = gBrowser.duplicateTab(tab);
       BrowserTestUtils.waitForEvent(newTab, "SSTabRestored", () => true).then(() => {
-        extension.sendMessage("change-tab-done", tabId, TabManager.getId(newTab));
+        extension.sendMessage("change-tab-done", tabId, tabTracker.getId(newTab));
       });
       return;
     }
 
     extension.sendMessage("change-tab-done", tabId);
   });
 
   yield extension.startup();
--- a/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
@@ -89,24 +89,24 @@ add_task(function* testDuplicateTabLazil
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
   });
 
   extension.onMessage("duplicate-tab", tabId => {
-    let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+    let {Management: {global: {tabTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
-    let tab = TabManager.getTab(tabId);
+    let tab = tabTracker.getTab(tabId);
     // This is a bit of a hack to load a tab in the background.
     let newTab = gBrowser.duplicateTab(tab, true);
 
     BrowserTestUtils.waitForEvent(newTab, "SSTabRestored", () => true).then(() => {
-      extension.sendMessage("duplicate-tab-done", TabManager.getId(newTab));
+      extension.sendMessage("duplicate-tab-done", tabTracker.getId(newTab));
     });
   });
 
   yield extension.startup();
   yield extension.awaitFinish("tabs.hasCorrectTabTitle");
   yield extension.unload();
 });
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_no_create.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_no_create.js
@@ -16,17 +16,17 @@ add_task(function* testExecuteScriptAtOn
 
   function background() {
     // Using variables to prevent listeners from running more than once, instead
     // of removing the listener. This is to minimize any IPC, since the bug that
     // is being tested is sensitive to timing.
     let ignore = false;
     let url;
     browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
-      if (changeInfo.status === "loading" && tab.url === url && !ignore) {
+      if (url && changeInfo.status === "loading" && tab.url === url && !ignore) {
         ignore = true;
         browser.tabs.executeScript(tabId, {
           code: "document.URL",
         }).then(results => {
           browser.test.assertEq(url, results[0], "Content script should run");
           browser.test.notifyPass("executeScript-at-onUpdated");
         }, error => {
           browser.test.fail(`Unexpected error: ${error} :: ${error.stack}`);
--- a/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
@@ -184,24 +184,24 @@ add_task(function* () {
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
   });
 
   extension.onMessage("msg", (id, msg, ...args) => {
-    let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+    let {Management: {global: {tabTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
     let resp;
     if (msg == "get-zoom") {
-      let tab = TabManager.getTab(args[0]);
+      let tab = tabTracker.getTab(args[0]);
       resp = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
     } else if (msg == "set-zoom") {
-      let tab = TabManager.getTab(args[0]);
+      let tab = tabTracker.getTab(args[0]);
       ZoomManager.setZoomForBrowser(tab.linkedBrowser);
     } else if (msg == "enlarge") {
       FullZoom.enlarge();
     } else if (msg == "site-specific") {
       if (args[0] == null) {
         SpecialPowers.clearUserPref(SITE_SPECIFIC_PREF);
       } else {
         SpecialPowers.setBoolPref(SITE_SPECIFIC_PREF, args[0]);
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
@@ -1,23 +1,23 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testWebNavigationGetNonExistentTab() {
   let extension = ExtensionTestUtils.loadExtension({
     background: async function() {
-      // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
+      // There is no "tabId = 0" because the id assigned by tabTracker (defined in ext-utils.js)
       // starts from 1.
       await browser.test.assertRejects(
         browser.webNavigation.getAllFrames({tabId: 0}),
         "Invalid tab ID: 0",
         "getAllFrames rejected Promise should pass the expected error");
 
-      // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
+      // There is no "tabId = 0" because the id assigned by tabTracker (defined in ext-utils.js)
       // starts from 1, processId is currently marked as optional and it is ignored.
       await browser.test.assertRejects(
         browser.webNavigation.getFrame({tabId: 0, frameId: 15, processId: 20}),
         "Invalid tab ID: 0",
         "getFrame rejected Promise should pass the expected error");
 
       browser.test.sendMessage("getNonExistentTab.done");
     },
--- a/browser/components/extensions/test/browser/browser_ext_windows_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_events.js
@@ -56,20 +56,20 @@ add_task(function* testWindowsEvents() {
 
   let extension = ExtensionTestUtils.loadExtension({
     background: `(${background})()`,
   });
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
 
-  let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
   let currentWindow = window;
-  let currentWindowId = WindowManager.getId(currentWindow);
+  let currentWindowId = windowTracker.getId(currentWindow);
   info(`Current window ID: ${currentWindowId}`);
 
   info(`Create browser window 1`);
   let win1 = yield BrowserTestUtils.openNewBrowserWindow();
   let win1Id = yield extension.awaitMessage("window-created");
   info(`Window 1 ID: ${win1Id}`);
 
   // This shouldn't be necessary, but tests intermittently fail, so let's give
--- a/build/valgrind/x86_64-redhat-linux-gnu.sup
+++ b/build/valgrind/x86_64-redhat-linux-gnu.sup
@@ -203,16 +203,47 @@
    Bug 1288618 comments 119 through 127 part 2
    Memcheck:Cond
    fun:__get_cpuid
    fun:cpuid
    fun:_ZN6SkOptsL4initEv
    fun:sk_once_no_arg_adaptor
 }
 
+# More stuff to do with CPUID and Skia.  Apparently we could get rid of
+# these if we could patch our in-tree Skia, but that's not favoured.
+#
+# Conditional jump or move depends on uninitialised value(s)
+#    at 0xFDD1D97: SkCpu::CacheRuntimeFeatures()
+#    by 0xFE8A66E: SkGraphics::Init()
+#    by 0xE757308: gfxPlatform::Init()
+#    by 0xE75772C: gfxPlatform::GetPlatform()
+{
+   Skia and CPUID, Jan 2017, #1
+   Memcheck:Cond
+   fun:_ZN5SkCpu20CacheRuntimeFeaturesEv
+   fun:_ZN10SkGraphics4InitEv
+   fun:_ZN11gfxPlatform4InitEv
+   fun:_ZN11gfxPlatform11GetPlatformEv
+}
+
+# Conditional jump or move depends on uninitialised value(s)
+#    at 0xFD5B218: SkOpts::Init()
+#    by 0xE757308: gfxPlatform::Init()
+#    by 0xE75772C: gfxPlatform::GetPlatform()
+#    by 0xF1A3691: mozilla::dom::ContentProcess::Init()
+{
+   Skia and CPUID, Jan 2017, #2
+   Memcheck:Cond
+   fun:_ZN6SkOpts4InitEv
+   fun:_ZN11gfxPlatform4InitEv
+   fun:_ZN11gfxPlatform11GetPlatformEv
+   fun:_ZN7mozilla3dom14ContentProcess4InitEv
+}
+
 
 ###################################################
 #  For valgrind-mochitest ("tc-M-V [tier 2]") runs on taskcluster.
 #  See bug 1248365.
 #  These are specific to Ubuntu 12.04.5, 64-bit.
 ###################################################
 
 
@@ -427,16 +458,30 @@
    Bug 1248365: FastConvertYUVToRGB32Row-1
    Memcheck:Value8
    fun:FastConvertYUVToRGB32Row
    fun:_ZN7mozilla3gfx19ConvertYCbCrToRGB32*
    fun:_ZN7mozilla3gfx17ConvertYCbCrToRGB*
    fun:_ZN7mozilla6layers16PlanarYCbCrImage18GetAsSourceSurface*
 }
 
+# Similarly:
+# Conditional jump or move depends on uninitialised value(s)
+#    at 0xFDAD1D1: sse41::blit_row_s32a_opaque(unsigned int*, unsigned int con
+#    by 0xFD60FA9: Sprite_D32_S32::blitRect(int, int, int, int) (in /home/work
+#    by 0xFEB9E0D: SkScan::FillIRect(SkIRect const&, SkRegion const*, SkBlitte
+#    by 0xFEBDDF3: SkScan::FillIRect(SkIRect const&, SkRasterClip const&, SkBl
+{
+   SKIA and SSE4, Jan 2017
+   Memcheck:Cond
+   fun:_ZN5sse41L20blit_row_s32a_opaque*
+   fun:_ZN14Sprite_D32_S328blitRect*
+   fun:_ZN6SkScan9FillIRect*
+   fun:_ZN6SkScan9FillIRect*
+}
 
 # This is probably a V false positive, due to an insufficiently accurate
 # description of the ioctl(SIOCETHTOOL) behavior.
 # Syscall param ioctl(SIOCETHTOOL) points to uninitialised byte(s)
 #    at 0x5D5CBF7: ioctl (syscall-template.S:82)
 #    by 0xF58EB67: nr_stun_get_addrs (in /home/worker/workspace/build/applica
 #    by 0xF594791: nr_stun_find_local_addresses (in /home/worker/workspace/bu
 #    by 0xF58A237: nr_ice_get_local_addresses (in /home/worker/workspace/buil
@@ -483,19 +528,19 @@
    obj:/*/libpthread*.so*
    obj:/*/libfontconfig.so*
    ...
    obj:/*/libfontconfig.so*
    fun:FcConfigAppFontAddDir
 }
 
 
-# There's nothing we can do about this short of throwing in
+# There's nothing we can do about these short of throwing in
 # --show-mismatched-frees=no, but that's a bit drastic, so for now,
-# just suppress it.
+# just suppress them.  A typical error is:
 #
 # Mismatched free() / delete / delete []
 #    at 0x4C2BE97: free (vg_replace_malloc.c:530)
 #    by 0xFCD09EC: ots::ots_post_free(ots::Font*) (in /home/worker/workspace/
 #    by 0xFCC600E: ots::Font::~Font() (in /home/worker/workspace/build/applic
 #    by 0xFCCBFA5: ots::OTSContext::Process(ots::OTSStream*, unsigned char co
 #    by 0xE7D7C8D: gfxUserFontEntry::SanitizeOpenTypeData(unsigned char const
 #    by 0xE7E371D: gfxUserFontEntry::LoadPlatformFont(unsigned char const*, u
@@ -513,8 +558,58 @@
 {
    Bug 1248365: ots::Font::~Font()-1
    Memcheck:Free
    fun:free
    fun:_ZN3ots13ots_post_free*
    fun:_ZN3ots4FontD1Ev
    fun:_ZN3ots10OTSContext7Process*
 }
+
+# and various similar:
+{
+   ots mismatched frees, Jan 2017, #1
+   Memcheck:Free
+   fun:_ZdlPv
+   fun:_ZN3ots14ots_glyf_parse*
+   fun:_ZN12_GLOBAL__N_114ProcessGenericEPN3ots12OpenTypeFile*
+   fun:_ZN12_GLOBAL__N_110ProcessTTFEPN3ots12OpenTypeFile*
+}
+{
+   ots mismatched frees, Jan 2017, #2
+   Memcheck:Free
+   fun:_ZdlPv
+   fun:_ZN3ots13ots_cff_parse*
+   fun:_ZN12_GLOBAL__N_114ProcessGenericEPN3ots12OpenTypeFile*
+   fun:_ZN3ots10OTSContext7ProcessEPNS_9OTSStream*
+}
+{
+   ots mismatched frees, Jan 2017, #3
+   Memcheck:Free
+   fun:_ZdlPv
+   fun:_ZN3ots13ots_cff_parse*
+   fun:_ZN12_GLOBAL__N_114ProcessGenericEPN3ots12OpenTypeFile*
+   fun:_ZN12_GLOBAL__N_110Process*
+}
+{
+   ots mismatched frees, Jan 2017, #4
+   Memcheck:Free
+   fun:_ZdlPv
+   fun:_ZN3ots12ots_cff_free*
+   fun:_ZN3ots4FontD1Ev*
+   fun:_ZN3ots10OTSContext7Process*
+}
+{
+   ots mismatched frees, Jan 2017, #5
+   Memcheck:Free
+   fun:_ZdlPv
+   fun:_ZN3ots13ots_loca_free*
+   fun:_ZN3ots4FontD1Ev*
+   fun:_ZN3ots10OTSContext7Process*
+}
+{
+   ots mismatched frees, Jan 2017, #6
+   Memcheck:Free
+   fun:_ZdlPv
+   fun:_ZN3ots14ots_glyf_parse*
+   fun:_ZN12_GLOBAL__N_114ProcessGenericEPN3ots12OpenTypeFile*
+   fun:_ZN3ots10OTSContext7ProcessEPNS_9OTSStream*
+}
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -517,29 +517,16 @@ nsScriptSecurityManager::CheckLoadURIFro
     nsIPrincipal* principal = nsContentUtils::SubjectPrincipal();
     nsresult rv = CheckLoadURIWithPrincipal(principal, aURI,
                                             nsIScriptSecurityManager::STANDARD);
     if (NS_SUCCEEDED(rv)) {
         // OK to load
         return NS_OK;
     }
 
-    // See if we're attempting to load a file: URI. If so, let a
-    // UniversalXPConnect capability trump the above check.
-    bool isFile = false;
-    bool isRes = false;
-    if (NS_FAILED(aURI->SchemeIs("file", &isFile)) ||
-        NS_FAILED(aURI->SchemeIs("resource", &isRes)))
-        return NS_ERROR_FAILURE;
-    if (isFile || isRes)
-    {
-        if (nsContentUtils::IsCallerChrome())
-            return NS_OK;
-    }
-
     // Report error.
     nsAutoCString spec;
     if (NS_FAILED(aURI->GetAsciiSpec(spec)))
         return NS_ERROR_FAILURE;
     nsAutoCString msg("Access to '");
     msg.Append(spec);
     msg.AppendLiteral("' from script denied");
     SetPendingExceptionASCII(cx, msg.get());
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -178,19 +178,19 @@ function CssComputedView(inspector, docu
   this.searchClearButton = doc.getElementById("computedview-searchinput-clear");
   this.includeBrowserStylesCheckbox =
     doc.getElementById("browser-style-checkbox");
 
   this.shortcuts = new KeyShortcuts({ window: this.styleWindow });
   this._onShortcut = this._onShortcut.bind(this);
   this.shortcuts.on("CmdOrCtrl+F", this._onShortcut);
   this.shortcuts.on("Escape", this._onShortcut);
+  this.styleDocument.addEventListener("copy", this._onCopy);
   this.styleDocument.addEventListener("mousedown", this.focusWindow);
   this.element.addEventListener("click", this._onClick);
-  this.element.addEventListener("copy", this._onCopy);
   this.element.addEventListener("contextmenu", this._onContextMenu);
   this.searchField.addEventListener("input", this._onFilterStyles);
   this.searchField.addEventListener("contextmenu", this.inspector.onTextBoxContextMenu);
   this.searchClearButton.addEventListener("click", this._onClearSearch);
   this.includeBrowserStylesCheckbox.addEventListener("input",
     this._onIncludeBrowserStyles);
 
   this.searchClearButton.hidden = true;
@@ -682,42 +682,57 @@ CssComputedView.prototype = {
 
   /**
    * Callback for copy event. Copy selected text.
    *
    * @param {Event} event
    *        copy event object.
    */
   _onCopy: function (event) {
-    this.copySelection();
-    event.preventDefault();
+    let win = this.styleWindow;
+    let text = win.getSelection().toString().trim();
+    if (text !== "") {
+      this.copySelection();
+      event.preventDefault();
+    }
   },
 
   /**
    * Copy the current selection to the clipboard
    */
   copySelection: function () {
     try {
       let win = this.styleWindow;
       let text = win.getSelection().toString().trim();
-
+      // isPropertyPresent is set when a property name is spotted and
+      // we assume that the next line will be a property value.
+      let isPropertyPresent = false;
       // Tidy up block headings by moving CSS property names and their
       // values onto the same line and inserting a colon between them.
       let textArray = text.split(/[\r\n]+/);
       let result = "";
 
       // Parse text array to output string.
       if (textArray.length > 1) {
         for (let prop of textArray) {
           if (CssComputedView.propertyNames.indexOf(prop) !== -1) {
+            // Property name found so setting isPropertyPresent to true
+            isPropertyPresent = true;
             // Property name
             result += prop;
+          } else if (isPropertyPresent === true) {
+            // Since isPropertyPresent is true so we assume that this is
+            // a property value and we append it to result preceeded by
+            // a :.
+            result += ": " + prop + ";\n";
+            isPropertyPresent = false;
           } else {
-            // Property value
-            result += ": " + prop + ";\n";
+            // since isPropertyPresent is not set, we assume this is
+            // normal text and we append it to result without any :.
+            result += prop + "\n";
           }
         }
       } else {
         // Short text fragment.
         result = textArray[0];
       }
 
       clipboardHelper.copyString(result);
@@ -752,17 +767,17 @@ CssComputedView.prototype = {
     }
 
     this.tooltips.destroy();
     this.highlighters.removeFromView(this);
 
     // Remove bound listeners
     this.styleDocument.removeEventListener("mousedown", this.focusWindow);
     this.element.removeEventListener("click", this._onClick);
-    this.element.removeEventListener("copy", this._onCopy);
+    this.styleDocument.removeEventListener("copy", this._onCopy);
     this.element.removeEventListener("contextmenu", this._onContextMenu);
     this.searchField.removeEventListener("input", this._onFilterStyles);
     this.searchField.removeEventListener("contextmenu",
       this.inspector.onTextBoxContextMenu);
     this.searchClearButton.removeEventListener("click", this._onClearSearch);
     this.includeBrowserStylesCheckbox.removeEventListener("input",
       this._onIncludeBrowserStyles);
 
--- a/devtools/client/performance/modules/logic/tree-model.js
+++ b/devtools/client/performance/modules/logic/tree-model.js
@@ -50,17 +50,17 @@ function ThreadNode(thread, options = {}
     this._uninvert();
   }
 }
 
 ThreadNode.prototype = {
   /**
    * Build an inverted call tree from profile samples. The format of the
    * samples is described in tools/profiler/ProfileEntry.h, under the heading
-   * "ThreadProfile JSON Format".
+   * "Thread profile JSON Format".
    *
    * The profile data is naturally presented inverted. Inverting the call tree
    * is also the default in the Performance tool.
    *
    * @param object samples
    *        The raw samples array received from the backend.
    * @param object stackTable
    *        The table of deduplicated stacks from the backend.
--- a/devtools/client/shared/components/tabs/tabs.css
+++ b/devtools/client/shared/components/tabs/tabs.css
@@ -25,18 +25,25 @@
   display: block;
   color: #A9A9A9;
   padding: 4px 8px;
   border: 1px solid transparent;
   text-decoration: none;
   white-space: nowrap;
 }
 
+/* To avoid "select all" commands from selecting content in hidden tabs */
+.tabs .hidden,
+.tabs .hidden * {
+  -moz-user-select: none !important;
+}
+
 .tabs .tabs-menu-item a {
   cursor: default;
+  -moz-user-select: none;
 }
 
 /* Make sure panel content takes entire vertical space.
   (minus the height of the tab bar) */
 .tabs .panels {
   height: calc(100% - 24px);
 }
 
--- a/devtools/client/shared/components/tabs/tabs.js
+++ b/devtools/client/shared/components/tabs/tabs.js
@@ -324,17 +324,17 @@ define(function (require, exports, modul
             width: selected ? "100%" : "0",
           };
 
           return (
             DOM.div({
               id: id ? id + "-panel" : "panel-" + index,
               key: index,
               style: style,
-              className: "tab-panel-box",
+              className: selected ? "tab-panel-box" : "tab-panel-box hidden",
               role: "tabpanel",
               "aria-labelledby": id ? id + "-tab" : "tab-" + index,
             },
               (selected || this.state.created[index]) ? tab : null
             )
           );
         });
 
--- a/devtools/client/themes/boxmodel.css
+++ b/devtools/client/themes/boxmodel.css
@@ -221,17 +221,17 @@
 .boxmodel-legend[data-box="margin"] {
   color: var(--theme-highlight-blue);
 }
 
 /* Editable fields */
 
 .boxmodel-editable {
   border: 1px dashed transparent;
-  -moz-user-select: text;
+  -moz-user-select: none;
 }
 
 .boxmodel-editable:hover {
   border-bottom-color: hsl(0, 0%, 50%);
 }
 
 /* Make sure the content size doesn't appear as editable like the other sizes */
 
--- a/devtools/client/themes/computed.css
+++ b/devtools/client/themes/computed.css
@@ -24,16 +24,17 @@
 #computedview-container-focusable {
   height: 100%;
   outline: none;
 }
 
 #computedview-toolbar {
   display: flex;
   align-items: center;
+  -moz-user-select: none;
 }
 
 #browser-style-checkbox {
   /* Bug 1200073 - extra space before the browser styles checkbox so
      they aren't squished together in a small window. Put also
      an extra space after. */
   margin-inline-start: 5px;
   margin-inline-end: 0;
--- a/devtools/client/themes/deprecated-boxmodel.css
+++ b/devtools/client/themes/deprecated-boxmodel.css
@@ -6,16 +6,17 @@
  * THIS STYLESHEET IS FOR THE DEPRECATED BOX MODEL MODULE (deprecated-box-model.js) AND
  * SHOULD NO LONGER BE MODIFIED.
  */
 
 #old-boxmodel-wrapper {
   border-bottom-style: solid;
   border-bottom-width: 1px;
   border-color: var(--theme-splitter-color);
+  -moz-user-select: none;
 }
 
 #old-boxmodel-container {
   /* The view will grow bigger as the window gets resized, until 400px */
   max-width: 400px;
   margin: 0px auto;
   padding: 0;
 }
@@ -228,17 +229,17 @@
 .old-boxmodel-legend[data-box="margin"] {
   color: var(--theme-highlight-blue);
 }
 
 /* Editable fields */
 
 .old-boxmodel-editable {
   border: 1px dashed transparent;
-  -moz-user-select: text;
+  -moz-user-select: none;
 }
 
 .old-boxmodel-editable:hover {
   border-bottom-color: hsl(0, 0%, 50%);
 }
 
 /* Make sure the content size doesn't appear as editable like the other sizes */
 
--- a/devtools/shared/fronts/call-watcher.js
+++ b/devtools/shared/fronts/call-watcher.js
@@ -50,19 +50,16 @@ protocol.FrontClassWithSpec(callWatcherS
  */
 CallWatcherFront.METHOD_FUNCTION = 0;
 CallWatcherFront.GETTER_FUNCTION = 1;
 CallWatcherFront.SETTER_FUNCTION = 2;
 
 CallWatcherFront.KNOWN_METHODS = {};
 
 CallWatcherFront.KNOWN_METHODS.CanvasRenderingContext2D = {
-  asyncDrawXULElement: {
-    enums: new Set([6]),
-  },
   drawWindow: {
     enums: new Set([6])
   },
 };
 
 CallWatcherFront.KNOWN_METHODS.WebGLRenderingContext = {
   activeTexture: {
     enums: new Set([0]),
--- a/docshell/test/navigation/mochitest.ini
+++ b/docshell/test/navigation/mochitest.ini
@@ -45,17 +45,17 @@ support-files =
 [test_bug270414.html]
 [test_bug278916.html]
 [test_bug279495.html]
 [test_bug344861.html]
 skip-if = toolkit == "android" || toolkit == "windows" # disabled on Windows because of bug 1234520
 [test_bug386782.html]
 [test_bug430624.html]
 [test_bug430723.html]
-skip-if = toolkit == 'android' #TIMED_OUT
+skip-if = (toolkit == 'android') || (!debug && (os == 'mac' || os == 'win')) # Bug 874423
 [test_child.html]
 [test_grandchild.html]
 [test_not-opener.html]
 [test_opener.html]
 [test_popup-navigates-children.html]
 [test_reserved.html]
 skip-if = (toolkit == 'android') || (debug && e10s) #too slow on Android 4.3 aws only; bug 1030403; bug 1263213 for debug e10s
 [test_sessionhistory.html]
--- a/dom/base/DOMParser.cpp
+++ b/dom/base/DOMParser.cpp
@@ -379,17 +379,17 @@ DOMParser::Init(nsIPrincipal* principal,
   return NS_OK;
 }
 
 /*static */already_AddRefed<DOMParser>
 DOMParser::Constructor(const GlobalObject& aOwner,
                        nsIPrincipal* aPrincipal, nsIURI* aDocumentURI,
                        nsIURI* aBaseURI, ErrorResult& rv)
 {
-  if (!nsContentUtils::IsCallerChrome()) {
+  if (aOwner.CallerType() != CallerType::System) {
     rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
   RefPtr<DOMParser> domParser = new DOMParser(aOwner.GetAsSupports());
   rv = domParser->InitInternal(aOwner.GetAsSupports(), aPrincipal, aDocumentURI,
                                aBaseURI);
   if (rv.Failed()) {
     return nullptr;
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3316,36 +3316,36 @@ Element::AttrValueToCORSMode(const nsAtt
   if (!aValue) {
     return CORS_NONE;
   }
 
   return CORSMode(aValue->GetEnumValue());
 }
 
 static const char*
-GetFullScreenError(nsIDocument* aDoc)
+GetFullScreenError(CallerType aCallerType)
 {
-  if (!nsContentUtils::IsRequestFullScreenAllowed()) {
+  if (!nsContentUtils::IsRequestFullScreenAllowed(aCallerType)) {
     return "FullscreenDeniedNotInputDriven";
   }
 
   return nullptr;
 }
 
 void
 Element::RequestFullscreen(CallerType aCallerType, ErrorResult& aError)
 {
   // Only grant full-screen requests if this is called from inside a trusted
   // event handler (i.e. inside an event handler for a user initiated event).
   // This stops the full-screen from being abused similar to the popups of old,
   // and it also makes it harder for bad guys' script to go full-screen and
   // spoof the browser chrome/window and phish logins etc.
   // Note that requests for fullscreen inside a web app's origin are exempt
   // from this restriction.
-  if (const char* error = GetFullScreenError(OwnerDoc())) {
+  if (const char* error = GetFullScreenError(aCallerType)) {
     OwnerDoc()->DispatchFullscreenError(error);
     return;
   }
 
   auto request = MakeUnique<FullscreenRequest>(this);
   request->mIsCallerChrome = (aCallerType == CallerType::System);
 
   OwnerDoc()->AsyncRequestFullScreen(Move(request));
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1419,16 +1419,17 @@ Navigator::GetMediaDevices(ErrorResult& 
   }
   return mMediaDevices;
 }
 
 void
 Navigator::MozGetUserMedia(const MediaStreamConstraints& aConstraints,
                            NavigatorUserMediaSuccessCallback& aOnSuccess,
                            NavigatorUserMediaErrorCallback& aOnError,
+                           CallerType aCallerType,
                            ErrorResult& aRv)
 {
   CallbackObjectHolder<NavigatorUserMediaSuccessCallback,
                        nsIDOMGetUserMediaSuccessCallback> holder1(&aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onsuccess =
     holder1.ToXPCOMCallback();
 
   CallbackObjectHolder<NavigatorUserMediaErrorCallback,
@@ -1437,17 +1438,18 @@ Navigator::MozGetUserMedia(const MediaSt
 
   if (!mWindow || !mWindow->GetOuterWindow() ||
       mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
   }
 
   MediaManager* manager = MediaManager::Get();
-  aRv = manager->GetUserMedia(mWindow, aConstraints, onsuccess, onerror);
+  aRv = manager->GetUserMedia(mWindow, aConstraints, onsuccess, onerror,
+                              aCallerType);
 }
 
 void
 Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
                                   MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                                   NavigatorUserMediaErrorCallback& aOnError,
                                   uint64_t aInnerWindowID,
                                   const nsAString& aCallID,
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -227,16 +227,17 @@ public:
 
   bool SendBeacon(const nsAString& aUrl,
                   const Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& aData,
                   ErrorResult& aRv);
 
   void MozGetUserMedia(const MediaStreamConstraints& aConstraints,
                        NavigatorUserMediaSuccessCallback& aOnSuccess,
                        NavigatorUserMediaErrorCallback& aOnError,
+                       CallerType aCallerType,
                        ErrorResult& aRv);
   void MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
                               MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                               NavigatorUserMediaErrorCallback& aOnError,
                               uint64_t aInnerWindowID,
                               const nsAString& aCallID,
                               ErrorResult& aRv);
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6842,33 +6842,33 @@ nsContentUtils::ChannelShouldInheritPrin
 bool
 nsContentUtils::IsFullScreenApiEnabled()
 {
   return sIsFullScreenApiEnabled;
 }
 
 /* static */
 bool
-nsContentUtils::IsRequestFullScreenAllowed()
+nsContentUtils::IsRequestFullScreenAllowed(CallerType aCallerType)
 {
   return !sTrustedFullScreenOnly ||
          EventStateManager::IsHandlingUserInput() ||
-         IsCallerChrome();
+         aCallerType == CallerType::System;
 }
 
 /* static */
 bool
-nsContentUtils::IsCutCopyAllowed()
+nsContentUtils::IsCutCopyAllowed(nsIPrincipal* aSubjectPrincipal)
 {
   if ((!IsCutCopyRestricted() && EventStateManager::IsHandlingUserInput()) ||
-      IsCallerChrome()) {
+      IsSystemPrincipal(aSubjectPrincipal)) {
     return true;
   }
 
-  return BasePrincipal::Cast(SubjectPrincipal())->AddonHasPermission(NS_LITERAL_STRING("clipboardWrite"));
+  return BasePrincipal::Cast(aSubjectPrincipal)->AddonHasPermission(NS_LITERAL_STRING("clipboardWrite"));
 }
 
 /* static */
 bool
 nsContentUtils::IsFrameTimingEnabled()
 {
   return sIsFrameTimingPrefEnabled;
 }
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1998,33 +1998,33 @@ public:
     { return sIsUnprefixedFullscreenApiEnabled; }
 
   /**
    * Returns true if requests for full-screen are allowed in the current
    * context. Requests are only allowed if the user initiated them (like with
    * a mouse-click or key press), unless this check has been disabled by
    * setting the pref "full-screen-api.allow-trusted-requests-only" to false.
    */
-  static bool IsRequestFullScreenAllowed();
+  static bool IsRequestFullScreenAllowed(mozilla::dom::CallerType aCallerType);
 
   /**
    * Returns true if calling execCommand with 'cut' or 'copy' arguments
    * is restricted to chrome code.
    */
   static bool IsCutCopyRestricted()
   {
     return !sIsCutCopyAllowed;
   }
 
   /**
    * Returns true if calling execCommand with 'cut' or 'copy' arguments is
-   * allowed in the current context. These are only allowed if the user initiated
-   * them (like with a mouse-click or key press).
+   * allowed for the given subject principal. These are only allowed if the user
+   * initiated them (like with a mouse-click or key press).
    */
-  static bool IsCutCopyAllowed();
+  static bool IsCutCopyAllowed(nsIPrincipal* aSubjectPrincipal);
 
   /*
    * Returns true if the performance timing APIs are enabled.
    */
   static bool IsPerformanceTimingEnabled()
   {
     return sIsPerformanceTimingEnabled;
   }
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -7281,19 +7281,20 @@ nsGlobalWindow::Confirm(const nsAString&
                         ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(ConfirmOuter, (aMessage, aSubjectPrincipal, aError),
                             aError, false);
 }
 
 already_AddRefed<Promise>
 nsGlobalWindow::Fetch(const RequestOrUSVString& aInput,
-                      const RequestInit& aInit, ErrorResult& aRv)
-{
-  return FetchRequest(this, aInput, aInit, aRv);
+                      const RequestInit& aInit,
+                      CallerType aCallerType, ErrorResult& aRv)
+{
+  return FetchRequest(this, aInput, aInit, aCallerType, aRv);
 }
 
 void
 nsGlobalWindow::PromptOuter(const nsAString& aMessage,
                             const nsAString& aInitial,
                             nsAString& aReturn,
                             nsIPrincipal& aSubjectPrincipal,
                             ErrorResult& aError)
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -959,16 +959,17 @@ public:
                    mozilla::ErrorResult& aError);
   void Prompt(const nsAString& aMessage, const nsAString& aInitial,
               nsAString& aReturn,
               nsIPrincipal& aSubjectPrincipal,
               mozilla::ErrorResult& aError);
   already_AddRefed<mozilla::dom::cache::CacheStorage> GetCaches(mozilla::ErrorResult& aRv);
   already_AddRefed<mozilla::dom::Promise> Fetch(const mozilla::dom::RequestOrUSVString& aInput,
                                                 const mozilla::dom::RequestInit& aInit,
+                                                mozilla::dom::CallerType aCallerType,
                                                 mozilla::ErrorResult& aRv);
   void PrintOuter(mozilla::ErrorResult& aError);
   void Print(mozilla::ErrorResult& aError);
   void ShowModalDialog(JSContext* aCx, const nsAString& aUrl,
                        JS::Handle<JS::Value> aArgument,
                        const nsAString& aOptions,
                        JS::MutableHandle<JS::Value> aRetval,
                        nsIPrincipal& aSubjectPrincipal,
@@ -1204,17 +1205,17 @@ public:
     mozilla::IgnoredErrorResult ignored;
     nsCOMPtr<nsPIDOMWindowOuter> win =
       GetContentInternal(ignored, mozilla::dom::CallerType::System);
     return win.forget();
   }
 
   void Get_content(JSContext* aCx,
                    JS::MutableHandle<JSObject*> aRetval,
-                   mozilla::dom::CallerType aCallerType,
+                   mozilla::dom::SystemCallerGuarantee aCallerType,
                    mozilla::ErrorResult& aError)
   {
     if (mDoc) {
       mDoc->WarnOnceAbout(nsIDocument::eWindow_Content);
     }
     GetContent(aCx, aRetval, aCallerType, aError);
   }
 
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -101,16 +101,17 @@
 #include "DocumentType.h"
 #include <algorithm>
 #include "nsGlobalWindow.h"
 #include "nsDOMMutationObserver.h"
 #include "GeometryUtils.h"
 #include "nsIAnimationObserver.h"
 #include "nsChildContentList.h"
 #include "mozilla/dom/NodeBinding.h"
+#include "mozilla/dom/BindingDeclarations.h"
 
 #ifdef ACCESSIBILITY
 #include "mozilla/dom/AccessibleNode.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
@@ -714,19 +715,21 @@ nsINode::GetBaseURI(nsAString &aURI) con
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   CopyUTF8toUTF16(spec, aURI);
   return NS_OK;
 }
 
 void
-nsINode::GetBaseURIFromJS(nsAString& aURI, ErrorResult& aRv) const
+nsINode::GetBaseURIFromJS(nsAString& aURI,
+                          CallerType aCallerType,
+                          ErrorResult& aRv) const
 {
-  nsCOMPtr<nsIURI> baseURI = GetBaseURI(nsContentUtils::IsCallerChrome());
+  nsCOMPtr<nsIURI> baseURI = GetBaseURI(aCallerType == CallerType::System);
   nsAutoCString spec;
   if (baseURI) {
     nsresult res = baseURI->GetSpec(spec);
     if (NS_FAILED(res)) {
       aRv.Throw(res);
       return;
     }
   }
@@ -1260,46 +1263,53 @@ nsINode::GetEventTargetParent(EventChain
   // This is only here so that we can use the NS_DECL_NSIDOMTARGET macro
   NS_ABORT();
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 void
 nsINode::GetBoxQuads(const BoxQuadOptions& aOptions,
                      nsTArray<RefPtr<DOMQuad> >& aResult,
+                     CallerType aCallerType,
                      mozilla::ErrorResult& aRv)
 {
-  mozilla::GetBoxQuads(this, aOptions, aResult, aRv);
+  mozilla::GetBoxQuads(this, aOptions, aResult, aCallerType, aRv);
 }
 
 already_AddRefed<DOMQuad>
 nsINode::ConvertQuadFromNode(DOMQuad& aQuad,
                              const GeometryNode& aFrom,
                              const ConvertCoordinateOptions& aOptions,
+                             CallerType aCallerType,
                              ErrorResult& aRv)
 {
-  return mozilla::ConvertQuadFromNode(this, aQuad, aFrom, aOptions, aRv);
+  return mozilla::ConvertQuadFromNode(this, aQuad, aFrom, aOptions, aCallerType,
+                                      aRv);
 }
 
 already_AddRefed<DOMQuad>
 nsINode::ConvertRectFromNode(DOMRectReadOnly& aRect,
                              const GeometryNode& aFrom,
                              const ConvertCoordinateOptions& aOptions,
+                             CallerType aCallerType,
                              ErrorResult& aRv)
 {
-  return mozilla::ConvertRectFromNode(this, aRect, aFrom, aOptions, aRv);
+  return mozilla::ConvertRectFromNode(this, aRect, aFrom, aOptions, aCallerType,
+                                      aRv);
 }
 
 already_AddRefed<DOMPoint>
 nsINode::ConvertPointFromNode(const DOMPointInit& aPoint,
                               const GeometryNode& aFrom,
                               const ConvertCoordinateOptions& aOptions,
+                              CallerType aCallerType,
                               ErrorResult& aRv)
 {
-  return mozilla::ConvertPointFromNode(this, aPoint, aFrom, aOptions, aRv);
+  return mozilla::ConvertPointFromNode(this, aPoint, aFrom, aOptions,
+                                       aCallerType, aRv);
 }
 
 nsresult
 nsINode::DispatchEvent(nsIDOMEvent *aEvent, bool* aRetVal)
 {
   // XXX sXBL/XBL2 issue -- do we really want the owner here?  What
   // if that's the XBL document?  Would we want its presshell?  Or what?
   nsCOMPtr<nsIDocument> document = OwnerDoc();
@@ -2939,17 +2949,17 @@ nsINode::WrapObject(JSContext *aCx, JS::
   // (3) we are running a privileged script.
   // Event handling is possible only if (1). If (2) event handling is
   // prevented.
   // If the document has never had a script handling object, untrusted
   // scripts (3) shouldn't touch it!
   bool hasHadScriptHandlingObject = false;
   if (!OwnerDoc()->GetScriptHandlingObject(hasHadScriptHandlingObject) &&
       !hasHadScriptHandlingObject &&
-      !nsContentUtils::IsCallerChrome()) {
+      !nsContentUtils::IsSystemCaller(aCx)) {
     Throw(aCx, NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   JS::Rooted<JSObject*> obj(aCx, WrapNode(aCx, aGivenProto));
   MOZ_ASSERT_IF(obj && ChromeOnlyAccess(),
                 xpc::IsInContentXBLScope(obj) ||
                 !xpc::UseContentXBLScope(js::GetObjectCompartment(obj)));
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -78,16 +78,17 @@ class Element;
 class EventHandlerNonNull;
 template<typename T> class Optional;
 class OwningNodeOrString;
 template<typename> class Sequence;
 class Text;
 class TextOrElementOrDocument;
 struct DOMPointInit;
 struct GetRootNodeOptions;
+enum class CallerType : uint32_t;
 } // namespace dom
 } // namespace mozilla
 
 #define NODE_FLAG_BIT(n_) \
   (nsWrapperCache::FlagsType(1U) << (WRAPPER_CACHE_FLAGS_BITS_USED + (n_)))
 
 enum {
   // This bit will be set if the node has a listener manager.
@@ -280,16 +281,17 @@ public:
   typedef mozilla::dom::BoxQuadOptions BoxQuadOptions;
   typedef mozilla::dom::ConvertCoordinateOptions ConvertCoordinateOptions;
   typedef mozilla::dom::DOMPoint DOMPoint;
   typedef mozilla::dom::DOMPointInit DOMPointInit;
   typedef mozilla::dom::DOMQuad DOMQuad;
   typedef mozilla::dom::DOMRectReadOnly DOMRectReadOnly;
   typedef mozilla::dom::OwningNodeOrString OwningNodeOrString;
   typedef mozilla::dom::TextOrElementOrDocument TextOrElementOrDocument;
+  typedef mozilla::dom::CallerType CallerType;
   typedef mozilla::ErrorResult ErrorResult;
 
   template<class T>
   using Sequence = mozilla::dom::Sequence<T>;
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_INODE_IID)
 
   // Among the sub-classes that inherit (directly or indirectly) from nsINode,
@@ -1768,17 +1770,19 @@ public:
     aNodeName.SetStringBuffer(nsStringBuffer::FromString(nodeName),
                               nodeName.Length());
   }
   MOZ_MUST_USE nsresult GetBaseURI(nsAString& aBaseURI) const;
   // Return the base URI for the document.
   // The returned value may differ if the document is loaded via XHR, and
   // when accessed from chrome privileged script and
   // from content privileged script for compatibility.
-  void GetBaseURIFromJS(nsAString& aBaseURI, mozilla::ErrorResult& aRv) const;
+  void GetBaseURIFromJS(nsAString& aBaseURI,
+                        CallerType aCallerType,
+                        ErrorResult& aRv) const;
   bool HasChildNodes() const
   {
     return HasChildren();
   }
   uint16_t CompareDocumentPosition(nsINode& aOther) const;
   void GetNodeValue(nsAString& aNodeValue)
   {
     GetNodeValueInternal(aNodeValue);
@@ -1873,29 +1877,33 @@ public:
   mozilla::dom::Element* GetFirstElementChild() const;
   mozilla::dom::Element* GetLastElementChild() const;
 
   void Prepend(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
   void Append(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
 
   void GetBoxQuads(const BoxQuadOptions& aOptions,
                    nsTArray<RefPtr<DOMQuad> >& aResult,
-                   mozilla::ErrorResult& aRv);
+                   CallerType aCallerType,
+                   ErrorResult& aRv);
 
   already_AddRefed<DOMQuad> ConvertQuadFromNode(DOMQuad& aQuad,
                                                 const TextOrElementOrDocument& aFrom,
                                                 const ConvertCoordinateOptions& aOptions,
+                                                CallerType aCallerType,
                                                 ErrorResult& aRv);
   already_AddRefed<DOMQuad> ConvertRectFromNode(DOMRectReadOnly& aRect,
                                                 const TextOrElementOrDocument& aFrom,
                                                 const ConvertCoordinateOptions& aOptions,
+                                                CallerType aCallerType,
                                                 ErrorResult& aRv);
   already_AddRefed<DOMPoint> ConvertPointFromNode(const DOMPointInit& aPoint,
                                                   const TextOrElementOrDocument& aFrom,
                                                   const ConvertCoordinateOptions& aOptions,
+                                                  CallerType aCallerType,
                                                   ErrorResult& aRv);
 
 protected:
 
   // Override this function to create a custom slots class.
   // Must not return null.
   virtual nsINode::nsSlots* CreateSlots();
 
--- a/dom/base/nsIObjectLoadingContent.idl
+++ b/dom/base/nsIObjectLoadingContent.idl
@@ -121,21 +121,16 @@ interface nsIObjectLoadingContent : nsIS
    */
   [noscript] void pluginDestroyed();
   [noscript] void pluginCrashed(in nsIPluginTag pluginTag,
                                 in AString pluginDumpID,
                                 in AString browserDumpID,
                                 in boolean submittedCrashReport);
 
   /**
-   * This method will play a plugin that has been stopped by click-to-play.
-   */
-  void playPlugin();
-
-  /**
    * Forces a re-evaluation and reload of the tag, optionally invalidating its
    * click-to-play state.  This can be used when the MIME type that provides a
    * type has changed, for instance, to force the tag to re-evalulate the
    * handler to use.
    */
   void reload(in boolean aClearActivation);
 
   /**
@@ -163,23 +158,9 @@ interface nsIObjectLoadingContent : nsIS
    */
   [noscript] void initializeFromChannel(in nsIRequest request);
 
   /**
    * The URL of the data/src loaded in the object. This may be null (i.e.
    * an <embed> with no src).
    */
   readonly attribute nsIURI srcURI;
-
-  /**
-   * The plugin's current state of fallback content. This property
-   * only makes sense if the plugin is not activated.
-   */
-  readonly attribute unsigned long pluginFallbackType;
-
-  /**
-   * If this plugin runs out-of-process, it has a runID to differentiate
-   * between different times the plugin process has been instantiated.
-   *
-   * This throws NS_ERROR_NOT_IMPLEMENTED for in-process plugins.
-   */
-  readonly attribute unsigned long runID;
 };
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -2910,16 +2910,20 @@ nsresult
 nsObjectLoadingContent::ScriptRequestPluginInstance(JSContext* aCx,
                                                     nsNPAPIPluginInstance **aResult)
 {
   // The below methods pull the cx off the stack, so make sure they match.
   //
   // NB: Sometimes there's a null cx on the stack, in which case |cx| is the
   // safe JS context. But in that case, IsCallerChrome() will return true,
   // so the ensuing expression is short-circuited.
+  // XXXbz the NB comment above doesn't really make sense.  At the moment, all
+  // the callers to this except maybe SetupProtoChain have a useful JSContext*
+  // that could be used for nsContentUtils::IsSystemCaller...  We do need to
+  // sort out what the SetupProtoChain callers look like.
   MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContext(),
                 aCx == nsContentUtils::GetCurrentJSContext());
   bool callerIsContentJS = (nsContentUtils::GetCurrentJSContext() &&
                             !nsContentUtils::IsCallerChrome() &&
                             !nsContentUtils::IsCallerContentXBL());
 
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
@@ -3162,35 +3166,31 @@ nsObjectLoadingContent::NotifyContentObj
     // Nothing to do here if there's no wrapper for mContent. The proto
     // chain will be fixed appropriately when the wrapper is created.
     return;
   }
 
   SetupProtoChain(cx, obj);
 }
 
-NS_IMETHODIMP
-nsObjectLoadingContent::PlayPlugin()
+void
+nsObjectLoadingContent::PlayPlugin(SystemCallerGuarantee, ErrorResult& aRv)
 {
-  if (!nsContentUtils::IsCallerChrome())
-    return NS_OK;
-
+  // This is a ChromeOnly method, so no need to check caller type here.
   if (!mActivated) {
     mActivated = true;
     LOG(("OBJLC [%p]: Activated by user", this));
   }
 
   // If we're in a click-to-play state, reload.
   // Fallback types >= eFallbackClickToPlay are plugin-replacement types, see
   // header
   if (mType == eType_Null && mFallbackType >= eFallbackClickToPlay) {
-    return LoadObject(true, true);
+    aRv = LoadObject(true, true);
   }
-
-  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsObjectLoadingContent::Reload(bool aClearActivation)
 {
   if (aClearActivation) {
     mActivated = false;
   }
@@ -3200,51 +3200,37 @@ nsObjectLoadingContent::Reload(bool aCle
 
 NS_IMETHODIMP
 nsObjectLoadingContent::GetActivated(bool *aActivated)
 {
   *aActivated = Activated();
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsObjectLoadingContent::GetPluginFallbackType(uint32_t* aPluginFallbackType)
-{
-  NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
-  *aPluginFallbackType = mFallbackType;
-  return NS_OK;
-}
-
 uint32_t
 nsObjectLoadingContent::DefaultFallbackType()
 {
   FallbackType reason;
   bool go = ShouldPlay(reason, true);
   if (go) {
     return PLUGIN_ACTIVE;
   }
   return reason;
 }
 
-NS_IMETHODIMP
-nsObjectLoadingContent::GetRunID(uint32_t* aRunID)
+uint32_t
+nsObjectLoadingContent::GetRunID(SystemCallerGuarantee, ErrorResult& aRv)
 {
-  if (NS_WARN_IF(!nsContentUtils::IsCallerChrome())) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-  if (NS_WARN_IF(!aRunID)) {
-    return NS_ERROR_INVALID_POINTER;
-  }
   if (!mHasRunID) {
     // The plugin instance must not have a run ID, so we must
     // be running the plugin in-process.
-    return NS_ERROR_NOT_IMPLEMENTED;
+    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+    return 0;
   }
-  *aRunID = mRunID;
-  return NS_OK;
+  return mRunID;
 }
 
 static bool sPrefsInitialized;
 static uint32_t sSessionTimeoutMinutes;
 static uint32_t sPersistentTimeoutDays;
 
 bool
 nsObjectLoadingContent::ShouldBlockContent()
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -9,16 +9,17 @@
  * various content nodes that want to provide plugin/document/image
  * loading functionality (eg <embed>, <object>, <applet>, etc).
  */
 
 #ifndef NSOBJECTLOADINGCONTENT_H_
 #define NSOBJECTLOADINGCONTENT_H_
 
 #include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "nsImageLoadingContent.h"
 #include "nsIStreamListener.h"
 #include "nsIChannelEventSink.h"
 #include "nsIContentPolicy.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsIRunnable.h"
 #include "nsIThreadInternal.h"
 #include "nsIFrame.h"
@@ -188,20 +189,18 @@ class nsObjectLoadingContent : public ns
     uint32_t DisplayedType() const
     {
       return mType;
     }
     uint32_t GetContentTypeForMIMEType(const nsAString& aMIMEType)
     {
       return GetTypeOfContent(NS_ConvertUTF16toUTF8(aMIMEType));
     }
-    void PlayPlugin(mozilla::ErrorResult& aRv)
-    {
-      aRv = PlayPlugin();
-    }
+    void PlayPlugin(mozilla::dom::SystemCallerGuarantee,
+                    mozilla::ErrorResult& aRv);
     void Reload(bool aClearActivation, mozilla::ErrorResult& aRv)
     {
       aRv = Reload(aClearActivation);
     }
     bool Activated() const
     {
       return mActivated;
     }
@@ -234,27 +233,18 @@ class nsObjectLoadingContent : public ns
     {
       aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
     }
     void LegacyCall(JSContext* aCx, JS::Handle<JS::Value> aThisVal,
                     const mozilla::dom::Sequence<JS::Value>& aArguments,
                     JS::MutableHandle<JS::Value> aRetval,
                     mozilla::ErrorResult& aRv);
 
-    uint32_t GetRunID(mozilla::ErrorResult& aRv)
-    {
-      uint32_t runID;
-      nsresult rv = GetRunID(&runID);
-      if (NS_FAILED(rv)) {
-        aRv.Throw(rv);
-        return 0;
-      }
-
-      return runID;
-    }
+    uint32_t GetRunID(mozilla::dom::SystemCallerGuarantee,
+                      mozilla::ErrorResult& aRv);
 
     bool IsRewrittenYoutubeEmbed() const
     {
       return mRewrittenYoutubeEmbed;
     }
 
     void PresetOpenerWindow(mozIDOMWindowProxy* aOpenerWindow, mozilla::ErrorResult& aRv);
 
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -530,12 +530,20 @@ public:
 } // namespace binding_detail
 
 // Enum to represent a system or non-system caller type.
 enum class CallerType : uint32_t {
   System,
   NonSystem
 };
 
+// A class that can be passed (by value or const reference) to indicate that the
+// caller is always a system caller.  This can be used as the type of an
+// argument to force only system callers to call a function.
+class SystemCallerGuarantee {
+public:
+  operator CallerType() const { return CallerType::System; }
+};
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_BindingDeclarations_h__
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1148,32 +1148,32 @@ QueryInterface(JSContext* cx, unsigned a
   if (!obj) {
       JS_ReportErrorASCII(cx, "Permission denied to access object");
       return false;
   }
 
   // Switch this to UnwrapDOMObjectToISupports once our global objects are
   // using new bindings.
   nsCOMPtr<nsISupports> native;
-  UnwrapArg<nsISupports>(obj, getter_AddRefs(native));
+  UnwrapArg<nsISupports>(cx, obj, getter_AddRefs(native));
   if (!native) {
     return Throw(cx, NS_ERROR_FAILURE);
   }
 
   if (argc < 1) {
     return Throw(cx, NS_ERROR_XPC_NOT_ENOUGH_ARGS);
   }
 
   if (!args[0].isObject()) {
     return Throw(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
   }
 
   nsCOMPtr<nsIJSID> iid;
   obj = &args[0].toObject();
-  if (NS_FAILED(UnwrapArg<nsIJSID>(obj, getter_AddRefs(iid)))) {
+  if (NS_FAILED(UnwrapArg<nsIJSID>(cx, obj, getter_AddRefs(iid)))) {
     return Throw(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
   }
   MOZ_ASSERT(iid);
 
   if (iid->GetID()->Equals(NS_GET_IID(nsIClassInfo))) {
     nsresult rv;
     nsCOMPtr<nsIClassInfo> ci = do_QueryInterface(native, &rv);
     if (NS_FAILED(rv)) {
@@ -3149,17 +3149,18 @@ AssertReturnTypeMatchesJitinfo(const JSJ
 bool
 CallerSubsumes(JSObject *aObject)
 {
   nsIPrincipal* objPrin = nsContentUtils::ObjectPrincipal(js::UncheckedUnwrap(aObject));
   return nsContentUtils::SubjectPrincipal()->Subsumes(objPrin);
 }
 
 nsresult
-UnwrapArgImpl(JS::Handle<JSObject*> src,
+UnwrapArgImpl(JSContext* cx,
+              JS::Handle<JSObject*> src,
               const nsIID &iid,
               void **ppArg)
 {
   if (!NS_IsMainThread()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsISupports *iface = xpc::UnwrapReflectorToISupports(src);
@@ -3169,17 +3170,17 @@ UnwrapArgImpl(JS::Handle<JSObject*> src,
     }
 
     return NS_OK;
   }
 
   // Only allow XPCWrappedJS stuff in system code.  Ideally we would remove this
   // even there, but that involves converting some things to WebIDL callback
   // interfaces and making some other things builtinclass...
-  if (!nsContentUtils::IsCallerChrome()) {
+  if (!nsContentUtils::IsSystemCaller(cx)) {
     return NS_ERROR_XPC_BAD_CONVERT_JS;
   }
 
   RefPtr<nsXPCWrappedJS> wrappedJS;
   nsresult rv = nsXPCWrappedJS::GetNewOrUsed(src, iid, getter_AddRefs(wrappedJS));
   if (NS_FAILED(rv) || !wrappedJS) {
     return rv;
   }
@@ -3187,21 +3188,22 @@ UnwrapArgImpl(JS::Handle<JSObject*> src,
   // We need to go through the QueryInterface logic to make this return
   // the right thing for the various 'special' interfaces; e.g.
   // nsIPropertyBag. We must use AggregatedQueryInterface in cases where
   // there is an outer to avoid nasty recursion.
   return wrappedJS->QueryInterface(iid, ppArg);
 }
 
 nsresult
-UnwrapWindowProxyImpl(JS::Handle<JSObject*> src,
+UnwrapWindowProxyImpl(JSContext* cx,
+                      JS::Handle<JSObject*> src,
                       nsPIDOMWindowOuter** ppArg)
 {
   nsCOMPtr<nsPIDOMWindowInner> inner;
-  nsresult rv = UnwrapArg<nsPIDOMWindowInner>(src, getter_AddRefs(inner));
+  nsresult rv = UnwrapArg<nsPIDOMWindowInner>(cx, src, getter_AddRefs(inner));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsPIDOMWindowOuter> outer = inner->GetOuterWindow();
   outer.forget(ppArg);
   return NS_OK;
 }
 
 bool
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -48,35 +48,38 @@ class nsIJSID;
 namespace mozilla {
 
 enum UseCounter : int16_t;
 
 namespace dom {
 template<typename DataType> class MozMap;
 
 nsresult
-UnwrapArgImpl(JS::Handle<JSObject*> src, const nsIID& iid, void** ppArg);
+UnwrapArgImpl(JSContext* cx, JS::Handle<JSObject*> src, const nsIID& iid,
+              void** ppArg);
 
 nsresult
-UnwrapWindowProxyImpl(JS::Handle<JSObject*> src, nsPIDOMWindowOuter** ppArg);
+UnwrapWindowProxyImpl(JSContext* cx, JS::Handle<JSObject*> src,
+                      nsPIDOMWindowOuter** ppArg);
 
 /** Convert a jsval to an XPCOM pointer. */
 template <class Interface>
 inline nsresult
-UnwrapArg(JS::Handle<JSObject*> src, Interface** ppArg)
+UnwrapArg(JSContext* cx, JS::Handle<JSObject*> src, Interface** ppArg)
 {
-  return UnwrapArgImpl(src, NS_GET_TEMPLATE_IID(Interface),
+  return UnwrapArgImpl(cx, src, NS_GET_TEMPLATE_IID(Interface),
                        reinterpret_cast<void**>(ppArg));
 }
 
 template <>
 inline nsresult
-UnwrapArg<nsPIDOMWindowOuter>(JS::Handle<JSObject*> src, nsPIDOMWindowOuter** ppArg)
+UnwrapArg<nsPIDOMWindowOuter>(JSContext* cx, JS::Handle<JSObject*> src,
+                              nsPIDOMWindowOuter** ppArg)
 {
-  return UnwrapWindowProxyImpl(src, ppArg);
+  return UnwrapWindowProxyImpl(cx, src, ppArg);
 }
 
 bool
 ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
                  bool aSecurityError, const char* aInterfaceName);
 
 bool
 ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -691,19 +691,16 @@ DOMInterfaces = {
 
 'NetworkInformation': {
     'nativeType': 'mozilla::dom::network::Connection',
 },
 
 'Node': {
     'nativeType': 'nsINode',
     'concrete': False,
-    'binaryNames': {
-        'baseURI': 'baseURIFromJS'
-    }
 },
 
 'NodeIterator': {
     'wrapperCache': False,
 },
 
 'NodeList': {
     'nativeType': 'nsINodeList',
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -4238,25 +4238,25 @@ class CastableObjectUnwrapper():
                 codeOnFailure=(codeOnFailure % { 'securityError': 'true'}))
             self.substitution["source"] = "maybeUncheckedObj"
             xpconnectUnwrap = dedent("""
                 nsresult rv;
                 { // Scope for the JSAutoCompartment, because we only
                   // want to be in that compartment for the UnwrapArg call.
                   JS::Rooted<JSObject*> source(cx, ${source});
                   JSAutoCompartment ac(cx, ${source});
-                  rv = UnwrapArg<${type}>(source, getter_AddRefs(objPtr));
+                  rv = UnwrapArg<${type}>(cx, source, getter_AddRefs(objPtr));
                 }
                 """)
         else:
             self.substitution["uncheckedObjDecl"] = ""
             self.substitution["source"] = source
             xpconnectUnwrap = (
                 "JS::Rooted<JSObject*> source(cx, ${source});\n"
-                "nsresult rv = UnwrapArg<${type}>(source, getter_AddRefs(objPtr));\n")
+                "nsresult rv = UnwrapArg<${type}>(cx, source, getter_AddRefs(objPtr));\n")
 
         if descriptor.hasXPConnectImpls:
             self.substitution["codeOnFailure"] = string.Template(
                 "RefPtr<${type}> objPtr;\n" +
                 xpconnectUnwrap +
                 "if (NS_FAILED(rv)) {\n"
                 "${indentedCodeOnFailure}"
                 "}\n"
@@ -5498,17 +5498,17 @@ def getJSToNativeConversionInfo(type, de
             if forceOwningType:
                 # Don't return a holderType in this case; our declName
                 # will just own stuff.
                 templateBody += "RefPtr<" + typeName + "> ${holderName};\n"
             else:
                 holderType = "RefPtr<" + typeName + ">"
             templateBody += (
                 "JS::Rooted<JSObject*> source(cx, &${val}.toObject());\n" +
-                "if (NS_FAILED(UnwrapArg<" + typeName + ">(source, getter_AddRefs(${holderName})))) {\n")
+                "if (NS_FAILED(UnwrapArg<" + typeName + ">(cx, source, getter_AddRefs(${holderName})))) {\n")
             templateBody += CGIndenter(onFailureBadType(failureCode,
                                                         descriptor.interface.identifier.name)).define()
             templateBody += ("}\n"
                              "MOZ_ASSERT(${holderName});\n")
 
             # And store our value in ${declName}
             templateBody += "${declName} = ${holderName};\n"
 
@@ -7034,16 +7034,17 @@ class CGCallGenerator(CGThing):
     isFallible is a boolean indicating whether the call should be fallible.
 
     resultVar: If the returnType is not void, then the result of the call is
     stored in a C++ variable named by resultVar. The caller is responsible for
     declaring the result variable. If the caller doesn't care about the result
     value, resultVar can be omitted.
     """
     def __init__(self, isFallible, needsSubjectPrincipal, needsCallerType,
+                 isChromeOnly,
                  arguments, argsPre, returnType, extendedAttributes, descriptor,
                  nativeMethodName, static, object="self", argsPost=[],
                  resultVar=None):
         CGThing.__init__(self)
 
         result, resultOutParam, resultRooter, resultArgs, resultConversion = \
             getRetvalDeclarationForType(returnType, descriptor)
 
@@ -7099,17 +7100,20 @@ class CGCallGenerator(CGThing):
             else:
                 assert resultOutParam == "ptr"
                 args.append(CGGeneric("&" + resultVar))
 
         if needsSubjectPrincipal:
             args.append(CGGeneric("subjectPrincipal"))
 
         if needsCallerType:
-            args.append(CGGeneric(callerTypeGetterForDescriptor(descriptor)))
+            if isChromeOnly:
+                args.append(CGGeneric("SystemCallerGuarantee()"))
+            else:
+                args.append(CGGeneric(callerTypeGetterForDescriptor(descriptor)))
 
         canOOM = "canOOM" in extendedAttributes
         if isFallible or canOOM:
             args.append(CGGeneric("rv"))
         args.extend(CGGeneric(arg) for arg in argsPost)
 
         # Build up our actual call
         self.cgRoot = CGList([])
@@ -7596,16 +7600,17 @@ class CGPerSignatureCall(CGThing):
                 cgThings.append(CGIterableMethodGenerator(descriptor,
                                                           idlNode.maplikeOrSetlikeOrIterable,
                                                           idlNode.identifier.name))
         else:
             cgThings.append(CGCallGenerator(
                 self.isFallible(),
                 idlNode.getExtendedAttribute('NeedsSubjectPrincipal'),
                 needsCallerType(idlNode),
+                isChromeOnly(idlNode),
                 self.getArguments(), argsPre, returnType,
                 self.extendedAttributes, descriptor,
                 nativeMethodName,
                 static, argsPost=argsPost, resultVar=resultVar))
 
         if useCounterName:
             # Generate a telemetry call for when [UseCounter] is used.
             code = "SetDocumentAndPageUseCounter(cx, obj, eUseCounter_%s);\n" % useCounterName
--- a/dom/bindings/test/TestFunctions.cpp
+++ b/dom/bindings/test/TestFunctions.cpp
@@ -1,20 +1,27 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 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/. */
 
 #include "mozilla/dom/TestFunctions.h"
 #include "mozilla/dom/TestFunctionsBinding.h"
+#include "nsStringBuffer.h"
 
 namespace mozilla {
 namespace dom {
 
+/* static */ TestFunctions*
+TestFunctions::Constructor(GlobalObject& aGlobal, ErrorResult& aRv)
+{
+  return new TestFunctions;
+}
+
 /* static */ void
 TestFunctions::ThrowUncatchableException(GlobalObject& aGlobal,
                                          ErrorResult& aRv)
 {
   aRv.ThrowUncatchableException();
 }
 
 /* static */ Promise*
@@ -26,10 +33,62 @@ TestFunctions::PassThroughPromise(Global
 /* static */ already_AddRefed<Promise>
 TestFunctions::PassThroughCallbackPromise(GlobalObject& aGlobal,
                                           PromiseReturner& aCallback,
                                           ErrorResult& aRv)
 {
   return aCallback.Call(aRv);
 }
 
+void
+TestFunctions::SetStringData(const nsAString& aString)
+{
+  mStringData = aString;
+}
+
+void
+TestFunctions::GetStringDataAsAString(nsAString& aString)
+{
+  aString = mStringData;
+}
+
+void
+TestFunctions::GetStringDataAsAString(uint32_t aLength, nsAString& aString)
+{
+  MOZ_RELEASE_ASSERT(aLength <= mStringData.Length(),
+                     "Bogus test passing in a too-big length");
+  aString.Assign(mStringData.BeginReading(), aLength);
+}
+
+void
+TestFunctions::GetStringDataAsDOMString(const Optional<uint32_t>& aLength,
+                                        DOMString& aString)
+{
+  uint32_t length;
+  if (aLength.WasPassed()) {
+    length = aLength.Value();
+    MOZ_RELEASE_ASSERT(length <= mStringData.Length(),
+                       "Bogus test passing in a too-big length");
+  } else {
+    length = mStringData.Length();
+  }
+
+  nsStringBuffer* buf = nsStringBuffer::FromString(mStringData);
+  if (buf) {
+    aString.SetStringBuffer(buf, length);
+    return;
+  }
+
+  // We better have an empty mStringData; otherwise why did we not have a string
+  // buffer?
+  MOZ_RELEASE_ASSERT(length == 0, "Why no stringbuffer?");
+  // No need to do anything here; aString is already empty.
+}
+
+bool
+TestFunctions::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+                          JS::MutableHandle<JSObject*> aWrapper)
+{
+  return TestFunctionsBinding::Wrap(aCx, this, aGivenProto, aWrapper);
+}
+
 }
 }
--- a/dom/bindings/test/TestFunctions.h
+++ b/dom/bindings/test/TestFunctions.h
@@ -5,33 +5,48 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_TestFunctions_h
 #define mozilla_dom_TestFunctions_h
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "nsString.h"
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
 class PromiseReturner;
 
 class TestFunctions : public NonRefcountedDOMObject {
 public:
+  static TestFunctions* Constructor(GlobalObject& aGlobal, ErrorResult& aRv);
+
   static void
   ThrowUncatchableException(GlobalObject& aGlobal, ErrorResult& aRv);
 
   static Promise*
   PassThroughPromise(GlobalObject& aGlobal, Promise& aPromise);
 
   static already_AddRefed<Promise>
   PassThroughCallbackPromise(GlobalObject& aGlobal,
                              PromiseReturner& aCallback,
                              ErrorResult& aRv);
+
+  void SetStringData(const nsAString& aString);
+
+  void GetStringDataAsAString(nsAString& aString);
+  void GetStringDataAsAString(uint32_t aLength, nsAString& aString);
+  void GetStringDataAsDOMString(const Optional<uint32_t>& aLength,
+                                DOMString& aString);
+
+  bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+                  JS::MutableHandle<JSObject*> aWrapper);
+private:
+  nsString mStringData;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_TestFunctions_h
--- a/dom/bindings/test/mochitest.ini
+++ b/dom/bindings/test/mochitest.ini
@@ -71,8 +71,10 @@ skip-if = debug == false
 [test_jsimplemented_eventhandler.html]
 skip-if = debug == false
 [test_iterable.html]
 skip-if = debug == false
 [test_oom_reporting.html]
 [test_domProxyArrayLengthGetter.html]
 [test_exceptionSanitization.html]
 skip-if = os == "android"
+[test_stringBindings.html]
+skip-if = debug == false
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/test_stringBindings.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1334537
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1334537</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 1334537 **/
+  SimpleTest.waitForExplicitFinish();
+
+  function go() {
+    // Need a new global that will pick up our pref.
+    var ifr = document.createElement("iframe");
+    document.body.appendChild(ifr);
+
+    var t = new ifr.contentWindow.TestFunctions();
+    var testString = "abcdefghijklmnopqrstuvwxyz";
+    const substringLength = 10;
+    var shortTestString = testString.substring(0, substringLength);
+
+    t.setStringData(testString);
+    // Note: we want to do all our gets before we start running code we don't
+    // control inside the test harness, if we really want to exercise the string
+    // cache in controlled ways.
+
+    var asShortDOMString = t.getStringDataAsDOMString(substringLength);
+    var asFullDOMString = t.getStringDataAsDOMString();
+    var asShortAString = t.getStringDataAsAString(substringLength);
+    var asAString = t.getStringDataAsAString();
+
+    is(asShortAString, shortTestString, "Short DOMString should be short");
+    is(asFullDOMString, testString, "Full DOMString should be test string");
+    is(asShortAString, shortTestString, "Short AString should be short");
+    is(asAString, testString, "Full AString should be test string");
+
+    SimpleTest.finish();
+  }
+
+  addLoadEvent(function() {
+    SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]},
+                              go);
+  });
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1334537">Mozilla Bug 1334537</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/cache/Cache.cpp
+++ b/dom/cache/Cache.cpp
@@ -320,17 +320,17 @@ Cache::MatchAll(const Optional<RequestOr
     }
   }
 
   return ExecuteOp(args, aRv);
 }
 
 already_AddRefed<Promise>
 Cache::Add(JSContext* aContext, const RequestOrUSVString& aRequest,
-           ErrorResult& aRv)
+           CallerType aCallerType, ErrorResult& aRv)
 {
   if (NS_WARN_IF(!mActor)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   CacheChild::AutoLock actorLock(mActor);
 
@@ -350,22 +350,23 @@ Cache::Add(JSContext* aContext, const Re
 
   nsAutoString url;
   request->GetUrl(url);
   if (NS_WARN_IF(!IsValidPutRequestURL(url, aRv))) {
     return nullptr;
   }
 
   requestList.AppendElement(Move(request));
-  return AddAll(global, Move(requestList), aRv);
+  return AddAll(global, Move(requestList), aCallerType, aRv);
 }
 
 already_AddRefed<Promise>
 Cache::AddAll(JSContext* aContext,
               const Sequence<OwningRequestOrUSVString>& aRequestList,
+              CallerType aCallerType,
               ErrorResult& aRv)
 {
   if (NS_WARN_IF(!mActor)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   CacheChild::AutoLock actorLock(mActor);
@@ -399,17 +400,17 @@ Cache::AddAll(JSContext* aContext,
     request->GetUrl(url);
     if (NS_WARN_IF(!IsValidPutRequestURL(url, aRv))) {
       return nullptr;
     }
 
     requestList.AppendElement(Move(request));
   }
 
-  return AddAll(global, Move(requestList), aRv);
+  return AddAll(global, Move(requestList), aCallerType, aRv);
 }
 
 already_AddRefed<Promise>
 Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
            ErrorResult& aRv)
 {
   if (NS_WARN_IF(!mActor)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
@@ -591,17 +592,18 @@ Cache::ExecuteOp(AutoChildOpArgs& aOpArg
   }
 
   mActor->ExecuteOp(mGlobal, promise, this, aOpArgs.SendAsOpArgs());
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 Cache::AddAll(const GlobalObject& aGlobal,
-              nsTArray<RefPtr<Request>>&& aRequestList, ErrorResult& aRv)
+              nsTArray<RefPtr<Request>>&& aRequestList,
+              CallerType aCallerType, ErrorResult& aRv)
 {
   MOZ_DIAGNOSTIC_ASSERT(mActor);
 
   // If there is no work to do, then resolve immediately
   if (aRequestList.IsEmpty()) {
     RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
     if (NS_WARN_IF(!promise)) {
       return nullptr;
@@ -617,17 +619,17 @@ Cache::AddAll(const GlobalObject& aGloba
   // Begin fetching each request in parallel.  For now, if an error occurs just
   // abandon our previous fetch calls.  In theory we could cancel them in the
   // future once fetch supports it.
 
   for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
     RequestOrUSVString requestOrString;
     requestOrString.SetAsRequest() = aRequestList[i];
     RefPtr<Promise> fetch = FetchRequest(mGlobal, requestOrString,
-                                           RequestInit(), aRv);
+                                         RequestInit(), aCallerType, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
     fetchList.AppendElement(Move(fetch));
   }
 
   RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
--- a/dom/cache/Cache.h
+++ b/dom/cache/Cache.h
@@ -24,16 +24,17 @@ namespace dom {
 
 class OwningRequestOrUSVString;
 class Promise;
 struct CacheQueryOptions;
 class RequestOrUSVString;
 class Response;
 template<typename T> class Optional;
 template<typename T> class Sequence;
+enum class CallerType : uint32_t;
 
 namespace cache {
 
 class AutoChildOpArgs;
 class CacheChild;
 
 class Cache final : public nsISupports
                   , public nsWrapperCache
@@ -46,20 +47,21 @@ public:
   already_AddRefed<Promise>
   Match(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
         ErrorResult& aRv);
   already_AddRefed<Promise>
   MatchAll(const Optional<RequestOrUSVString>& aRequest,
            const CacheQueryOptions& aOptions, ErrorResult& aRv);
   already_AddRefed<Promise>
   Add(JSContext* aContext, const RequestOrUSVString& aRequest,
-      ErrorResult& aRv);
+      CallerType aCallerType, ErrorResult& aRv);
   already_AddRefed<Promise>
   AddAll(JSContext* aContext,
-         const Sequence<OwningRequestOrUSVString>& aRequests, ErrorResult& aRv);
+         const Sequence<OwningRequestOrUSVString>& aRequests,
+         CallerType aCallerType, ErrorResult& aRv);
   already_AddRefed<Promise>
   Put(const RequestOrUSVString& aRequest, Response& aResponse,
       ErrorResult& aRv);
   already_AddRefed<Promise>
   Delete(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
          ErrorResult& aRv);
   already_AddRefed<Promise>
   Keys(const Optional<RequestOrUSVString>& aRequest,
@@ -93,17 +95,17 @@ private:
   // Called when we're destroyed or CCed.
   void DisconnectFromActor();
 
   already_AddRefed<Promise>
   ExecuteOp(AutoChildOpArgs& aOpArgs, ErrorResult& aRv);
 
   already_AddRefed<Promise>
   AddAll(const GlobalObject& aGlobal, nsTArray<RefPtr<Request>>&& aRequestList,
-         ErrorResult& aRv);
+         CallerType aCallerType, ErrorResult& aRv);
 
   already_AddRefed<Promise>
   PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
          const nsTArray<RefPtr<Response>>& aResponseList,
          ErrorResult& aRv);
 
   nsCOMPtr<nsIGlobalObject> mGlobal;
   CacheChild* mActor;
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5351,108 +5351,16 @@ CanvasRenderingContext2D::DrawWindow(nsG
   }
 
   // note that x and y are coordinates in the document that
   // we're drawing; x and y are drawn to 0,0 in current user
   // space.
   RedrawUser(gfxRect(0, 0, aW, aH));
 }
 
-void
-CanvasRenderingContext2D::AsyncDrawXULElement(nsXULElement& aElem,
-                                              double aX, double aY,
-                                              double aW, double aH,
-                                              const nsAString& aBgColor,
-                                              uint32_t aFlags,
-                                              ErrorResult& aError)
-{
-  // We can't allow web apps to call this until we fix at least the
-  // following potential security issues:
-  // -- rendering cross-domain IFRAMEs and then extracting the results
-  // -- rendering the user's theme and then extracting the results
-  // -- rendering native anonymous content (e.g., file input paths;
-  // scrollbars should be allowed)
-  if (!nsContentUtils::IsCallerChrome()) {
-    // not permitted to use DrawWindow
-    // XXX ERRMSG we need to report an error to developers here! (bug 329026)
-    aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
-    return;
-  }
-
-#if 0
-  nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(&elem);
-  if (!loaderOwner) {
-    aError.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  RefPtr<nsFrameLoader> frameloader = loaderOwner->GetFrameLoader();
-  if (!frameloader) {
-    aError.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  PBrowserParent *child = frameloader->GetRemoteBrowser();
-  if (!child) {
-    nsIDocShell* docShell = frameLoader->GetExistingDocShell();
-    if (!docShell) {
-      aError.Throw(NS_ERROR_FAILURE);
-      return;
-    }
-
-    nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow();
-    if (!window) {
-      aError.Throw(NS_ERROR_FAILURE);
-      return;
-    }
-
-    return DrawWindow(window->GetCurrentInnerWindow(), aX, aY, aW, aH,
-                      aBgColor, aFlags);
-  }
-
-  // protect against too-large surfaces that will cause allocation
-  // or overflow issues
-  if (!Factory::CheckSurfaceSize(IntSize(aW, aH), 0xffff)) {
-    aError.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  bool flush =
-    (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) == 0;
-
-  uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
-  if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) {
-    renderDocFlags |= nsIPresShell::RENDER_CARET;
-  }
-  if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) {
-    renderDocFlags &= ~nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
-  }
-
-  nsRect rect(nsPresContext::CSSPixelsToAppUnits(aX),
-              nsPresContext::CSSPixelsToAppUnits(aY),
-              nsPresContext::CSSPixelsToAppUnits(aW),
-              nsPresContext::CSSPixelsToAppUnits(aH));
-  if (mIPC) {
-    PDocumentRendererParent *pdocrender =
-      child->SendPDocumentRendererConstructor(rect,
-                                              mThebes->CurrentMatrix(),
-                                              nsString(aBGColor),
-                                              renderDocFlags, flush,
-                                              nsIntSize(mWidth, mHeight));
-    if (!pdocrender)
-      return NS_ERROR_FAILURE;
-
-    DocumentRendererParent *docrender =
-      static_cast<DocumentRendererParent *>(pdocrender);
-
-    docrender->SetCanvasContext(this, mThebes);
-  }
-#endif
-}
-
 //
 // device pixel getting/setting
 //
 
 already_AddRefed<ImageData>
 CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx,
                                        double aSy, double aSw,
                                        double aSh, ErrorResult& aError)
@@ -5465,17 +5373,21 @@ CanvasRenderingContext2D::GetImageData(J
     NS_ERROR("No canvas element and no docshell in GetImageData!!!");
     aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   // Check only if we have a canvas element; if we were created with a docshell,
   // then it's special internal use.
   if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
-      !nsContentUtils::IsCallerChrome())
+      // We could ask bindings for the caller type, but they already hand us a
+      // JSContext, and we're at least _somewhat_ perf-sensitive (so may not
+      // want to compute the caller type in the common non-write-only case), so
+      // let's just use what we have.
+      !nsContentUtils::IsSystemCaller(aCx))
   {
     // XXX ERRMSG we need to report an error to developers here! (bug 329026)
     aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   if (!IsFinite(aSx) || !IsFinite(aSy) ||
       !IsFinite(aSw) || !IsFinite(aSh)) {
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -395,19 +395,16 @@ public:
       CurrentState().imageSmoothingEnabled = aImageSmoothingEnabled;
     }
   }
 
   void DrawWindow(nsGlobalWindow& aWindow, double aX, double aY,
 		  double aW, double aH,
                   const nsAString& aBgColor, uint32_t aFlags,
                   mozilla::ErrorResult& aError);
-  void AsyncDrawXULElement(nsXULElement& aElem, double aX, double aY, double aW,
-                           double aH, const nsAString& aBgColor, uint32_t aFlags,
-                           mozilla::ErrorResult& aError);
 
   enum RenderingMode {
     SoftwareBackendMode,
     OpenGLBackendMode,
     DefaultBackendMode
   };
 
   bool SwitchRenderingMode(RenderingMode aRenderingMode);
--- a/dom/canvas/CanvasRenderingContextHelper.cpp
+++ b/dom/canvas/CanvasRenderingContextHelper.cpp
@@ -48,28 +48,38 @@ CanvasRenderingContextHelper::ToBlob(JSC
         AutoJSAPI jsapi;
         if (jsapi.Init(mGlobal)) {
           JS_updateMallocCounter(jsapi.cx(), size);
         }
       }
 
       RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
 
-      mBlobCallback->Call(*newBlob, rv);
+      mBlobCallback->Call(newBlob, rv);
 
       mGlobal = nullptr;
       mBlobCallback = nullptr;
 
       return rv.StealNSResult();
     }
 
     nsCOMPtr<nsIGlobalObject> mGlobal;
     RefPtr<BlobCallback> mBlobCallback;
   };
 
+   nsIntSize elemSize = GetWidthHeight();
+   if (elemSize.width == 0 || elemSize.height == 0) {
+     // According to spec, blob should return null if either its horizontal
+     // dimension or its vertical dimension is zero. See link below.
+     // https://html.spec.whatwg.org/multipage/scripting.html#dom-canvas-toblob
+     BlobCallback* blobCallback = &aCallback;
+     blobCallback->Call(nullptr, aRv);
+     return;
+   }
+
   RefPtr<EncodeCompleteCallback> callback =
     new EncodeCallback(aGlobal, &aCallback);
 
   ToBlob(aCx, aGlobal, callback, aType, aParams, aRv);
 }
 
 void
 CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -665,42 +665,45 @@ public:
     bool StartVRPresentation();
 
     ////
 
     webgl::PackingInfo
     ValidImplementationColorReadPI(const webgl::FormatUsageInfo* usage) const;
 
 protected:
-    bool ReadPixels_SharedPrecheck(ErrorResult* const out_error);
+    bool ReadPixels_SharedPrecheck(dom::CallerType aCallerType,
+                                   ErrorResult& out_error);
     void ReadPixelsImpl(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
                         GLenum type, void* data, uint32_t dataLen);
     bool DoReadPixelsAndConvert(const webgl::FormatInfo* srcFormat, GLint x, GLint y,
                                 GLsizei width, GLsizei height, GLenum format,
                                 GLenum destType, void* dest, uint32_t dataLen,
                                 uint32_t rowStride);
 public:
     void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
                     GLenum type, const dom::Nullable<dom::ArrayBufferView>& maybeView,
-                    ErrorResult& rv)
+                    dom::CallerType aCallerType, ErrorResult& rv)
     {
         const char funcName[] = "readPixels";
         if (maybeView.IsNull()) {
             ErrorInvalidValue("%s: `pixels` must not be null.", funcName);
             return;
         }
-        ReadPixels(x, y, width, height, format, type, maybeView.Value(), 0, rv);
+        ReadPixels(x, y, width, height, format, type, maybeView.Value(), 0,
+                   aCallerType, rv);
     }
 
     void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
-                    GLenum type, WebGLsizeiptr offset, ErrorResult& out_error);
+                    GLenum type, WebGLsizeiptr offset,
+                    dom::CallerType, ErrorResult& out_error);
 
     void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
                     GLenum type, const dom::ArrayBufferView& dstData, GLuint dstOffset,
-                    ErrorResult& out_error);
+                    dom::CallerType, ErrorResult& out_error);
 
     ////
 
     void RenderbufferStorage(GLenum target, GLenum internalFormat,
                              GLsizei width, GLsizei height);
 protected:
     void RenderbufferStorage_base(const char* funcName, GLenum target,
                                   GLsizei samples, GLenum internalformat,
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1238,27 +1238,28 @@ GetJSScalarFromGLType(GLenum type, js::S
         return true;
 
     default:
         return false;
     }
 }
 
 bool
-WebGLContext::ReadPixels_SharedPrecheck(ErrorResult* const out_error)
+WebGLContext::ReadPixels_SharedPrecheck(CallerType aCallerType,
+                                        ErrorResult& out_error)
 {
     if (IsContextLost())
         return false;
 
     if (mCanvasElement &&
         mCanvasElement->IsWriteOnly() &&
-        !nsContentUtils::IsCallerChrome())
+        aCallerType != CallerType::System)
     {
         GenerateWarning("readPixels: Not allowed");
-        out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
+        out_error.Throw(NS_ERROR_DOM_SECURITY_ERR);
         return false;
     }
 
     return true;
 }
 
 bool
 WebGLContext::ValidatePackSize(const char* funcName, uint32_t width, uint32_t height,
@@ -1301,20 +1302,21 @@ WebGLContext::ValidatePackSize(const cha
     *out_rowStride = rowStride.value();
     *out_endOffset = usedBytesPerImage.value();
     return true;
 }
 
 void
 WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
                          GLenum type, const dom::ArrayBufferView& dstView,
-                         GLuint dstElemOffset, ErrorResult& out_error)
+                         GLuint dstElemOffset, CallerType aCallerType,
+                         ErrorResult& out_error)
 {
     const char funcName[] = "readPixels";
-    if (!ReadPixels_SharedPrecheck(&out_error))
+    if (!ReadPixels_SharedPrecheck(aCallerType, out_error))
         return;
 
     if (mBoundPixelPackBuffer) {
         ErrorInvalidOperation("%s: PIXEL_PACK_BUFFER must be null.", funcName);
         return;
     }
 
     ////
@@ -1340,20 +1342,21 @@ WebGLContext::ReadPixels(GLint x, GLint 
 
     ////
 
     ReadPixelsImpl(x, y, width, height, format, type, bytes, byteLen);
 }
 
 void
 WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
-                         GLenum type, WebGLsizeiptr offset, ErrorResult& out_error)
+                         GLenum type, WebGLsizeiptr offset,
+                         CallerType aCallerType, ErrorResult& out_error)
 {
     const char funcName[] = "readPixels";
-    if (!ReadPixels_SharedPrecheck(&out_error))
+    if (!ReadPixels_SharedPrecheck(aCallerType, out_error))
         return;
 
     const auto& buffer = ValidateBufferSelection(funcName, LOCAL_GL_PIXEL_PACK_BUFFER);
     if (!buffer)
         return;
 
     //////
 
--- a/dom/canvas/test/mochitest.ini
+++ b/dom/canvas/test/mochitest.ini
@@ -246,16 +246,17 @@ tags = imagebitmap
 tags = imagebitmap
 [test_imagebitmap_transfer.html]
 tags = imagebitmap
 [test_ImageData_ctor.html]
 [test_isPointInStroke.html]
 [test_mozGetAsFile.html]
 [test_strokeText_throw.html]
 [test_toBlob.html]
+[test_toBlob_zero_dimension.html]
 [test_toDataURL_alpha.html]
 [test_toDataURL_lowercase_ascii.html]
 [test_toDataURL_parameters.html]
 [test_windingRuleUndefined.html]
 [test_2d.fillText.gradient.html]
 [test_2d_composite_canvaspattern_setTransform.html]
 [test_createPattern_broken.html]
 [test_filter.html]
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_toBlob_zero_dimension.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<title>Canvas test: toBlob</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<canvas id="c" width="100" height="0"></canvas>
+<script>
+
+function testblob(blob) {
+  is(blob, null, "Null blob expected");
+}
+
+function test1(canvas)
+{
+  canvas.toBlob(function(blob) {
+    testblob(blob);
+    test2(canvas);
+  }, "image/png");
+}
+
+function test2(canvas)
+{
+  canvas.width = 0;
+  canvas.height = 100;
+  canvas.toBlob(function(blob) {
+    testblob(blob);
+    SimpleTest.finish();
+  }, "image/png");
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function () {
+
+var canvas = document.getElementById('c');
+var ctx = canvas.getContext('2d');
+test1(canvas);
+
+});
+</script>
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -32,20 +32,16 @@
 #include "nsJSEnvironment.h"
 #include "nsLayoutUtils.h"
 #include "nsPIWindowRoot.h"
 #include "WorkerPrivate.h"
 
 namespace mozilla {
 namespace dom {
 
-namespace workers {
-extern bool IsCurrentThreadRunningChromeWorker();
-} // namespace workers
-
 static char *sPopupAllowedEvents;
 
 static bool sReturnHighResTimeStamp = false;
 static bool sReturnHighResTimeStampIsSet = false;
 
 Event::Event(EventTarget* aOwner,
              nsPresContext* aPresContext,
              WidgetEvent* aEvent)
@@ -362,17 +358,17 @@ Event::SetTrusted(bool aTrusted)
 {
   mEvent->mFlags.mIsTrusted = aTrusted;
 }
 
 bool
 Event::Init(mozilla::dom::EventTarget* aGlobal)
 {
   if (!mIsMainThreadEvent) {
-    return nsContentUtils::ThreadsafeIsCallerChrome();
+    return workers::IsCurrentThreadRunningChromeWorker();
   }
   bool trusted = false;
   nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(aGlobal);
   if (w) {
     nsCOMPtr<nsIDocument> d = w->GetExtantDoc();
     if (d) {
       trusted = nsContentUtils::IsChromeDoc(d);
       nsIPresShell* s = d->GetShell();
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -15,16 +15,17 @@
 #include "nsCharSeparatedTokenizer.h"
 #include "nsDOMString.h"
 #include "nsNetUtil.h"
 #include "nsReadableUtils.h"
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
 
 #include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/BodyUtil.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/FetchDriver.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FormData.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/MutableBlobStreamListener.h"
 #include "mozilla/dom/Promise.h"
@@ -171,30 +172,28 @@ public:
     // ...but release it before calling Fetch, because mResolver's callback can
     // be called synchronously and they want the mutex, too.
     return fetch->Fetch(mResolver);
   }
 };
 
 already_AddRefed<Promise>
 FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
-             const RequestInit& aInit, ErrorResult& aRv)
+             const RequestInit& aInit, CallerType aCallerType, ErrorResult& aRv)
 {
   RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   // Double check that we have chrome privileges if the Request's content
-  // policy type has been overridden.  Note, we must do this before
-  // entering the global below.  Otherwise the IsCallerChrome() will
-  // always fail.
+  // policy type has been overridden.
   MOZ_ASSERT_IF(aInput.IsRequest() &&
                 aInput.GetAsRequest().IsContentPolicyTypeOverridden(),
-                nsContentUtils::IsCallerChrome());
+                aCallerType == CallerType::System);
 
   AutoJSAPI jsapi;
   if (!jsapi.Init(aGlobal)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   JSContext* cx = jsapi.cx();
--- a/dom/fetch/Fetch.h
+++ b/dom/fetch/Fetch.h
@@ -27,24 +27,26 @@ class nsIGlobalObject;
 namespace mozilla {
 namespace dom {
 
 class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
 class BlobImpl;
 class InternalRequest;
 class OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
 class RequestOrUSVString;
+enum class CallerType : uint32_t;
 
 namespace workers {
 class WorkerPrivate;
 } // namespace workers
 
 already_AddRefed<Promise>
 FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
-             const RequestInit& aInit, ErrorResult& aRv);
+             const RequestInit& aInit, CallerType aCallerType,
+             ErrorResult& aRv);
 
 nsresult
 UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest);
 
 /*
  * Creates an nsIInputStream based on the fetch specifications 'extract a byte
  * stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract.
  * Stores content type in out param aContentType.
--- a/dom/file/File.cpp
+++ b/dom/file/File.cpp
@@ -494,19 +494,20 @@ File::GetLastModifiedDate(ErrorResult& a
 
 int64_t
 File::GetLastModified(ErrorResult& aRv)
 {
   return mImpl->GetLastModified(aRv);
 }
 
 void
-File::GetMozFullPath(nsAString& aFilename, ErrorResult& aRv) const
+File::GetMozFullPath(nsAString& aFilename, SystemCallerGuarantee aGuarantee,
+                     ErrorResult& aRv) const
 {
-  mImpl->GetMozFullPath(aFilename, aRv);
+  mImpl->GetMozFullPath(aFilename, aGuarantee, aRv);
 }
 
 void
 File::GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const
 {
   mImpl->GetMozFullPathInternal(aFileName, aRv);
 }
 
@@ -575,28 +576,25 @@ File::Constructor(const GlobalObject& aG
   RefPtr<File> file = new File(aGlobal.GetAsSupports(), impl);
   return file.forget();
 }
 
 /* static */ already_AddRefed<File>
 File::CreateFromNsIFile(const GlobalObject& aGlobal,
                         nsIFile* aData,
                         const ChromeFilePropertyBag& aBag,
+                        SystemCallerGuarantee aGuarantee,
                         ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (!nsContentUtils::IsCallerChrome()) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
 
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
 
   RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(EmptyString());
-  impl->InitializeChromeFile(window, aData, aBag, true, aRv);
+  impl->InitializeChromeFile(window, aData, aBag, true, aGuarantee, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
   MOZ_ASSERT(impl->IsFile());
 
   if (aBag.mLastModified.WasPassed()) {
     impl->SetLastModified(aBag.mLastModified.Value());
   }
@@ -604,27 +602,23 @@ File::CreateFromNsIFile(const GlobalObje
   RefPtr<File> domFile = new File(aGlobal.GetAsSupports(), impl);
   return domFile.forget();
 }
 
 /* static */ already_AddRefed<File>
 File::CreateFromFileName(const GlobalObject& aGlobal,
                          const nsAString& aData,
                          const ChromeFilePropertyBag& aBag,
+                         SystemCallerGuarantee aGuarantee,
                          ErrorResult& aRv)
 {
-  if (!nsContentUtils::ThreadsafeIsCallerChrome()) {
-    aRv.ThrowTypeError<MSG_MISSING_ARGUMENTS>(NS_LITERAL_STRING("File"));
-    return nullptr;
-  }
-
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
 
   RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(EmptyString());
-  impl->InitializeChromeFile(window, aData, aBag, aRv);
+  impl->InitializeChromeFile(window, aData, aBag, aGuarantee, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
   MOZ_ASSERT(impl->IsFile());
 
   if (aBag.mLastModified.WasPassed()) {
     impl->SetLastModified(aBag.mLastModified.Value());
   }
@@ -685,36 +679,23 @@ BlobImplBase::GetPath(nsAString& aPath) 
 void
 BlobImplBase::SetPath(const nsAString& aPath)
 {
   MOZ_ASSERT(mIsFile, "Should only be called on files");
   mPath = aPath;
 }
 
 void
-BlobImplBase::GetMozFullPath(nsAString& aFileName, ErrorResult& aRv) const
+BlobImplBase::GetMozFullPath(nsAString& aFileName,
+                             SystemCallerGuarantee /* unused */,
+                             ErrorResult& aRv) const
 {
   MOZ_ASSERT(mIsFile, "Should only be called on files");
 
-  aFileName.Truncate();
-
-  if (NS_IsMainThread()) {
-    if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
-      GetMozFullPathInternal(aFileName, aRv);
-    }
-
-    return;
-  }
-
-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(workerPrivate);
-
-  if (workerPrivate->UsesSystemPrincipal()) {
-    GetMozFullPathInternal(aFileName, aRv);
-  }
+  GetMozFullPathInternal(aFileName, aRv);
 }
 
 void
 BlobImplBase::GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const
 {
   if (!mIsFile) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
--- a/dom/file/File.h
+++ b/dom/file/File.h
@@ -203,40 +203,43 @@ public:
               const FilePropertyBag& aBag,
               ErrorResult& aRv);
 
   // ChromeOnly
   static already_AddRefed<File>
   CreateFromFileName(const GlobalObject& aGlobal,
                      const nsAString& aData,
                      const ChromeFilePropertyBag& aBag,
+                     SystemCallerGuarantee aGuarantee,
                      ErrorResult& aRv);
 
   // ChromeOnly
   static already_AddRefed<File>
   CreateFromNsIFile(const GlobalObject& aGlobal,
                     nsIFile* aData,
                     const ChromeFilePropertyBag& aBag,
+                    SystemCallerGuarantee aGuarantee,
                     ErrorResult& aRv);
 
   void GetName(nsAString& aName) const;
 
   int64_t GetLastModified(ErrorResult& aRv);
 
   Date GetLastModifiedDate(ErrorResult& aRv);
 
   // GetPath and SetPath are currently used only for the webkitRelativePath
   // attribute and they are only used when this File object is created from a
   // Directory, generated by a Directory Picker.
 
   void GetPath(nsAString& aName) const;
 
   void SetPath(const nsAString& aName);
 
-  void GetMozFullPath(nsAString& aFilename, ErrorResult& aRv) const;
+  void GetMozFullPath(nsAString& aFilename, SystemCallerGuarantee aGuarantee,
+                      ErrorResult& aRv) const;
 
   void GetMozFullPathInternal(nsAString& aName, ErrorResult& aRv) const;
 
 protected:
   virtual bool HasFileInterface() const override { return true; }
 
 private:
   // File constructor should never be used directly. Use Blob::Create or
@@ -260,17 +263,19 @@ public:
   virtual void GetPath(nsAString& aName) const = 0;
 
   virtual void SetPath(const nsAString& aName) = 0;
 
   virtual int64_t GetLastModified(ErrorResult& aRv) = 0;
 
   virtual void SetLastModified(int64_t aLastModified) = 0;
 
-  virtual void GetMozFullPath(nsAString& aName, ErrorResult& aRv) const = 0;
+  virtual void GetMozFullPath(nsAString& aName,
+                              SystemCallerGuarantee /* unused */,
+                              ErrorResult& aRv) const = 0;
 
   virtual void GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const = 0;
 
   virtual uint64_t GetSize(ErrorResult& aRv) = 0;
 
   virtual void GetType(nsAString& aType) = 0;
 
   /**
@@ -404,17 +409,19 @@ public:
   virtual void GetPath(nsAString& aName) const override;
 
   virtual void SetPath(const nsAString& aName) override;
 
   virtual int64_t GetLastModified(ErrorResult& aRv) override;
 
   virtual void SetLastModified(int64_t aLastModified) override;
 
-  virtual void GetMozFullPath(nsAString& aName, ErrorResult& aRv) const override;
+  virtual void GetMozFullPath(nsAString& aName,
+                              SystemCallerGuarantee /* unused */,
+                              ErrorResult& aRv) const override;
 
   virtual void GetMozFullPathInternal(nsAString& aFileName,
                                       ErrorResult& aRv) const override;
 
   virtual uint64_t GetSize(ErrorResult& aRv) override
   {
     return mLength;
   }
--- a/dom/file/MultipartBlobImpl.cpp
+++ b/dom/file/MultipartBlobImpl.cpp
@@ -323,26 +323,25 @@ MultipartBlobImpl::SetMutable(bool aMuta
   return NS_OK;
 }
 
 void
 MultipartBlobImpl::InitializeChromeFile(nsPIDOMWindowInner* aWindow,
                                         nsIFile* aFile,
                                         const ChromeFilePropertyBag& aBag,
                                         bool aIsFromNsIFile,
+                                        SystemCallerGuarantee /* unused */,
                                         ErrorResult& aRv)
 {
   MOZ_ASSERT(!mImmutable, "Something went wrong ...");
   if (mImmutable) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 
-  MOZ_ASSERT(nsContentUtils::IsCallerChrome());
-
   mName = aBag.mName;
   mContentType = aBag.mType;
   mIsFromNsIFile = aIsFromNsIFile;
 
   bool exists;
   aRv = aFile->Exists(&exists);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
@@ -394,25 +393,26 @@ MultipartBlobImpl::InitializeChromeFile(
   SetLengthAndModifiedDate(aRv);
   NS_WARNING_ASSERTION(!aRv.Failed(), "SetLengthAndModifiedDate failed");
 }
 
 void
 MultipartBlobImpl::InitializeChromeFile(nsPIDOMWindowInner* aWindow,
                                         const nsAString& aData,
                                         const ChromeFilePropertyBag& aBag,
+                                        SystemCallerGuarantee aGuarantee,
                                         ErrorResult& aRv)
 {
   nsCOMPtr<nsIFile> file;
   aRv = NS_NewLocalFile(aData, false, getter_AddRefs(file));
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  InitializeChromeFile(aWindow, file, aBag, false, aRv);
+  InitializeChromeFile(aWindow, file, aBag, false, aGuarantee, aRv);
 }
 
 bool
 MultipartBlobImpl::MayBeClonedToOtherThreads() const
 {
   for (uint32_t i = 0; i < mBlobImpls.Length(); ++i) {
     if (!mBlobImpls[i]->MayBeClonedToOtherThreads()) {
       return false;
--- a/dom/file/MultipartBlobImpl.h
+++ b/dom/file/MultipartBlobImpl.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_dom_MultipartBlobImpl_h
 #define mozilla_dom_MultipartBlobImpl_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Move.h"
 #include "mozilla/dom/File.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/FileBinding.h"
 #include <algorithm>
 #include "nsPIDOMWindow.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -58,22 +59,24 @@ public:
                       const Sequence<Blob::BlobPart>& aData,
                       const nsAString& aContentType,
                       bool aNativeEOL,
                       ErrorResult& aRv);
 
   void InitializeChromeFile(nsPIDOMWindowInner* aWindow,
                             const nsAString& aData,
                             const ChromeFilePropertyBag& aBag,
+                            SystemCallerGuarantee aGuarantee,
                             ErrorResult& aRv);
 
   void InitializeChromeFile(nsPIDOMWindowInner* aWindow,
                             nsIFile* aData,
                             const ChromeFilePropertyBag& aBag,
                             bool aIsFromNsIFile,
+                            SystemCallerGuarantee /* unused */,
                             ErrorResult& aRv);
 
   virtual already_AddRefed<BlobImpl>
   CreateSlice(uint64_t aStart, uint64_t aLength,
               const nsAString& aContentType,
               ErrorResult& aRv) override;
 
   virtual uint64_t GetSize(ErrorResult& aRv) override
--- a/dom/file/ipc/Blob.cpp
+++ b/dom/file/ipc/Blob.cpp
@@ -2078,17 +2078,18 @@ public:
 
   int64_t
   GetLastModified(ErrorResult& aRv) override;
 
   void
   SetLastModified(int64_t aLastModified) override;
 
   void
-  GetMozFullPath(nsAString& aName, ErrorResult& aRv) const override;
+  GetMozFullPath(nsAString& aName, SystemCallerGuarantee aGuarantee,
+                 ErrorResult& aRv) const override;
 
   void
   GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const override;
 
   bool
   IsDirectory() const override;
 
   uint64_t
@@ -2865,19 +2866,21 @@ void
 BlobParent::
 RemoteBlobImpl::SetLastModified(int64_t aLastModified)
 {
   MOZ_CRASH("SetLastModified of a remote blob is not allowed!");
 }
 
 void
 BlobParent::
-RemoteBlobImpl::GetMozFullPath(nsAString& aName, ErrorResult& aRv) const
+RemoteBlobImpl::GetMozFullPath(nsAString& aName,
+                               SystemCallerGuarantee aGuarantee,
+                               ErrorResult& aRv) const
 {
-  mBlobImpl->GetMozFullPath(aName, aRv);
+  mBlobImpl->GetMozFullPath(aName, aGuarantee, aRv);
 }
 
 void
 BlobParent::
 RemoteBlobImpl::GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const
 {
   mBlobImpl->GetMozFullPathInternal(aFileName, aRv);
 }
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -2800,17 +2800,22 @@ nsHTMLDocument::EditingStateChanged()
     // Set the editor to not insert br's on return when in p
     // elements by default.
     // XXX Do we only want to do this for designMode?
     // Note that it doesn't matter what CallerType we pass, because the callee
     // doesn't use it for this command.  Play it safe and pass the more
     // restricted one.
     ErrorResult errorResult;
     Unused << ExecCommand(NS_LITERAL_STRING("insertBrOnReturn"), false,
-                          NS_LITERAL_STRING("false"), CallerType::NonSystem,
+                          NS_LITERAL_STRING("false"),
+                          // Principal doesn't matter here, because the
+                          // insertBrOnReturn command doesn't use it.   Still
+                          // it's too bad we can't easily grab a nullprincipal
+                          // from somewhere without allocating one..
+                          *NodePrincipal(),
                           errorResult);
 
     if (errorResult.Failed()) {
       // Editor setup failed. Editing is not on after all.
       // XXX Should we reset the editable flag on nodes?
       editSession->TearDownEditorOnWindow(window);
       mEditingState = eOff;
 
@@ -3129,17 +3134,17 @@ ConvertToMidasInternalCommand(const nsAS
                                             outCommandID, dummyCString,
                                             dummyBool, dummyBool, true);
 }
 
 bool
 nsHTMLDocument::ExecCommand(const nsAString& commandID,
                             bool doShowUI,
                             const nsAString& value,
-                            CallerType aCallerType,
+                            nsIPrincipal& aSubjectPrincipal,
                             ErrorResult& rv)
 {
   //  for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
   //  this might add some ugly JS dependencies?
 
   nsAutoCString cmdToDispatch, paramStr;
   bool isBool, boolVal;
   if (!ConvertToMidasInternalCommand(commandID, value,
@@ -3159,17 +3164,17 @@ nsHTMLDocument::ExecCommand(const nsAStr
   // if they are requesting UI from us, let's fail since we have no UI
   if (doShowUI) {
     return false;
   }
 
   // special case for cut & copy
   // cut & copy are allowed in non editable documents
   if (isCutCopy) {
-    if (!nsContentUtils::IsCutCopyAllowed()) {
+    if (!nsContentUtils::IsCutCopyAllowed(&aSubjectPrincipal)) {
       // We have rejected the event due to it not being performed in an
       // input-driven context therefore, we report the error to the console.
       nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                       NS_LITERAL_CSTRING("DOM"), this,
                                       nsContentUtils::eDOM_PROPERTIES,
                                       "ExecCommandCutCopyDeniedNotInputDriven");
       return false;
     }
@@ -3189,17 +3194,17 @@ nsHTMLDocument::ExecCommand(const nsAStr
   }
 
   if (commandID.LowerCaseEqualsLiteral("gethtml")) {
     rv.Throw(NS_ERROR_FAILURE);
     return false;
   }
 
   bool restricted = commandID.LowerCaseEqualsLiteral("paste");
-  if (restricted && aCallerType != CallerType::System) {
+  if (restricted && !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal)) {
     return false;
   }
 
   // get command manager and dispatch command to our window if it's acceptable
   nsCOMPtr<nsICommandManager> cmdMgr;
   GetMidasCommandManager(getter_AddRefs(cmdMgr));
   if (!cmdMgr) {
     rv.Throw(NS_ERROR_FAILURE);
@@ -3255,34 +3260,34 @@ nsHTMLDocument::ExecCommand(const nsAStr
     rv = cmdMgr->DoCommand(cmdToDispatch.get(), cmdParams, window);
   }
 
   return !rv.Failed();
 }
 
 bool
 nsHTMLDocument::QueryCommandEnabled(const nsAString& commandID,
-                                    CallerType aCallerType,
+                                    nsIPrincipal& aSubjectPrincipal,
                                     ErrorResult& rv)
 {
   nsAutoCString cmdToDispatch;
   if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
     return false;
   }
 
   // cut & copy are always allowed
   bool isCutCopy = commandID.LowerCaseEqualsLiteral("cut") ||
                    commandID.LowerCaseEqualsLiteral("copy");
   if (isCutCopy) {
-    return nsContentUtils::IsCutCopyAllowed();
+    return nsContentUtils::IsCutCopyAllowed(&aSubjectPrincipal);
   }
 
   // Report false for restricted commands
   bool restricted = commandID.LowerCaseEqualsLiteral("paste");
-  if (restricted && aCallerType != CallerType::System) {
+  if (restricted && !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal)) {
     return false;
   }
 
   // if editing is not on, bail
   if (!IsEditingOnAfterFlush()) {
     return false;
   }
 
@@ -3455,16 +3460,19 @@ nsHTMLDocument::QueryCommandSupported(co
   // may also be disallowed to be called from non-privileged content.
   // For that reason, we report the support status of corresponding
   // command accordingly.
   if (aCallerType != CallerType::System) {
     if (commandID.LowerCaseEqualsLiteral("paste")) {
       return false;
     }
     if (nsContentUtils::IsCutCopyRestricted()) {
+      // XXXbz should we worry about correctly reporting "true" in the
+      // "restricted, but we're an addon with clipboardWrite permissions" case?
+      // See also nsContentUtils::IsCutCopyAllowed.
       if (commandID.LowerCaseEqualsLiteral("cut") ||
           commandID.LowerCaseEqualsLiteral("copy")) {
         return false;
       }
     }
   }
 
   // commandID is supported if it can be converted to a Midas command
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -213,20 +213,20 @@ public:
   void SetDesignMode(const nsAString& aDesignMode,
                      nsIPrincipal& aSubjectPrincipal,
                      mozilla::ErrorResult& rv);
   void SetDesignMode(const nsAString& aDesignMode,
                      const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
                      mozilla::ErrorResult& rv);
   bool ExecCommand(const nsAString& aCommandID, bool aDoShowUI,
                    const nsAString& aValue,
-                   mozilla::dom::CallerType aCallerType,
+                   nsIPrincipal& aSubjectPrincipal,
                    mozilla::ErrorResult& rv);
   bool QueryCommandEnabled(const nsAString& aCommandID,
-                           mozilla::dom::CallerType aCallerType,
+                           nsIPrincipal& aSubjectPrincipal,
                            mozilla::ErrorResult& rv);
   bool QueryCommandIndeterm(const nsAString& aCommandID,
                             mozilla::ErrorResult& rv);
   bool QueryCommandState(const nsAString& aCommandID, mozilla::ErrorResult& rv);
   bool QueryCommandSupported(const nsAString& aCommandID,
                              mozilla::dom::CallerType aCallerType);
   void QueryCommandValue(const nsAString& aCommandID, nsAString& aValue,
                          mozilla::ErrorResult& rv);
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -436,17 +436,17 @@ support-files =
 [test_formData.html]
 [test_formSubmission.html]
 skip-if = toolkit == 'android' #TIMED_OUT
 [test_formSubmission2.html]
 skip-if = toolkit == 'android'
 [test_formelements.html]
 [test_fullscreen-api.html]
 tags = fullscreen
-skip-if = toolkit == 'android'
+skip-if = toolkit == 'android' || (e10s && os == 'linux') # Bug 1307347
 support-files =
   file_fullscreen-api.html
   file_fullscreen-backdrop.html
   file_fullscreen-denied-inner.html
   file_fullscreen-denied.html
   file_fullscreen-esc-exit-inner.html
   file_fullscreen-esc-exit.html
   file_fullscreen-hidden.html
--- a/dom/indexedDB/FileSnapshot.h
+++ b/dom/indexedDB/FileSnapshot.h
@@ -79,19 +79,20 @@ private:
 
   virtual void
   SetLastModified(int64_t aLastModified) override
   {
     mBlobImpl->SetLastModified(aLastModified);
   }
 
   virtual void
-  GetMozFullPath(nsAString& aName, ErrorResult& aRv) const override
+  GetMozFullPath(nsAString& aName, SystemCallerGuarantee aGuarantee,
+                 ErrorResult& aRv) const override
   {
-    mBlobImpl->GetMozFullPath(aName, aRv);
+    mBlobImpl->GetMozFullPath(aName, aGuarantee, aRv);
   }
 
   virtual void
   GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const override
   {
     mBlobImpl->GetMozFullPathInternal(aFileName, aRv);
   }
 
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -449,54 +449,60 @@ IDBFactory::IncrementParentLoggingReques
 
   mBackgroundActor->SendIncrementLoggingRequestSerialNumber();
 }
 
 already_AddRefed<IDBOpenDBRequest>
 IDBFactory::Open(JSContext* aCx,
                  const nsAString& aName,
                  uint64_t aVersion,
+                 CallerType aCallerType,
                  ErrorResult& aRv)
 {
   return OpenInternal(aCx,
                       /* aPrincipal */ nullptr,
                       aName,
                       Optional<uint64_t>(aVersion),
                       Optional<StorageType>(),
                       /* aDeleting */ false,
+                      aCallerType,
                       aRv);
 }
 
 already_AddRefed<IDBOpenDBRequest>
 IDBFactory::Open(JSContext* aCx,
                  const nsAString& aName,
                  const IDBOpenDBOptions& aOptions,
+                 CallerType aCallerType,
                  ErrorResult& aRv)
 {
   return OpenInternal(aCx,
                       /* aPrincipal */ nullptr,
                       aName,
                       aOptions.mVersion,
                       aOptions.mStorage,
                       /* aDeleting */ false,
+                      aCallerType,
                       aRv);
 }
 
 already_AddRefed<IDBOpenDBRequest>
 IDBFactory::DeleteDatabase(JSContext* aCx,
                            const nsAString& aName,
                            const IDBOpenDBOptions& aOptions,
+                           CallerType aCallerType,
                            ErrorResult& aRv)
 {
   return OpenInternal(aCx,
                       /* aPrincipal */ nullptr,
                       aName,
                       Optional<uint64_t>(),
                       aOptions.mStorage,
                       /* aDeleting */ true,
+                      aCallerType,
                       aRv);
 }
 
 int16_t
 IDBFactory::Cmp(JSContext* aCx, JS::Handle<JS::Value> aFirst,
                 JS::Handle<JS::Value> aSecond, ErrorResult& aRv)
 {
   Key first, second;
@@ -520,99 +526,107 @@ IDBFactory::Cmp(JSContext* aCx, JS::Hand
   return Key::CompareKeys(first, second);
 }
 
 already_AddRefed<IDBOpenDBRequest>
 IDBFactory::OpenForPrincipal(JSContext* aCx,
                              nsIPrincipal* aPrincipal,
                              const nsAString& aName,
                              uint64_t aVersion,
+                             SystemCallerGuarantee aGuarantee,
                              ErrorResult& aRv)
 {
   MOZ_ASSERT(aPrincipal);
   if (!NS_IsMainThread()) {
-    MOZ_CRASH("Figure out security checks for workers!");
+    MOZ_CRASH("Figure out security checks for workers!  What's this aPrincipal "
+              "we have on a worker thread?");
   }
-  MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
   return OpenInternal(aCx,
                       aPrincipal,
                       aName,
                       Optional<uint64_t>(aVersion),
                       Optional<StorageType>(),
                       /* aDeleting */ false,
+                      aGuarantee,
                       aRv);
 }
 
 already_AddRefed<IDBOpenDBRequest>
 IDBFactory::OpenForPrincipal(JSContext* aCx,
                              nsIPrincipal* aPrincipal,
                              const nsAString& aName,
                              const IDBOpenDBOptions& aOptions,
+                             SystemCallerGuarantee aGuarantee,
                              ErrorResult& aRv)
 {
   MOZ_ASSERT(aPrincipal);
   if (!NS_IsMainThread()) {
-    MOZ_CRASH("Figure out security checks for workers!");
+    MOZ_CRASH("Figure out security checks for workers!  What's this aPrincipal "
+              "we have on a worker thread?");
   }
-  MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
   return OpenInternal(aCx,
                       aPrincipal,
                       aName,
                       aOptions.mVersion,
                       aOptions.mStorage,
                       /* aDeleting */ false,
+                      aGuarantee,
                       aRv);
 }
 
 already_AddRefed<IDBOpenDBRequest>
 IDBFactory::DeleteForPrincipal(JSContext* aCx,
                                nsIPrincipal* aPrincipal,
                                const nsAString& aName,
                                const IDBOpenDBOptions& aOptions,
+                               SystemCallerGuarantee aGuarantee,
                                ErrorResult& aRv)
 {
   MOZ_ASSERT(aPrincipal);
   if (!NS_IsMainThread()) {
-    MOZ_CRASH("Figure out security checks for workers!");
+    MOZ_CRASH("Figure out security checks for workers!  What's this aPrincipal "
+              "we have on a worker thread?");
   }
-  MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
   return OpenInternal(aCx,
                       aPrincipal,
                       aName,
                       Optional<uint64_t>(),
                       aOptions.mStorage,
                       /* aDeleting */ true,
+                      aGuarantee,
                       aRv);
 }
 
 already_AddRefed<IDBOpenDBRequest>
 IDBFactory::OpenInternal(JSContext* aCx,
                          nsIPrincipal* aPrincipal,
                          const nsAString& aName,
                          const Optional<uint64_t>& aVersion,
                          const Optional<StorageType>& aStorageType,
                          bool aDeleting,
+                         CallerType aCallerType,
                          ErrorResult& aRv)
 {
   MOZ_ASSERT(mWindow || mOwningObject);
   MOZ_ASSERT_IF(!mWindow, !mPrivateBrowsingMode);
 
   CommonFactoryRequestParams commonParams;
   commonParams.privateBrowsingMode() = mPrivateBrowsingMode;
 
   PrincipalInfo& principalInfo = commonParams.principalInfo();
 
   if (aPrincipal) {
     if (!NS_IsMainThread()) {
-      MOZ_CRASH("Figure out security checks for workers!");
+      MOZ_CRASH("Figure out security checks for workers!  What's this "
+                "aPrincipal we have on a worker thread?");
     }
-    MOZ_ASSERT(nsContentUtils::IsCallerChrome());
+    MOZ_ASSERT(aCallerType == CallerType::System);
     MOZ_DIAGNOSTIC_ASSERT(mPrivateBrowsingMode == (aPrincipal->GetPrivateBrowsingId() > 0));
 
     if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(aPrincipal,
                                                       &principalInfo)))) {
       IDB_REPORT_INTERNAL_ERR();
       aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
       return nullptr;
     }
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -3,16 +3,17 @@
 /* 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 mozilla_dom_idbfactory_h__
 #define mozilla_dom_idbfactory_h__
 
 #include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/StorageTypeBinding.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsISupports.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
@@ -33,16 +34,17 @@ class PrincipalInfo;
 } // namespace ipc
 
 namespace dom {
 
 struct IDBOpenDBOptions;
 class IDBOpenDBRequest;
 template <typename> class Optional;
 class TabChild;
+enum class CallerType : uint32_t;
 
 namespace indexedDB {
 class BackgroundFactoryChild;
 class FactoryRequestParams;
 class LoggingInfo;
 }
 
 class IDBFactory final
@@ -157,55 +159,61 @@ public:
 
   bool
   IsChrome() const;
 
   already_AddRefed<IDBOpenDBRequest>
   Open(JSContext* aCx,
        const nsAString& aName,
        uint64_t aVersion,
+       CallerType aCallerType,
        ErrorResult& aRv);
 
   already_AddRefed<IDBOpenDBRequest>
   Open(JSContext* aCx,
        const nsAString& aName,
        const IDBOpenDBOptions& aOptions,
+       CallerType aCallerType,
        ErrorResult& aRv);
 
   already_AddRefed<IDBOpenDBRequest>
   DeleteDatabase(JSContext* aCx,
                  const nsAString& aName,
                  const IDBOpenDBOptions& aOptions,
+                 CallerType aCallerType,
                  ErrorResult& aRv);
 
   int16_t
   Cmp(JSContext* aCx,
       JS::Handle<JS::Value> aFirst,
       JS::Handle<JS::Value> aSecond,
       ErrorResult& aRv);
 
   already_AddRefed<IDBOpenDBRequest>
   OpenForPrincipal(JSContext* aCx,
                    nsIPrincipal* aPrincipal,
                    const nsAString& aName,
                    uint64_t aVersion,
+                   SystemCallerGuarantee,
                    ErrorResult& aRv);
 
   already_AddRefed<IDBOpenDBRequest>
   OpenForPrincipal(JSContext* aCx,
                    nsIPrincipal* aPrincipal,
                    const nsAString& aName,
                    const IDBOpenDBOptions& aOptions,
+                   SystemCallerGuarantee,
                    ErrorResult& aRv);
 
   already_AddRefed<IDBOpenDBRequest>
   DeleteForPrincipal(JSContext* aCx,
                      nsIPrincipal* aPrincipal,
                      const nsAString& aName,
                      const IDBOpenDBOptions& aOptions,
+                     SystemCallerGuarantee,
                      ErrorResult& aRv);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IDBFactory)
 
   // nsWrapperCache
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
@@ -233,16 +241,17 @@ private:
 
   already_AddRefed<IDBOpenDBRequest>
   OpenInternal(JSContext* aCx,
                nsIPrincipal* aPrincipal,
                const nsAString& aName,
                const Optional<uint64_t>& aVersion,
                const Optional<StorageType>& aStorageType,
                bool aDeleting,
+               CallerType aCallerType,
                ErrorResult& aRv);
 
   nsresult
   BackgroundActorCreated(PBackgroundChild* aBackgroundActor,
                          const indexedDB::LoggingInfo& aLoggingInfo);
 
   void
   BackgroundActorFailed();
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -169,28 +169,30 @@ MediaDevices::~MediaDevices()
 }
 
 NS_IMPL_ISUPPORTS(MediaDevices::GumResolver, nsIDOMGetUserMediaSuccessCallback)
 NS_IMPL_ISUPPORTS(MediaDevices::EnumDevResolver, nsIGetUserMediaDevicesSuccessCallback)
 NS_IMPL_ISUPPORTS(MediaDevices::GumRejecter, nsIDOMGetUserMediaErrorCallback)
 
 already_AddRefed<Promise>
 MediaDevices::GetUserMedia(const MediaStreamConstraints& aConstraints,
+			   CallerType aCallerType,
                            ErrorResult &aRv)
 {
   nsPIDOMWindowInner* window = GetOwner();
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
   RefPtr<Promise> p = Promise::Create(go, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
   RefPtr<GumResolver> resolver = new GumResolver(p);
   RefPtr<GumRejecter> rejecter = new GumRejecter(p);
 
   aRv = MediaManager::Get()->GetUserMedia(window, aConstraints,
-                                          resolver, rejecter);
+                                          resolver, rejecter,
+					  aCallerType);
   return p.forget();
 }
 
 already_AddRefed<Promise>
 MediaDevices::EnumerateDevices(ErrorResult &aRv)
 {
   nsPIDOMWindowInner* window = GetOwner();
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
--- a/dom/media/MediaDevices.h
+++ b/dom/media/MediaDevices.h
@@ -34,17 +34,18 @@ public:
   NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_MEDIADEVICES_IMPLEMENTATION_IID)
 
   JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override;
 
   // No code needed, as MediaTrackSupportedConstraints members default to true.
   void GetSupportedConstraints(MediaTrackSupportedConstraints& aResult) {};
 
   already_AddRefed<Promise>
-  GetUserMedia(const MediaStreamConstraints& aConstraints, ErrorResult &aRv);
+  GetUserMedia(const MediaStreamConstraints& aConstraints,
+	       CallerType aCallerType, ErrorResult &aRv);
 
   already_AddRefed<Promise>
   EnumerateDevices(ErrorResult &aRv);
 
   virtual void OnDeviceChange() override;
 
   mozilla::dom::EventHandlerNonNull* GetOndevicechange();
 
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -33,16 +33,17 @@
 #include "nsICryptoHMAC.h"
 #include "nsIKeyModule.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIInputStream.h"
 #include "nsILineInputStream.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Types.h"
 #include "mozilla/PeerIdentity.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/GetUserMediaRequestBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/MediaDevices.h"
 #include "mozilla/Base64.h"
@@ -234,17 +235,18 @@ public:
 
   void NotifyChromeOfTrackStops();
 
   typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
 
   already_AddRefed<PledgeVoid>
   ApplyConstraintsToTrack(nsPIDOMWindowInner* aWindow,
                           TrackID aID,
-                          const dom::MediaTrackConstraints& aConstraints);
+                          const dom::MediaTrackConstraints& aConstraints,
+                          dom::CallerType aCallerType);
 
   // mVideo/AudioDevice are set by Activate(), so we assume they're capturing
   // if set and represent a real capture device.
   bool CapturingVideo()
   {
     MOZ_ASSERT(NS_IsMainThread());
     return mVideoDevice && !mStopped &&
            !mVideoDevice->GetSource()->IsAvailable() &&
@@ -1107,26 +1109,28 @@ public:
 
         const PeerIdentity* GetPeerIdentity() const override
         {
           return mPeerIdentity;
         }
 
         already_AddRefed<PledgeVoid>
         ApplyConstraints(nsPIDOMWindowInner* aWindow,
-                         const MediaTrackConstraints& aConstraints) override
+                         const MediaTrackConstraints& aConstraints,
+                         dom::CallerType aCallerType) override
         {
           if (sInShutdown || !mListener) {
             // Track has been stopped, or we are in shutdown. In either case
             // there's no observable outcome, so pretend we succeeded.
             RefPtr<PledgeVoid> p = new PledgeVoid();
             p->Resolve(false);
             return p.forget();
           }
-          return mListener->ApplyConstraintsToTrack(aWindow, mTrackID, aConstraints);
+          return mListener->ApplyConstraintsToTrack(aWindow, mTrackID,
+                                                    aConstraints, aCallerType);
         }
 
         void
         GetSettings(dom::MediaTrackSettings& aOutSettings) override
         {
           if (mListener) {
             mListener->GetSettings(aOutSettings, mTrackID);
           }
@@ -1993,17 +1997,18 @@ enum class GetUserMediaSecurityState {
  * The entry point for this file. A call from Navigator::mozGetUserMedia
  * will end up here. MediaManager is a singleton that is responsible
  * for handling all incoming getUserMedia calls from every window.
  */
 nsresult
 MediaManager::GetUserMedia(nsPIDOMWindowInner* aWindow,
                            const MediaStreamConstraints& aConstraintsPassedIn,
                            nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
-                           nsIDOMGetUserMediaErrorCallback* aOnFailure)
+                           nsIDOMGetUserMediaErrorCallback* aOnFailure,
+                           dom::CallerType aCallerType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(aOnFailure);
   MOZ_ASSERT(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
   uint64_t windowID = aWindow->WindowID();
@@ -2031,17 +2036,17 @@ MediaManager::GetUserMedia(nsPIDOMWindow
   }
 
   // Determine permissions early (while we still have a stack).
 
   nsIURI* docURI = aWindow->GetDocumentURI();
   if (!docURI) {
     return NS_ERROR_UNEXPECTED;
   }
-  bool isChrome = nsContentUtils::IsCallerChrome();
+  bool isChrome = (aCallerType == dom::CallerType::System);
   bool privileged = isChrome ||
       Preferences::GetBool("media.navigator.permission.disabled", false);
   bool isHTTPS = false;
   docURI->SchemeIs("https", &isHTTPS);
   nsCString host;
   nsresult rv = docURI->GetHost(host);
   // Test for some other schemes that ServiceWorker recognizes
   bool isFile;
@@ -3461,17 +3466,18 @@ GetUserMediaCallbackMediaStreamListener:
 }
 
 // ApplyConstraints for track
 
 auto
 GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack(
     nsPIDOMWindowInner* aWindow,
     TrackID aTrackID,
-    const MediaTrackConstraints& aConstraints) -> already_AddRefed<PledgeVoid>
+    const MediaTrackConstraints& aConstraints,
+    dom::CallerType aCallerType) -> already_AddRefed<PledgeVoid>
 {
   MOZ_ASSERT(NS_IsMainThread());
   RefPtr<PledgeVoid> p = new PledgeVoid();
 
   // XXX to support multiple tracks of a type in a stream, this should key off
   // the TrackID and not just the type
   RefPtr<AudioDevice> audioDevice =
     aTrackID == kAudioTrack ? mAudioDevice.get() : nullptr;
@@ -3484,17 +3490,17 @@ GetUserMediaCallbackMediaStreamListener:
          aTrackID, aTrackID == kAudioTrack ? "audio" : "video"));
     p->Resolve(false);
     return p.forget();
   }
 
   RefPtr<MediaManager> mgr = MediaManager::GetInstance();
   uint32_t id = mgr->mOutstandingVoidPledges.Append(*p);
   uint64_t windowId = aWindow->WindowID();
-  bool isChrome = nsContentUtils::IsCallerChrome();
+  bool isChrome = (aCallerType == dom::CallerType::System);
 
   MediaManager::PostTask(NewTaskFrom([id, windowId,
                                       audioDevice, videoDevice,
                                       aConstraints, isChrome]() mutable {
     MOZ_ASSERT(MediaManager::IsInMediaThread());
     RefPtr<MediaManager> mgr = MediaManager::GetInstance();
     const char* badConstraint = nullptr;
     nsresult rv = NS_OK;
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -43,16 +43,17 @@
 #include "base/thread.h"
 #include "base/task.h"
 
 namespace mozilla {
 namespace dom {
 struct MediaStreamConstraints;
 struct MediaTrackConstraints;
 struct MediaTrackConstraintSet;
+enum class CallerType : uint32_t;
 } // namespace dom
 
 namespace ipc {
 class PrincipalInfo;
 }
 
 class MediaManager;
 class GetUserMediaCallbackMediaStreamListener;
@@ -236,17 +237,18 @@ public:
   // Note: also calls aListener->Remove(), even if inactive
   void RemoveFromWindowList(uint64_t aWindowID,
     GetUserMediaCallbackMediaStreamListener *aListener);
 
   nsresult GetUserMedia(
     nsPIDOMWindowInner* aWindow,
     const dom::MediaStreamConstraints& aConstraints,
     nsIDOMGetUserMediaSuccessCallback* onSuccess,
-    nsIDOMGetUserMediaErrorCallback* onError);
+    nsIDOMGetUserMediaErrorCallback* onError,
+    dom::CallerType aCallerType);
 
   nsresult GetUserMediaDevices(nsPIDOMWindowInner* aWindow,
                                const dom::MediaStreamConstraints& aConstraints,
                                nsIGetUserMediaDevicesSuccessCallback* onSuccess,
                                nsIDOMGetUserMediaErrorCallback* onError,
                                uint64_t aInnerWindowID = 0,
                                const nsAString& aCallID = nsString());
 
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -39,17 +39,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamTrackSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 auto
 MediaStreamTrackSource::ApplyConstraints(
     nsPIDOMWindowInner* aWindow,
-    const dom::MediaTrackConstraints& aConstraints) -> already_AddRefed<PledgeVoid>
+    const dom::MediaTrackConstraints& aConstraints,
+    CallerType aCallerType) -> already_AddRefed<PledgeVoid>
 {
   RefPtr<PledgeVoid> p = new PledgeVoid();
   p->Reject(new MediaStreamError(aWindow,
                                  NS_LITERAL_STRING("OverconstrainedError"),
                                  NS_LITERAL_STRING("")));
   return p.forget();
 }
 
@@ -265,16 +266,17 @@ MediaStreamTrack::GetConstraints(dom::Me
 void
 MediaStreamTrack::GetSettings(dom::MediaTrackSettings& aResult)
 {
   GetSource().GetSettings(aResult);
 }
 
 already_AddRefed<Promise>
 MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
+                                   CallerType aCallerType,
                                    ErrorResult &aRv)
 {
   if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
     nsString str;
     aConstraints.ToJSON(str);
 
     LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
                          "constraints %s", this, NS_ConvertUTF16toUTF8(str).get()));
@@ -289,17 +291,18 @@ MediaStreamTrack::ApplyConstraints(const
 
   // Forward constraints to the source.
   //
   // After GetSource().ApplyConstraints succeeds (after it's been to media-thread
   // and back), and no sooner, do we set mConstraints to the newly applied values.
 
   // Keep a reference to this, to make sure it's still here when we get back.
   RefPtr<MediaStreamTrack> that = this;
-  RefPtr<PledgeVoid> p = GetSource().ApplyConstraints(window, aConstraints);
+  RefPtr<PledgeVoid> p = GetSource().ApplyConstraints(window, aConstraints,
+                                                      aCallerType);
   p->Then([this, that, promise, aConstraints](bool& aDummy) mutable {
     mConstraints = aConstraints;
     promise->MaybeResolve(false);
   }, [promise](MediaStreamError*& reason) mutable {
     promise->MaybeReject(reason);
   });
   return promise.forget();
 }
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -35,16 +35,17 @@ class ProcessedMediaStream;
 class RemoteSourceStreamInfo;
 class SourceStreamInfo;
 
 namespace dom {
 
 class AudioStreamTrack;
 class VideoStreamTrack;
 class MediaStreamError;
+enum class CallerType : uint32_t;
 
 /**
  * Common interface through which a MediaStreamTrack can communicate with its
  * producer on the main thread.
  *
  * Kept alive by a strong ref in all MediaStreamTracks (original and clones)
  * sharing this source.
  */
@@ -118,17 +119,18 @@ public:
   typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
 
   /**
    * We provide a fallback solution to ApplyConstraints() here.
    * Sources that support ApplyConstraints() will have to override it.
    */
   virtual already_AddRefed<PledgeVoid>
   ApplyConstraints(nsPIDOMWindowInner* aWindow,
-                   const dom::MediaTrackConstraints& aConstraints);
+                   const dom::MediaTrackConstraints& aConstraints,
+                   CallerType aCallerType);
 
   /**
    * Same for GetSettings (no-op).
    */
   virtual void
   GetSettings(dom::MediaTrackSettings& aResult) {};
 
   /**
@@ -283,17 +285,18 @@ public:
   void GetLabel(nsAString& aLabel) { GetSource().GetLabel(aLabel); }
   bool Enabled() { return mEnabled; }
   void SetEnabled(bool aEnabled);
   void Stop();
   void GetConstraints(dom::MediaTrackConstraints& aResult);
   void GetSettings(dom::MediaTrackSettings& aResult);
 
   already_AddRefed<Promise>
-  ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
+  ApplyConstraints(const dom::MediaTrackConstraints& aConstraints,
+                   CallerType aCallerType, ErrorResult &aRv);
   already_AddRefed<MediaStreamTrack> Clone();
   MediaStreamTrackState ReadyState() { return mReadyState; }
 
   IMPL_EVENT_HANDLER(ended)
 
   /**
    * Convenience (and legacy) method for when ready state is "ended".
    */
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -221,27 +221,27 @@ public:
   class AllocationHandle
   {
   public:
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AllocationHandle);
   protected:
     ~AllocationHandle() {}
   public:
     AllocationHandle(const dom::MediaTrackConstraints& aConstraints,
-                     const ipc::PrincipalInfo& aPrincipalInfo,
+                     const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
                      const MediaEnginePrefs& aPrefs,
                      const nsString& aDeviceId)
 
     : mConstraints(aConstraints),
       mPrincipalInfo(aPrincipalInfo),
       mPrefs(aPrefs),
       mDeviceId(aDeviceId) {}
   public:
     NormalizedConstraints mConstraints;
-    ipc::PrincipalInfo mPrincipalInfo;
+    mozilla::ipc::PrincipalInfo mPrincipalInfo;
     MediaEnginePrefs mPrefs;
     nsString mDeviceId;
   };
 
   /* Release the device back to the system. */
   virtual nsresult Deallocate(AllocationHandle* aHandle)
   {
     MOZ_ASSERT(aHandle);
--- a/dom/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/dom/media/webspeech/recognition/SpeechRecognition.cpp
@@ -719,17 +719,19 @@ SpeechRecognition::GetServiceURI(nsStrin
 void
 SpeechRecognition::SetServiceURI(const nsAString& aArg, ErrorResult& aRv)
 {
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
   return;
 }
 
 void
-SpeechRecognition::Start(const Optional<NonNull<DOMMediaStream>>& aStream, ErrorResult& aRv)
+SpeechRecognition::Start(const Optional<NonNull<DOMMediaStream>>& aStream,
+                         CallerType aCallerType,
+                         ErrorResult& aRv)
 {
   if (mCurrentState != STATE_IDLE) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   if (!SetRecognitionService(aRv)) {
     return;
@@ -751,17 +753,18 @@ SpeechRecognition::Start(const Optional<
   if (aStream.WasPassed()) {
     StartRecording(&aStream.Value());
   } else {
     AutoNoJSAPI();
     MediaManager* manager = MediaManager::Get();
     manager->GetUserMedia(GetOwner(),
                           constraints,
                           new GetUserMediaSuccessCallback(this),
-                          new GetUserMediaErrorCallback(this));
+                          new GetUserMediaErrorCallback(this),
+                          aCallerType);
   }
 
   RefPtr<SpeechEvent> event = new SpeechEvent(this, EVENT_START);
   NS_DispatchToMainThread(event);
 }
 
 bool
 SpeechRecognition::SetRecognitionService(ErrorResult& aRv)
--- a/dom/media/webspeech/recognition/SpeechRecognition.h
+++ b/dom/media/webspeech/recognition/SpeechRecognition.h
@@ -22,16 +22,17 @@
 #include "mozilla/WeakPtr.h"
 
 #include "SpeechGrammarList.h"
 #include "SpeechRecognitionResultList.h"
 #include "SpeechStreamListener.h"
 #include "nsISpeechRecognitionService.h"
 #include "endpointer.h"
 
+#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/SpeechRecognitionError.h"
 
 namespace mozilla {
 
 class DOMMediaStream;
 
 namespace dom {
 
@@ -85,17 +86,18 @@ public:
   uint32_t MaxAlternatives() const;
 
   void SetMaxAlternatives(uint32_t aArg);
 
   void GetServiceURI(nsString& aRetVal, ErrorResult& aRv) const;
 
   void SetServiceURI(const nsAString& aArg, ErrorResult& aRv);
 
-  void Start(const Optional<NonNull<DOMMediaStream>>& aStream, ErrorResult& aRv);
+  void Start(const Optional<NonNull<DOMMediaStream>>& aStream,
+             CallerType aCallerType, ErrorResult& aRv);
 
   void Stop();
 
   void Abort();
 
   IMPL_EVENT_HANDLER(audiostart)
   IMPL_EVENT_HANDLER(soundstart)
   IMPL_EVENT_HANDLER(speechstart)
--- a/dom/presentation/provider/AndroidCastDeviceProvider.js
+++ b/dom/presentation/provider/AndroidCastDeviceProvider.js
@@ -8,17 +8,17 @@
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 // globals XPCOMUtils
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 // globals Services
 Cu.import("resource://gre/modules/Services.jsm");
-// globals Messaging
+// globals EventDispatcher
 Cu.import("resource://gre/modules/Messaging.jsm");
 
 function log(str) {
   // dump("-*- AndroidCastDeviceProvider -*-: " + str + "\n");
 }
 
 // Helper function: transfer nsIPresentationChannelDescription to json
 function descriptionToString(aDescription) {
@@ -296,17 +296,17 @@ ChromecastRemoteDisplayDevice.prototype 
                                                 this._role);
 
     if (this._role == Ci.nsIPresentationService.ROLE_CONTROLLER) {
       // Only connect to Chromecast for controller.
       // Monitor the receiver being ready.
       Services.obs.addObserver(this, TOPIC_PRESENTATION_VIEW_READY, true);
 
       // Launch Chromecast service in Android.
-      Messaging.sendRequestForResult({
+      EventDispatcher.instance.sendRequestForResult({
         type: TOPIC_ANDROID_CAST_DEVICE_START,
         id:   this.id
       }).then(result => {
         log("Chromecast is connected.");
       }).catch(error => {
         log("Can not connect to Chromecast.");
         // If Chromecast can not be launched, remove the observer.
         Services.obs.removeObserver(this, TOPIC_PRESENTATION_VIEW_READY);
@@ -318,17 +318,17 @@ ChromecastRemoteDisplayDevice.prototype 
       this._ctrlChannel.notifyConnected();
     }
 
     return this._ctrlChannel;
   },
 
   disconnect: function CRDD_disconnect() {
     // Disconnect from Chromecast.
-    Messaging.sendRequestForResult({
+    EventDispatcher.instance.sendRequestForResult({
       type: TOPIC_ANDROID_CAST_DEVICE_STOP,
       id:   this.id
     });
   },
 
   isRequestedUrlSupported: function CRDD_isRequestedUrlSupported(aUrl) {
     let url = Cc["@mozilla.org/network/io-service;1"]
                 .getService(Ci.nsIIOService)
@@ -405,17 +405,17 @@ AndroidCastDeviceProvider.prototype = {
       // remove observer
       Services.obs.removeObserver(this, TOPIC_ANDROID_CAST_DEVICE_ADDED);
       Services.obs.removeObserver(this, TOPIC_ANDROID_CAST_DEVICE_CHANGED);
       Services.obs.removeObserver(this, TOPIC_ANDROID_CAST_DEVICE_REMOVED);
       return;
     }
 
     // Sync all device already found by Android.
-    Messaging.sendRequest({ type: TOPIC_ANDROID_CAST_DEVICE_SYNCDEVICE });
+    EventDispatcher.instance.sendRequest({ type: TOPIC_ANDROID_CAST_DEVICE_SYNCDEVICE });
     // Observer registration
     Services.obs.addObserver(this, TOPIC_ANDROID_CAST_DEVICE_ADDED, false);
     Services.obs.addObserver(this, TOPIC_ANDROID_CAST_DEVICE_CHANGED, false);
     Services.obs.addObserver(this, TOPIC_ANDROID_CAST_DEVICE_REMOVED, false);
   },
 
   get listener() {
     return this._listener;
--- a/dom/push/PushRecord.jsm
+++ b/dom/push/PushRecord.jsm
@@ -9,17 +9,17 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
+XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher",
                                   "resource://gre/modules/Messaging.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
@@ -141,17 +141,17 @@ PushRecord.prototype = {
   getLastVisit: Task.async(function* () {
     if (!this.quotaApplies() || this.isTabOpen()) {
       // If the registration isn't subject to quota, or the user already
       // has the site open, skip expensive database queries.
       return Date.now();
     }
 
     if (AppConstants.MOZ_ANDROID_HISTORY) {
-      let result = yield Messaging.sendRequestForResult({
+      let result = yield EventDispatcher.instance.sendRequestForResult({
         type: "History:GetPrePathLastVisitedTimeMilliseconds",
         prePath: this.uri.prePath,
       });
       return result == 0 ? -Infinity : result;
     }
 
     // Places History transition types that can fire a
     // `pushsubscriptionchange` event when the user visits a site with expired push
--- a/dom/push/PushServiceAndroidGCM.jsm
+++ b/dom/push/PushServiceAndroidGCM.jsm
@@ -8,17 +8,17 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");
 const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm");
 const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm");
-Cu.import("resource://gre/modules/Messaging.jsm"); /*global: Messaging */
+Cu.import("resource://gre/modules/Messaging.jsm"); /*global: EventDispatcher */
 Cu.import("resource://gre/modules/Services.jsm"); /*global: Services */
 Cu.import("resource://gre/modules/Preferences.jsm"); /*global: Preferences */
 Cu.import("resource://gre/modules/Promise.jsm"); /*global: Promise */
 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global: XPCOMUtils */
 
 const Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("Push");
 
 this.EXPORTED_SYMBOLS = ["PushServiceAndroidGCM"];
@@ -131,41 +131,41 @@ this.PushServiceAndroidGCM = {
         // The Push server may append padding.
         padding: "ignore",
       });
     }
     return { headers, message };
   },
 
   _configure: function(serverURL, debug) {
-    return Messaging.sendRequestForResult({
+    return EventDispatcher.instance.sendRequestForResult({
       type: "PushServiceAndroidGCM:Configure",
       endpoint: serverURL.spec,
       debug: debug,
     });
   },
 
   init: function(options, mainPushService, serverURL) {
     console.debug("init()");
     this._mainPushService = mainPushService;
     this._serverURI = serverURL;
 
     prefs.observe("debug", this);
     Services.obs.addObserver(this, "PushServiceAndroidGCM:ReceivedPushMessage", false);
 
     return this._configure(serverURL, !!prefs.get("debug")).then(() => {
-      Messaging.sendRequestForResult({
+      EventDispatcher.instance.sendRequestForResult({
         type: "PushServiceAndroidGCM:Initialized"
       });
     });
   },
 
   uninit: function() {
     console.debug("uninit()");
-    Messaging.sendRequestForResult({
+    EventDispatcher.instance.sendRequestForResult({
       type: "PushServiceAndroidGCM:Uninitialized"
     });
 
     this._mainPushService = null;
     Services.obs.removeObserver(this, "PushServiceAndroidGCM:ReceivedPushMessage");
     prefs.ignore("debug", this);
   },
 
@@ -173,17 +173,17 @@ this.PushServiceAndroidGCM = {
     // No action required.
   },
 
   connect: function(records) {
     console.debug("connect:", records);
     // It's possible for the registration or subscriptions backing the
     // PushService to not be registered with the underlying AndroidPushService.
     // Expire those that are unrecognized.
-    return Messaging.sendRequestForResult({
+    return EventDispatcher.instance.sendRequestForResult({
       type: "PushServiceAndroidGCM:DumpSubscriptions",
     })
     .then(subscriptions => {
       console.debug("connect:", subscriptions);
       // subscriptions maps chid => subscription data.
       return Promise.all(records.map(record => {
         if (subscriptions.hasOwnProperty(record.keyID)) {
           console.debug("connect:", "hasOwnProperty", record.keyID);
@@ -218,17 +218,17 @@ this.PushServiceAndroidGCM = {
     let message = {
       type: "PushServiceAndroidGCM:SubscribeChannel",
       appServerKey: appServerKey,
     }
     if (record.scope == FXA_PUSH_SCOPE) {
       message.service = "fxa";
     }
     // Caller handles errors.
-    return Messaging.sendRequestForResult(message)
+    return EventDispatcher.instance.sendRequestForResult(message)
     .then(data => {
       console.debug("Got data:", data);
       return PushCrypto.generateKeys()
         .then(exportedKeys =>
           new PushRecordAndroidGCM({
             // Straight from autopush.
             channelID: data.channelID,
             pushEndpoint: data.endpoint,
@@ -244,17 +244,17 @@ this.PushServiceAndroidGCM = {
             appServerKey: record.appServerKey,
           })
       );
     });
   },
 
   unregister: function(record) {
     console.debug("unregister: ", record);
-    return Messaging.sendRequestForResult({
+    return EventDispatcher.instance.sendRequestForResult({
       type: "PushServiceAndroidGCM:UnsubscribeChannel",
       channelID: record.keyID,
     });
   },
 
   reportDeliveryError: function(messageID, reason) {
     console.warn("reportDeliveryError: Ignoring message delivery error",
       messageID, reason);
--- a/dom/quota/QuotaManagerService.cpp
+++ b/dom/quota/QuotaManagerService.cpp
@@ -535,17 +535,16 @@ QuotaManagerService::GetUsageForPrincipa
   request.forget(_retval);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 QuotaManagerService::Clear(nsIQuotaRequest** _retval)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
   if (NS_WARN_IF(!gTestingMode)) {
     return NS_ERROR_UNEXPECTED;
   }
 
   RefPtr<Request> request = new Request();
 
   ClearAllParams params;
@@ -564,17 +563,16 @@ QuotaManagerService::Clear(nsIQuotaReque
 NS_IMETHODIMP
 QuotaManagerService::ClearStoragesForPrincipal(nsIPrincipal* aPrincipal,
                                                const nsACString& aPersistenceType,
                                                bool aClearAll,
                                                nsIQuotaRequest** _retval)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPrincipal);
-  MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
   nsCString suffix;
   aPrincipal->OriginAttributesRef().CreateSuffix(suffix);
 
   if (NS_WARN_IF(aClearAll && !suffix.IsEmpty())) {
     // The originAttributes should be default originAttributes when the
     // aClearAll flag is set.
     return NS_ERROR_INVALID_ARG;
@@ -621,17 +619,16 @@ QuotaManagerService::ClearStoragesForPri
   request.forget(_retval);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 QuotaManagerService::Reset(nsIQuotaRequest** _retval)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
   if (NS_WARN_IF(!gTestingMode)) {
     return NS_ERROR_UNEXPECTED;
   }
 
   RefPtr<Request> request = new Request();
 
   ResetAllParams params;
--- a/dom/webidl/Cache.webidl
+++ b/dom/webidl/Cache.webidl
@@ -12,19 +12,19 @@
 
 [Exposed=(Window,Worker),
  Func="mozilla::dom::cache::Cache::PrefEnabled"]
 interface Cache {
   [NewObject]
   Promise<Response> match(RequestInfo request, optional CacheQueryOptions options);
   [NewObject]
   Promise<sequence<Response>> matchAll(optional RequestInfo request, optional CacheQueryOptions options);
-  [NewObject]
+  [NewObject, NeedsCallerType]
   Promise<void> add(RequestInfo request);
-  [NewObject]
+  [NewObject, NeedsCallerType]
   Promise<void> addAll(sequence<RequestInfo> requests);
   [NewObject]
   Promise<void> put(RequestInfo request, Response response);
   [NewObject]
   Promise<boolean> delete(RequestInfo request, optional CacheQueryOptions options);
   [NewObject]
   Promise<sequence<Request>> keys(optional RequestInfo request, optional CacheQueryOptions options);
 };
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -105,20 +105,16 @@ interface CanvasRenderingContext2D {
    * transparency.
    *
    * This API cannot currently be used by Web content. It is chrome
    * and Web Extensions (with a permission) only.
    */
   [Throws, Func="CanvasUtils::HasDrawWindowPrivilege"]
   void drawWindow(Window window, double x, double y, double w, double h,
                   DOMString bgColor, optional unsigned long flags = 0);
-  [Throws, ChromeOnly]
-  void asyncDrawXULElement(XULElement elem, double x, double y, double w,
-                           double h, DOMString bgColor,
-                           optional unsigned long flags = 0);
 
   /**
    * This causes a context that is currently using a hardware-accelerated
    * backend to fallback to a software one. All state should be preserved.
    */
   [ChromeOnly]
   void demote();
 };
--- a/dom/webidl/File.webidl
+++ b/dom/webidl/File.webidl
@@ -31,19 +31,19 @@ dictionary ChromeFilePropertyBag : FileP
 // Mozilla extensions
 partial interface File {
   [GetterThrows, Deprecated="FileLastModifiedDate"]
   readonly attribute Date lastModifiedDate;
 
   [BinaryName="path", Func="mozilla::dom::Directory::WebkitBlinkDirectoryPickerEnabled"]
   readonly attribute USVString webkitRelativePath;
 
-  [GetterThrows, ChromeOnly]
+  [GetterThrows, ChromeOnly, NeedsCallerType]
   readonly attribute DOMString mozFullPath;
 
-  [ChromeOnly, Throws]
+  [ChromeOnly, Throws, NeedsCallerType]
   static File createFromNsIFile(nsIFile file,
                                 optional ChromeFilePropertyBag options);
 
-  [ChromeOnly, Throws]
+  [ChromeOnly, Throws, NeedsCallerType]
   static File createFromFileName(USVString fileName,
                                  optional ChromeFilePropertyBag options);
 };
--- a/dom/webidl/GeometryUtils.webidl
+++ b/dom/webidl/GeometryUtils.webidl
@@ -18,21 +18,21 @@ dictionary BoxQuadOptions {
 
 dictionary ConvertCoordinateOptions {
   CSSBoxType fromBox = "border";
   CSSBoxType toBox = "border";
 };
 
 [NoInterfaceObject]
 interface GeometryUtils {
-  [Throws, Func="nsINode::HasBoxQuadsSupport"]
+  [Throws, Func="nsINode::HasBoxQuadsSupport", NeedsCallerType]
   sequence<DOMQuad> getBoxQuads(optional BoxQuadOptions options);
-  [Throws, Pref="layout.css.convertFromNode.enabled"]
+  [Throws, Pref="layout.css.convertFromNode.enabled", NeedsCallerType]
   DOMQuad convertQuadFromNode(DOMQuad quad, GeometryNode from, optional ConvertCoordinateOptions options);
-  [Throws, Pref="layout.css.convertFromNode.enabled"]
+  [Throws, Pref="layout.css.convertFromNode.enabled", NeedsCallerType]
   DOMQuad convertRectFromNode(DOMRectReadOnly rect, GeometryNode from, optional ConvertCoordinateOptions options);
-  [Throws, Pref="layout.css.convertFromNode.enabled"]
+  [Throws, Pref="layout.css.convertFromNode.enabled", NeedsCallerType]
   DOMPoint convertPointFromNode(DOMPointInit point, GeometryNode from, optional ConvertCoordinateOptions options);
 };
 
 // PseudoElement implements GeometryUtils;
 
 typedef (Text or Element /* or PseudoElement */ or Document) GeometryNode;
--- a/dom/webidl/HTMLCanvasElement.webidl
+++ b/dom/webidl/HTMLCanvasElement.webidl
@@ -63,9 +63,9 @@ interface MozCanvasPrintState
   readonly attribute nsISupports context;
 
   // To be called when rendering to the context is done.
   void done();
 };
 
 callback PrintCallback = void(MozCanvasPrintState ctx);
 
-callback BlobCallback = void(Blob blob);
+callback BlobCallback = void(Blob? blob);
--- a/dom/webidl/HTMLDocument.webidl
+++ b/dom/webidl/HTMLDocument.webidl
@@ -40,20 +40,20 @@ interface HTMLDocument : Document {
   void close();
   [Throws]
   void write(DOMString... text);
   [Throws]
   void writeln(DOMString... text);
 
   [SetterThrows, NeedsSubjectPrincipal]
            attribute DOMString designMode;
-  [Throws, NeedsCallerType]
+  [Throws, NeedsSubjectPrincipal]
   boolean execCommand(DOMString commandId, optional boolean showUI = false,
                       optional DOMString value = "");
-  [Throws, NeedsCallerType]
+  [Throws, NeedsSubjectPrincipal]
   boolean queryCommandEnabled(DOMString commandId);
   [Throws]
   boolean queryCommandIndeterm(DOMString commandId);
   [Throws]
   boolean queryCommandState(DOMString commandId);
   [NeedsCallerType]
   boolean queryCommandSupported(DOMString commandId);
   [Throws]
--- a/dom/webidl/HTMLObjectElement.webidl
+++ b/dom/webidl/HTMLObjectElement.webidl
@@ -159,17 +159,17 @@ interface MozObjectLoadingContent {
 
   [ChromeOnly]
   sequence<MozPluginParameter> getPluginParameters();
 
   /**
    * This method will play a plugin that has been stopped by the click-to-play
    * feature.
    */
-  [ChromeOnly, Throws]
+  [ChromeOnly, Throws, NeedsCallerType]
   void playPlugin();
 
   /**
    * Forces a re-evaluation and reload of the tag, optionally invalidating its
    * click-to-play state.  This can be used when the MIME type that provides a
    * type has changed, for instance, to force the tag to re-evalulate the
    * handler to use.
    */
@@ -199,17 +199,17 @@ interface MozObjectLoadingContent {
 
   /**
    * If this object currently owns a running plugin, regardless of whether or
    * not one is pending spawn/despawn.
    */
   [ChromeOnly]
   readonly attribute boolean hasRunningPlugin;
 
-  [ChromeOnly, Throws]
+  [ChromeOnly, Throws, NeedsCallerType]
   readonly attribute unsigned long runID;
 };
 
 /**
  * Name:Value pair type used for passing parameters to NPAPI or javascript
  * plugins.
  */
 dictionary MozPluginParameter {
--- a/dom/webidl/IDBFactory.webidl
+++ b/dom/webidl/IDBFactory.webidl
@@ -20,46 +20,46 @@ dictionary IDBOpenDBOptions
 
 /**
  * Interface that defines the indexedDB property on a window.  See
  * http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBFactory
  * for more information.
  */
 [Exposed=(Window,Worker,System)]
 interface IDBFactory {
-  [Throws]
+  [Throws, NeedsCallerType]
   IDBOpenDBRequest
   open(DOMString name,
        [EnforceRange] unsigned long long version);
 
-  [Throws]
+  [Throws, NeedsCallerType]
   IDBOpenDBRequest
   open(DOMString name,
        optional IDBOpenDBOptions options);
 
-  [Throws]
+  [Throws, NeedsCallerType]
   IDBOpenDBRequest
   deleteDatabase(DOMString name,
                  optional IDBOpenDBOptions options);
 
   [Throws]
   short
   cmp(any first,
       any second);
 
-  [Throws, ChromeOnly]
+  [Throws, ChromeOnly, NeedsCallerType]
   IDBOpenDBRequest
   openForPrincipal(Principal principal,
                    DOMString name,
                    [EnforceRange] unsigned long long version);
 
-  [Throws, ChromeOnly]
+  [Throws, ChromeOnly, NeedsCallerType]
   IDBOpenDBRequest
   openForPrincipal(Principal principal,
                    DOMString name,
                    optional IDBOpenDBOptions options);
 
-  [Throws, ChromeOnly]
+  [Throws, ChromeOnly, NeedsCallerType]
   IDBOpenDBRequest
   deleteForPrincipal(Principal principal,
                      DOMString name,
                      optional IDBOpenDBOptions options);
 };
--- a/dom/webidl/MediaDevices.webidl
+++ b/dom/webidl/MediaDevices.webidl
@@ -14,11 +14,11 @@
 interface MediaDevices : EventTarget {
   [Pref="media.ondevicechange.enabled"]
   attribute EventHandler ondevicechange;
   MediaTrackSupportedConstraints getSupportedConstraints();
 
   [Throws]
   Promise<sequence<MediaDeviceInfo>> enumerateDevices();
 
-  [Throws]
+  [Throws, NeedsCallerType]
   Promise<MediaStream> getUserMedia(optional MediaStreamConstraints constraints);
 };
--- a/dom/webidl/MediaStreamTrack.webidl
+++ b/dom/webidl/MediaStreamTrack.webidl
@@ -82,12 +82,12 @@ interface MediaStreamTrack : EventTarget
     readonly    attribute MediaStreamTrackState readyState;
                 attribute EventHandler          onended;
     MediaStreamTrack       clone ();
     void                   stop ();
 //  MediaTrackCapabilities getCapabilities ();
     MediaTrackConstraints  getConstraints ();
     MediaTrackSettings     getSettings ();
 
-    [Throws]
+    [Throws, NeedsCallerType]
     Promise<void>          applyConstraints (optional MediaTrackConstraints constraints);
 //              attribute EventHandler          onoverconstrained;
 };
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -310,17 +310,18 @@ callback NavigatorUserMediaSuccessCallba
 callback NavigatorUserMediaErrorCallback = void (MediaStreamError error);
 
 partial interface Navigator {
   [Throws, Func="Navigator::HasUserMediaSupport"]
   readonly attribute MediaDevices mediaDevices;
 
   // Deprecated. Use mediaDevices.getUserMedia instead.
   [Deprecated="NavigatorGetUserMedia", Throws,
-   Func="Navigator::HasUserMediaSupport", UnsafeInPrerendering]
+   Func="Navigator::HasUserMediaSupport", UnsafeInPrerendering,
+   NeedsCallerType]
   void mozGetUserMedia(MediaStreamConstraints constraints,
                        NavigatorUserMediaSuccessCallback successCallback,
                        NavigatorUserMediaErrorCallback errorCallback);
 };
 
 // nsINavigatorUserMedia
 callback MozGetUserMediaDevicesSuccessCallback = void (nsIVariant? devices);
 partial interface Navigator {
--- a/dom/webidl/Node.webidl
+++ b/dom/webidl/Node.webidl
@@ -26,17 +26,17 @@ interface Node : EventTarget {
   const unsigned short DOCUMENT_TYPE_NODE = 10;
   const unsigned short DOCUMENT_FRAGMENT_NODE = 11;
   const unsigned short NOTATION_NODE = 12; // historical
   [Constant]
   readonly attribute unsigned short nodeType;
   [Pure]
   readonly attribute DOMString nodeName;
 
-  [Pure, Throws]
+  [Pure, Throws, NeedsCallerType, BinaryName="baseURIFromJS"]
   readonly attribute DOMString? baseURI;
 
   [Pure, BinaryName=getComposedDoc]
   readonly attribute boolean isConnected;
   [Pure]
   readonly attribute Document? ownerDocument;
   [Pure]
   Node getRootNode(optional GetRootNodeOptions options);
--- a/dom/webidl/SpeechRecognition.webidl
+++ b/dom/webidl/SpeechRecognition.webidl
@@ -20,17 +20,17 @@ interface SpeechRecognition : EventTarge
     [Throws]
     attribute boolean continuous;
     attribute boolean interimResults;
     attribute unsigned long maxAlternatives;
     [Throws]
     attribute DOMString serviceURI;
 
     // methods to drive the speech interaction
-    [Throws, UnsafeInPrerendering]
+    [Throws, UnsafeInPrerendering, NeedsCallerType]
     void start(optional MediaStream stream);
     void stop();
     void abort();
 
     // event methods
     attribute EventHandler onaudiostart;
     attribute EventHandler onsoundstart;
     attribute EventHandler onspeechstart;
--- a/dom/webidl/TestFunctions.webidl
+++ b/dom/webidl/TestFunctions.webidl
@@ -3,21 +3,39 @@
  * 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/.
  */
 
 // A dumping ground for random testing functions
 
 callback PromiseReturner = Promise<any>();
 
-[Pref="dom.expose_test_interfaces"]
+[Pref="dom.expose_test_interfaces",
+ Constructor]
 interface TestFunctions {
   [Throws]
   static void throwUncatchableException();
 
   // Simply returns its argument.  Can be used to test Promise
   // argument processing behavior.
   static Promise<any> passThroughPromise(Promise<any> arg);
 
   // Returns whatever Promise the given PromiseReturner returned.
   [Throws]
   static Promise<any> passThroughCallbackPromise(PromiseReturner callback);
+
+  // Some basic tests for string binding round-tripping behavior.
+  void setStringData(DOMString arg);
+
+  // Get the string data, using an nsAString argument on the C++ side.
+  // This will just use Assign/operator=, whatever that does.
+  DOMString getStringDataAsAString();
+
+  // Get the string data, but only "length" chars of it, using an
+  // nsAString argument on the C++ side.  This will always copy on the
+  // C++ side.
+  DOMString getStringDataAsAString(unsigned long length);
+
+  // Get the string data, but only "length" chars of it, using a
+  // DOMString argument on the C++ side and trying to hand it
+  // stringbuffers.  If length not passed, use our full length.
+  DOMString getStringDataAsDOMString(optional unsigned long length);
 };
--- a/dom/webidl/WebGL2RenderingContext.webidl
+++ b/dom/webidl/WebGL2RenderingContext.webidl
@@ -606,24 +606,24 @@ interface WebGL2RenderingContextBase
     /* Writing to the drawing buffer */
     void vertexAttribDivisor(GLuint index, GLuint divisor);
     void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount);
     void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, GLintptr offset, GLsizei instanceCount);
     void drawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, GLintptr offset);
 
     /* Reading back pixels */
     // WebGL1:
-    [Throws] // Throws on readback in a write-only context.
+    [Throws, NeedsCallerType] // Throws on readback in a write-only context.
     void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
                     ArrayBufferView? dstData);
     // WebGL2:
-    [Throws] // Throws on readback in a write-only context.
+    [Throws, NeedsCallerType] // Throws on readback in a write-only context.
     void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
                     GLintptr offset);
-    [Throws] // Throws on readback in a write-only context.
+    [Throws, NeedsCallerType] // Throws on readback in a write-only context.
     void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
                     ArrayBufferView dstData, GLuint dstOffset);
 
     /* Multiple Render Targets */
     void drawBuffers(sequence<GLenum> buffers);
 
     void clearBufferfv(GLenum buffer, GLint drawbuffer, Float32List values,
                        optional GLuint srcOffset = 0);
--- a/dom/webidl/WebGLRenderingContext.webidl
+++ b/dom/webidl/WebGLRenderingContext.webidl
@@ -723,17 +723,17 @@ interface WebGLRenderingContext {
                               ArrayBufferView data);
     // compressedTexSubImage2D has WebGL2 overloads.
     void compressedTexSubImage2D(GLenum target, GLint level,
                                  GLint xoffset, GLint yoffset,
                                  GLsizei width, GLsizei height, GLenum format,
                                  ArrayBufferView data);
 
     // readPixels has WebGL2 overloads.
-    [Throws]
+    [Throws, NeedsCallerType]
     void readPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                     GLenum format, GLenum type, ArrayBufferView? pixels);
 
     // texImage2D has WebGL2 overloads.
     // Overloads must share [Throws].
     [Throws] // Can't actually throw.
     void texImage2D(GLenum target, GLint level, GLint internalformat,
                     GLsizei width, GLsizei height, GLint border, GLenum format,
--- a/dom/webidl/WindowOrWorkerGlobalScope.webidl
+++ b/dom/webidl/WindowOrWorkerGlobalScope.webidl
@@ -41,17 +41,18 @@ interface WindowOrWorkerGlobalScope {
   [Throws]
   Promise<ImageBitmap> createImageBitmap(ImageBitmapSource aImage);
   [Throws]
   Promise<ImageBitmap> createImageBitmap(ImageBitmapSource aImage, long aSx, long aSy, long aSw, long aSh);
 };
 
 // https://fetch.spec.whatwg.org/#fetch-method
 partial interface WindowOrWorkerGlobalScope {
-  [NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init);
+  [NewObject, NeedsCallerType]
+  Promise<Response> fetch(RequestInfo input, optional RequestInit init);
 };
 
 // https://w3c.github.io/webappsec-secure-contexts/#monkey-patching-global-object
 partial interface WindowOrWorkerGlobalScope {
   readonly attribute boolean isSecureContext;
 };
 
 // http://w3c.github.io/IndexedDB/#factory-interface
--- a/dom/webidl/Worklet.webidl
+++ b/dom/webidl/Worklet.webidl
@@ -4,11 +4,11 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is
  * https://drafts.css-houdini.org/worklets/#idl-index
  */
 
 [Pref="dom.worklet.enabled"]
 interface Worklet {
-  [NewObject, Throws]
+  [NewObject, Throws, NeedsCallerType]
   Promise<void> import(USVString moduleURL);
 };
--- a/dom/webidl/XMLDocument.webidl
+++ b/dom/webidl/XMLDocument.webidl
@@ -8,16 +8,16 @@
  * http://www.whatwg.org/specs/web-apps/current-work/#xmldocument
  */
 
 // http://dom.spec.whatwg.org/#xmldocument
 interface XMLDocument : Document {};
 
 // http://www.whatwg.org/specs/web-apps/current-work/#xmldocument
 partial interface XMLDocument {
-  [Throws]
+  [Throws, NeedsCallerType]
   boolean load(DOMString url);
 };
 
 // Gecko extensions?
 partial interface XMLDocument {
   attribute boolean async;
 };
--- a/dom/webidl/XSLTProcessor.webidl
+++ b/dom/webidl/XSLTProcessor.webidl
@@ -99,11 +99,11 @@ interface XSLTProcessor {
     */
     [ChromeOnly]
     const unsigned long DISABLE_ALL_LOADS = 1;
 
     /**
     * Flags for this processor. Defaults to 0. See individual flags above
     * for documentation for effect of reset()
     */
-    [ChromeOnly]
+    [ChromeOnly, NeedsCallerType]
     attribute unsigned long flags;
 };
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4270,25 +4270,25 @@ WorkerPrivate::Constructor(const GlobalO
 {
   return WorkerPrivate::Constructor(aGlobal, aScriptURL, false,
                                     WorkerTypeDedicated, EmptyCString(),
                                     nullptr, aRv);
 }
 
 // static
 bool
-WorkerPrivate::WorkerAvailable(JSContext* /* unused */, JSObject* /* unused */)
+WorkerPrivate::WorkerAvailable(JSContext* aCx, JSObject* /* unused */)
 {
   // If we're already on a worker workers are clearly enabled.
   if (!NS_IsMainThread()) {
     return true;
   }
 
   // If our caller is chrome, workers are always available.
-  if (nsContentUtils::IsCallerChrome()) {
+  if (nsContentUtils::IsSystemCaller(aCx)) {
     return true;
   }
 
   // Else check the pref.
   return Preferences::GetBool(PREF_WORKERS_ENABLED);
 }
 
 // static
@@ -4307,17 +4307,17 @@ ChromeWorkerPrivate::Constructor(const G
 bool
 ChromeWorkerPrivate::WorkerAvailable(JSContext* aCx, JSObject* /* unused */)
 {
   // Chrome is always allowed to use workers, and content is never
   // allowed to use ChromeWorker, so all we have to check is the
   // caller.  However, chrome workers apparently might not have a
   // system principal, so we have to check for them manually.
   if (NS_IsMainThread()) {
-    return nsContentUtils::IsCallerChrome();
+    return nsContentUtils::IsSystemCaller(aCx);
   }
 
   return GetWorkerPrivateFromContext(aCx)->IsChromeWorker();
 }
 
 // static
 already_AddRefed<WorkerPrivate>
 WorkerPrivate::Constructor(const GlobalObject& aGlobal,
@@ -4488,17 +4488,17 @@ WorkerPrivate::GetLoadInfo(JSContext* aC
     AssertIsOnMainThread();
 
     // Make sure that the IndexedDatabaseManager is set up
     Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
 
     nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
     MOZ_ASSERT(ssm);
 
-    bool isChrome = nsContentUtils::IsCallerChrome();
+    bool isChrome = nsContentUtils::IsSystemCaller(aCx);
 
     // First check to make sure the caller has permission to make a privileged
     // worker if they called the ChromeWorker/ChromeSharedWorker constructor.
     if (aIsChromeWorker && !isChrome) {
       return NS_ERROR_DOM_SECURITY_ERR;
     }
 
     // Chrome callers (whether creating a ChromeWorker or Worker) always get the
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -395,19 +395,20 @@ WorkerGlobalScope::GetPerformance()
     mPerformance = Performance::CreateForWorker(mWorkerPrivate);
   }
 
   return mPerformance;
 }
 
 already_AddRefed<Promise>
 WorkerGlobalScope::Fetch(const RequestOrUSVString& aInput,
-                         const RequestInit& aInit, ErrorResult& aRv)
+                         const RequestInit& aInit,
+                         CallerType aCallerType, ErrorResult& aRv)
 {
-  return FetchRequest(this, aInput, aInit, aRv);
+  return FetchRequest(this, aInput, aInit, aCallerType, aRv);
 }
 
 already_AddRefed<IDBFactory>
 WorkerGlobalScope::GetIndexedDB(ErrorResult& aErrorResult)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   RefPtr<IDBFactory> indexedDB = mIndexedDB;
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -25,16 +25,17 @@ class Function;
 class IDBFactory;
 enum class ImageBitmapFormat : uint8_t;
 class Performance;
 class Promise;
 class RequestOrUSVString;
 class ServiceWorkerRegistration;
 class WorkerLocation;
 class WorkerNavigator;
+enum class CallerType : uint32_t;
 
 namespace cache {
 
 class CacheStorage;
 
 } // namespace cache
 
 namespace workers {
@@ -150,17 +151,18 @@ public:
   IMPL_EVENT_HANDLER(offline)
 
   void
   Dump(const Optional<nsAString>& aString) const;
 
   Performance* GetPerformance();
 
   already_AddRefed<Promise>
-  Fetch(const RequestOrUSVString& aInput, const RequestInit& aInit, ErrorResult& aRv);
+  Fetch(const RequestOrUSVString& aInput, const RequestInit& aInit,
+        CallerType aCallerType, ErrorResult& aRv);
 
   already_AddRefed<IDBFactory>
   GetIndexedDB(ErrorResult& aErrorResult);
 
   already_AddRefed<cache::CacheStorage>
   GetCaches(ErrorResult& aRv);
 
   bool IsSecureContext() const;
--- a/dom/worklet/Worklet.cpp
+++ b/dom/worklet/Worklet.cpp
@@ -28,17 +28,18 @@ namespace dom {
 
 class WorkletFetchHandler : public PromiseNativeHandler
                           , public nsIStreamLoaderObserver
 {
 public:
   NS_DECL_ISUPPORTS
 
   static already_AddRefed<Promise>
-  Fetch(Worklet* aWorklet, const nsAString& aModuleURL, ErrorResult& aRv)
+  Fetch(Worklet* aWorklet, const nsAString& aModuleURL, CallerType aCallerType,
+        ErrorResult& aRv)
   {
     MOZ_ASSERT(aWorklet);
 
     nsCOMPtr<nsIGlobalObject> global =
       do_QueryInterface(aWorklet->GetParentObject());
     MOZ_ASSERT(global);
 
     RefPtr<Promise> promise = Promise::Create(global, aRv);
@@ -80,17 +81,18 @@ public:
       }
     }
 
     RequestOrUSVString request;
     request.SetAsUSVString().Rebind(aModuleURL.Data(), aModuleURL.Length());
 
     RequestInit init;
 
-    RefPtr<Promise> fetchPromise = FetchRequest(global, request, init, aRv);
+    RefPtr<Promise> fetchPromise =
+      FetchRequest(global, request, init, aCallerType, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       promise->MaybeReject(aRv);
       return promise.forget();
     }
 
     RefPtr<WorkletFetchHandler> handler =
       new WorkletFetchHandler(aWorklet, aModuleURL, promise);
     fetchPromise->AppendNativeHandler(handler);
@@ -345,19 +347,20 @@ Worklet::~Worklet()
 
 JSObject*
 Worklet::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return WorkletBinding::Wrap(aCx, this, aGivenProto);
 }
 
 already_AddRefed<Promise>
-Worklet::Import(const nsAString& aModuleURL, ErrorResult& aRv)
+Worklet::Import(const nsAString& aModuleURL, CallerType aCallerType,
+                ErrorResult& aRv)
 {
-  return WorkletFetchHandler::Fetch(this, aModuleURL, aRv);
+  return WorkletFetchHandler::Fetch(this, aModuleURL, aCallerType, aRv);
 }
 
 WorkletGlobalScope*
 Worklet::GetOrCreateGlobalScope(JSContext* aCx)
 {
   if (!mScope) {
     switch (mWorkletType) {
       case eAudioWorklet:
--- a/dom/worklet/Worklet.h
+++ b/dom/worklet/Worklet.h
@@ -17,16 +17,17 @@ class nsPIDOMWindowInner;
 class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
 class WorkletGlobalScope;
 class WorkletFetchHandler;
+enum class CallerType : uint32_t;
 
 class Worklet final : public nsISupports
                     , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Worklet)
 
@@ -42,17 +43,18 @@ public:
   {
     return mWindow;
   }
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   already_AddRefed<Promise>
-  Import(const nsAString& aModuleURL, ErrorResult& aRv);
+  Import(const nsAString& aModuleURL, CallerType aCallerType,
+         ErrorResult& aRv);
 
   WorkletGlobalScope*
   GetOrCreateGlobalScope(JSContext* aCx);
 
 private:
   ~Worklet();
 
   WorkletFetchHandler*
--- a/dom/xml/XMLDocument.cpp
+++ b/dom/xml/XMLDocument.cpp
@@ -272,17 +272,18 @@ XMLDocument::ResetToURI(nsIURI *aURI, ns
     mChannel->Cancel(NS_BINDING_ABORTED);
     mChannelIsPending = false;
   }
 
   nsDocument::ResetToURI(aURI, aLoadGroup, aPrincipal);
 }
 
 bool
-XMLDocument::Load(const nsAString& aUrl, ErrorResult& aRv)
+XMLDocument::Load(const nsAString& aUrl, CallerType aCallerType,
+                  ErrorResult& aRv)
 {
   bool hasHadScriptObject = true;
   nsIScriptGlobalObject* scriptObject =
     GetScriptHandlingObject(hasHadScriptObject);
   if (!scriptObject && hasHadScriptObject) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return false;
   }
@@ -303,17 +304,17 @@ XMLDocument::Load(const nsAString& aUrl,
 
   // Reporting a warning on ourselves is rather pointless, because we probably
   // have no window id (and hence the warning won't show up in any web console)
   // and probably aren't considered a "content document" because we're not
   // loaded in a docshell, so won't accumulate telemetry for use counters.  Try
   // warning on our entry document, if any, since that should have things like
   // window ids and associated docshells.
   nsIDocument* docForWarning = callingDoc ? callingDoc.get() : this;
-  if (nsContentUtils::IsCallerChrome()) {
+  if (aCallerType == CallerType::System) {
     docForWarning->WarnOnceAbout(nsIDocument::eChromeUseOfDOM3LoadMethod);
   } else {
     docForWarning->WarnOnceAbout(nsIDocument::eUseOfDOM3LoadMethod);
   } 
 
   nsIURI *baseURI = mDocumentURI;
   nsAutoCString charset;
 
--- a/dom/xml/XMLDocument.h
+++ b/dom/xml/XMLDocument.h
@@ -3,16 +3,17 @@
 /* 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 mozilla_dom_XMLDocument_h
 #define mozilla_dom_XMLDocument_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "nsDocument.h"
 #include "nsIDOMXMLDocument.h"
 #include "nsIScriptContext.h"
 
 class nsIURI;
 class nsIChannel;
 
 namespace mozilla {
@@ -52,17 +53,17 @@ public:
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
 
   virtual void DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const override;
   // DocAddSizeOfIncludingThis is inherited from nsIDocument.
 
 
   // WebIDL API
-  bool Load(const nsAString& aUrl, mozilla::ErrorResult& aRv);
+  bool Load(const nsAString& aUrl, CallerType aCallerType, ErrorResult& aRv);
   bool Async() const
   {
     return mAsync;
   }
   void SetAsync(bool aAsync)
   {
     mAsync = aAsync;
   }
--- a/dom/xslt/nsIXSLTProcessorPrivate.idl
+++ b/dom/xslt/nsIXSLTProcessorPrivate.idl
@@ -9,15 +9,9 @@
 interface nsIXSLTProcessorPrivate : nsISupports
 {
   /**
    * Disables all loading of external documents, such as from
    * <xsl:import> and document()
    * Defaults to off and is *not* reset by calls to reset()
    */
   const unsigned long DISABLE_ALL_LOADS = 1;
-
-  /**
-   * Flags for this processor. Defaults to 0. See individual flags above
-   * for documentation for effect of reset()
-   */
-  attribute unsigned long flags;
 };
--- a/dom/xslt/xslt/txMozillaXSLTProcessor.cpp
+++ b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp
@@ -1006,36 +1006,26 @@ txMozillaXSLTProcessor::Reset()
     mStylesheetDocument = nullptr;
     mEmbeddedStylesheetRoot = nullptr;
     mCompileResult = NS_OK;
     mVariables.clear();
 
     return NS_OK;
 }
 
-NS_IMETHODIMP
-txMozillaXSLTProcessor::SetFlags(uint32_t aFlags)
+void
+txMozillaXSLTProcessor::SetFlags(uint32_t aFlags, SystemCallerGuarantee)
 {
-    NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(),
-                   NS_ERROR_DOM_SECURITY_ERR);
-
     mFlags = aFlags;
-
-    return NS_OK;
 }
 
-NS_IMETHODIMP
-txMozillaXSLTProcessor::GetFlags(uint32_t* aFlags)
+uint32_t
+txMozillaXSLTProcessor::Flags(SystemCallerGuarantee)
 {
-    NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(),
-                   NS_ERROR_DOM_SECURITY_ERR);
-
-    *aFlags = mFlags;
-
-    return NS_OK;
+    return mFlags;
 }
 
 NS_IMETHODIMP
 txMozillaXSLTProcessor::LoadStyleSheet(nsIURI* aUri,
                                        nsIDocument* aLoaderDocument)
 {
     mozilla::net::ReferrerPolicy refpol = mozilla::net::RP_Unset;
     if (mStylesheetDocument) {
--- a/dom/xslt/xslt/txMozillaXSLTProcessor.h
+++ b/dom/xslt/xslt/txMozillaXSLTProcessor.h
@@ -12,16 +12,17 @@
 #include "nsIXSLTProcessor.h"
 #include "nsIXSLTProcessorPrivate.h"
 #include "txExpandedNameMap.h"
 #include "txNamespaceMap.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/net/ReferrerPolicy.h"
 
 class nsINode;
 class nsIDOMNode;
 class nsIURI;
 class txStylesheet;
 class txResultRecycler;
 class txIGlobalParameter;
@@ -123,22 +124,18 @@ public:
                              mozilla::ErrorResult& aRv);
     void RemoveParameter(const nsAString& aNamespaceURI,
                          const nsAString& aLocalName,
                          mozilla::ErrorResult& aRv)
     {
         aRv = RemoveParameter(aNamespaceURI, aLocalName);
     }
 
-    uint32_t Flags()
-    {
-        uint32_t flags;
-        GetFlags(&flags);
-        return flags;
-    }
+    uint32_t Flags(mozilla::dom::SystemCallerGuarantee);
+    void SetFlags(uint32_t aFlags, mozilla::dom::SystemCallerGuarantee);
 
     nsresult setStylesheet(txStylesheet* aStylesheet);
     void reportError(nsresult aResult, const char16_t *aErrorText,
                      const char16_t *aSourceText);
 
     nsIDOMNode *GetSourceContentModel()
     {
         return mSource;
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -119,55 +119,65 @@ ImageContainerListener::NotifyComposite(
 void
 ImageContainerListener::ClearImageContainer()
 {
   MutexAutoLock lock(mLock);
   mImageContainer = nullptr;
 }
 
 void
-ImageContainer::EnsureImageClient(bool aCreate)
+ImageContainer::EnsureImageClient()
 {
   // If we're not forcing a new ImageClient, then we can skip this if we don't have an existing
   // ImageClient, or if the existing one belongs to an IPC actor that is still open.
-  if (!aCreate && (!mImageClient || mImageClient->GetForwarder()->GetLayersIPCActor()->IPCOpen())) {
+  if (!mIsAsync) {
+    return;
+  }
+  if (mImageClient && mImageClient->GetForwarder()->GetLayersIPCActor()->IPCOpen()) {
     return;
   }
 
   RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton();
   if (imageBridge) {
     mImageClient = imageBridge->CreateImageClient(CompositableType::IMAGE, this);
     if (mImageClient) {
       mAsyncContainerHandle = mImageClient->GetAsyncHandle();
       mNotifyCompositeListener = new ImageContainerListener(this);
+    } else {
+      // It's okay to drop the async container handle since the ImageBridgeChild
+      // is going to die anyway.
+      mAsyncContainerHandle = CompositableHandle();
+      mNotifyCompositeListener = nullptr;
     }
   }
 }
 
 ImageContainer::ImageContainer(Mode flag)
 : mReentrantMonitor("ImageContainer.mReentrantMonitor"),
   mGenerationCounter(++sGenerationCounter),
   mPaintCount(0),
   mDroppedImageCount(0),
   mImageFactory(new ImageFactory()),
   mRecycleBin(new BufferRecycleBin()),
+  mIsAsync(flag == ASYNCHRONOUS),
   mCurrentProducerID(-1)
 {
   if (flag == ASYNCHRONOUS) {
-    EnsureImageClient(true);
+    EnsureImageClient();
   }
 }
 
 ImageContainer::ImageContainer(const CompositableHandle& aHandle)
   : mReentrantMonitor("ImageContainer.mReentrantMonitor"),
   mGenerationCounter(++sGenerationCounter),
   mPaintCount(0),
   mDroppedImageCount(0),
   mImageFactory(nullptr),
   mRecycleBin(nullptr),
+  mIsAsync(true),
   mAsyncContainerHandle(aHandle),
   mCurrentProducerID(-1)
 {
   MOZ_ASSERT(mAsyncContainerHandle);
 }
 
 ImageContainer::~ImageContainer()
 {
@@ -180,28 +190,28 @@ ImageContainer::~ImageContainer()
     }
   }
 }
 
 RefPtr<PlanarYCbCrImage>
 ImageContainer::CreatePlanarYCbCrImage()
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  EnsureImageClient(false);
+  EnsureImageClient();
   if (mImageClient && mImageClient->AsImageClientSingle()) {
     return new SharedPlanarYCbCrImage(mImageClient);
   }
   return mImageFactory->CreatePlanarYCbCrImage(mScaleHint, mRecycleBin);
 }
 
 RefPtr<SharedRGBImage>
 ImageContainer::CreateSharedRGBImage()
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  EnsureImageClient(false);
+  EnsureImageClient();
   if (!mImageClient || !mImageClient->AsImageClientSingle()) {
     return nullptr;
   }
   return new SharedRGBImage(mImageClient);
 }
 
 void
 ImageContainer::SetCurrentImageInternal(const nsTArray<NonOwningImage>& aImages)
@@ -337,23 +347,24 @@ ImageContainer::SetCurrentImagesInTransa
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   NS_ASSERTION(!mImageClient, "Should use async image transfer with ImageBridge.");
 
   SetCurrentImageInternal(aImages);
 }
 
 bool ImageContainer::IsAsync() const
 {
-  return !!mAsyncContainerHandle;
+  return mIsAsync;
 }
 
 CompositableHandle ImageContainer::GetAsyncContainerHandle()
 {
-  NS_ASSERTION(IsAsync(),"Shared image ID is only relevant to async ImageContainers");
-  EnsureImageClient(false);
+  NS_ASSERTION(IsAsync(), "Shared image ID is only relevant to async ImageContainers");
+  NS_ASSERTION(mAsyncContainerHandle, "Should have a shared image ID");
+  EnsureImageClient();
   return mAsyncContainerHandle;
 }
 
 bool
 ImageContainer::HasCurrentImage()
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -604,17 +604,17 @@ private:
   void SetCurrentImageInternal(const nsTArray<NonOwningImage>& aImages);
 
   // This is called to ensure we have an active image, this may not be true
   // when we're storing image information in a RemoteImageData structure.
   // NOTE: If we have remote data mRemoteDataMutex should be locked when
   // calling this function!
   void EnsureActiveImage();
 
-  void EnsureImageClient(bool aCreate);
+  void EnsureImageClient();
 
   // ReentrantMonitor to protect thread safe access to the "current
   // image", and any other state which is shared between threads.
   ReentrantMonitor mReentrantMonitor;
 
   nsTArray<OwningImage> mCurrentImages;
 
   // Updates every time mActiveImage changes
@@ -644,16 +644,17 @@ private:
   // sucessfully created with ENABLE_ASYNC, or points to null otherwise.
   // 'unsuccessful' in this case only means that the ImageClient could not
   // be created, most likely because off-main-thread compositing is not enabled.
   // In this case the ImageContainer is perfectly usable, but it will forward
   // frames to the compositor through transactions in the main thread rather than
   // asynchronusly using the ImageBridge IPDL protocol.
   RefPtr<ImageClient> mImageClient;
 
+  bool mIsAsync;
   CompositableHandle mAsyncContainerHandle;
 
   nsTArray<FrameID> mFrameIDsNotYetComposited;
   // ProducerID for last current image(s), including the frames in
   // mFrameIDsNotYetComposited
   ProducerID mCurrentProducerID;
 
   RefPtr<ImageContainerListener> mNotifyCompositeListener;
--- a/gfx/layers/apz/util/CheckerboardReportService.cpp
+++ b/gfx/layers/apz/util/CheckerboardReportService.cpp
@@ -154,17 +154,17 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(C
 /*static*/ bool
 CheckerboardReportService::IsEnabled(JSContext* aCtx, JSObject* aGlobal)
 {
   // Only allow this in the parent process
   if (!XRE_IsParentProcess()) {
     return false;
   }
   // Allow privileged code or about:checkerboard (unprivileged) to access this.
-  return nsContentUtils::IsCallerChrome()
+  return nsContentUtils::IsSystemCaller(aCtx)
       || nsContentUtils::IsSpecificAboutPage(aGlobal, "about:checkerboard");
 }
 
 /*static*/ already_AddRefed<CheckerboardReportService>
 CheckerboardReportService::Constructor(const dom::GlobalObject& aGlobal, ErrorResult& aRv)
 {
   RefPtr<CheckerboardReportService> ces = new CheckerboardReportService(aGlobal.GetAsSupports());
   return ces.forget();
--- a/gfx/layers/client/ImageClient.cpp
+++ b/gfx/layers/client/ImageClient.cpp
@@ -220,20 +220,30 @@ ImageClientSingle::UpdateImage(ImageCont
     }
 
     if (!texture) {
       // Slow path, we should not be hitting it very often and if we do it means
       // we are using an Image class that is not backed by textureClient and we
       // should fix it.
       texture = CreateTextureClientForImage(image, GetForwarder());
     }
-    if (!texture || !AddTextureClient(texture)) {
+
+    if (!texture) {
       return false;
     }
 
+    // We check if the texture's allocator is still open, since in between media
+    // decoding a frame and adding it to the compositable, we could have
+    // restarted the GPU process.
+    if (!texture->GetAllocator()->IPCOpen()) {
+      continue;
+    }
+    if (!AddTextureClient(texture)) {
+      return false;
+    }
 
     CompositableForwarder::TimedTextureClient* t = textures.AppendElement();
     t->mTextureClient = texture;
     t->mTimeStamp = img.mTimeStamp;
     t->mPictureRect = image->GetPictureRect();
     t->mFrameID = img.mFrameID;
     t->mProducerID = img.mProducerID;
 
@@ -285,15 +295,21 @@ bool
 ImageClientBridge::UpdateImage(ImageContainer* aContainer, uint32_t aContentFlags)
 {
   if (!GetForwarder() || !mLayer) {
     return false;
   }
   if (mAsyncContainerHandle == aContainer->GetAsyncContainerHandle()) {
     return true;
   }
+
   mAsyncContainerHandle = aContainer->GetAsyncContainerHandle();
+  if (!mAsyncContainerHandle) {
+    // If we couldn't contact a working ImageBridgeParent, just return.
+    return true;
+  }
+
   static_cast<ShadowLayerForwarder*>(GetForwarder())->AttachAsyncCompositable(mAsyncContainerHandle, mLayer);
   return true;
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -415,18 +415,22 @@ LayerTransactionParent::RecvUpdate(const
         return IPC_OK();
       }
       ImageBridgeParent* imageBridge = ImageBridgeParent::GetInstance(OtherPid());
       if (!imageBridge) {
         return IPC_FAIL_NO_REASON(this);
       }
       RefPtr<CompositableHost> host = imageBridge->FindCompositable(op.compositable());
       if (!host) {
-        NS_ERROR("CompositableHost not found in the map");
-        return IPC_FAIL_NO_REASON(this);
+        // This normally should not happen, but can after a GPU process crash.
+        // Media may not have had time to update the ImageContainer associated
+        // with a video frame, and we may try to attach a stale CompositableHandle.
+        // Rather than break the whole transaction, we just continue.
+        gfxCriticalNote << "CompositableHost " << op.compositable().Value() << " not found";
+        continue;
       }
       if (!Attach(AsLayer(op.layer()), host, true)) {
         return IPC_FAIL_NO_REASON(this);
       }
       if (mLayerManager->GetCompositor()) {
         host->SetCompositorID(mLayerManager->GetCompositor()->GetCompositorID());
       }
       break;
--- a/gfx/thebes/gfxPlatformGtk.cpp
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -404,17 +404,17 @@ double
 gfxPlatformGtk::GetDPIScale()
 {
     // Integer scale factors work well with GTK window scaling, image scaling,
     // and pixel alignment, but there is a range where 1 is too small and 2 is
     // too big.  An additional step of 1.5 is added because this is common
     // scale on WINNT and at this ratio the advantages of larger rendering
     // outweigh the disadvantages from scaling and pixel mis-alignment.
     int32_t dpi = GetDPI();
-    if (dpi < 144) {
+    if (dpi < 132) {
         return 1.0;
     } else if (dpi < 168) {
         return 1.5;
     } else {
         return round(dpi/96.0);
     }
 }
 
--- a/ipc/chromium/src/base/compiler_specific.h
+++ b/ipc/chromium/src/base/compiler_specific.h
@@ -62,18 +62,20 @@
 #define MSVC_PUSH_WARNING_LEVEL(n)
 #define MSVC_POP_WARNING()
 #define MSVC_DISABLE_OPTIMIZE()
 #define MSVC_ENABLE_OPTIMIZE()
 #define ALLOW_THIS_IN_INITIALIZER_LIST(code) code
 
 #endif  // COMPILER_MSVC
 
-
-#if defined(COMPILER_GCC)
-#define ALLOW_UNUSED __attribute__((unused))
+// Annotate a function indicating the caller must examine the return value.
+// Use like:
+//   int foo() WARN_UNUSED_RESULT;
+// To explicitly ignore a result, see |ignore_result()| in base/macros.h.
+#undef WARN_UNUSED_RESULT
+#if defined(COMPILER_GCC) || defined(__clang__)
 #define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
-#else  // Not GCC
-#define ALLOW_UNUSED
+#else
 #define WARN_UNUSED_RESULT
 #endif
 
 #endif  // BASE_COMPILER_SPECIFIC_H_
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -426,17 +426,17 @@ WrapperOwner::DOMQI(JSContext* cx, JS::H
 {
     // Someone's calling us, handle nsISupports specially to avoid unnecessary
     // CPOW traffic.
     HandleValue id = args[0];
     if (id.isObject()) {
         RootedObject idobj(cx, &id.toObject());
         nsCOMPtr<nsIJSID> jsid;
 
-        nsresult rv = UnwrapArg<nsIJSID>(idobj, getter_AddRefs(jsid));
+        nsresult rv = UnwrapArg<nsIJSID>(cx, idobj, getter_AddRefs(jsid));
         if (NS_SUCCEEDED(rv)) {
             MOZ_ASSERT(jsid, "bad wrapJS");
             const nsID* idptr = jsid->GetID();
             if (idptr->Equals(NS_GET_IID(nsISupports))) {
                 args.rval().set(args.thisv());
                 return true;
             }
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2594,27 +2594,27 @@ JS_GetElement(JSContext* cx, HandleObjec
 }
 
 JS_PUBLIC_API(bool)
 JS_ForwardSetPropertyTo(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                         HandleValue receiver, ObjectOpResult& result)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    assertSameCompartment(cx, obj, id, receiver);
+    assertSameCompartment(cx, obj, id, v, receiver);
 
     return SetProperty(cx, obj, id, v, receiver, result);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetPropertyById(JSContext* cx, HandleObject obj, HandleId id, HandleValue v)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    assertSameCompartment(cx, obj, id);
+    assertSameCompartment(cx, obj, id, v);
 
     RootedValue receiver(cx, ObjectValue(*obj));
     ObjectOpResult ignored;
     return SetProperty(cx, obj, id, v, receiver, ignored);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetProperty(JSContext* cx, HandleObject obj, const char* name, HandleValue v)
@@ -2758,17 +2758,17 @@ JS_DeleteElement(JSContext* cx, HandleOb
     return JS_DeleteElement(cx, obj, index, ignored);
 }
 
 JS_PUBLIC_API(bool)
 JS_Enumerate(JSContext* cx, HandleObject obj, JS::MutableHandle<IdVector> props)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    assertSameCompartment(cx, obj);
+    assertSameCompartment(cx, obj, props);
     MOZ_ASSERT(props.empty());
 
     AutoIdVector ids(cx);
     if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &ids))
         return false;
 
     return props.append(ids.begin(), ids.end());
 }
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -78,16 +78,21 @@ class CompartmentChecker
         check(rooted.get());
     }
 
     template<typename T>
     void check(Handle<T> handle) {
         check(handle.get());
     }
 
+    template<typename T>
+    void check(MutableHandle<T> handle) {
+        check(handle.get());
+    }
+
     void checkAtom(gc::Cell* cell) {
 #ifdef DEBUG
         // Atoms which move across zone boundaries need to be marked in the new
         // zone, see JS_MarkCrossZoneId.
         if (compartment) {
             JSRuntime* rt = compartment->runtimeFromAnyThread();
             MOZ_ASSERT(rt->gc.atomMarking.atomIsMarked(compartment->zone(), cell));
         }
@@ -111,16 +116,30 @@ class CompartmentChecker
         if (v.isObject())
             check(&v.toObject());
         else if (v.isString())
             check(v.toString());
         else if (v.isSymbol())
             check(v.toSymbol());
     }
 
+    // Check the contents of any container class that supports the C++
+    // iteration protocol, eg GCVector<jsid>.
+    template <typename Container>
+    typename mozilla::EnableIf<
+        mozilla::IsSame<
+            decltype(((Container*)nullptr)->begin()),
+            decltype(((Container*)nullptr)->end())
+        >::value
+    >::Type
+    check(const Container& container) {
+        for (auto i : container)
+            check(i);
+    }
+
     void check(const ValueArray& arr) {
         for (size_t i = 0; i < arr.length; i++)
             check(arr.array[i]);
     }
 
     void check(const JSValueArray& arr) {
         for (size_t i = 0; i < arr.length; i++)
             check(arr.array[i]);
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -284,19 +284,22 @@ SandboxFetch(JSContext* cx, JS::HandleOb
     if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue,
                       "Argument 2 of fetch", false)) {
         return false;
     }
     nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(scope);
     if (!global) {
         return false;
     }
+    dom::CallerType callerType = nsContentUtils::IsSystemCaller(cx) ?
+        dom::CallerType::System : dom::CallerType::NonSystem;
     ErrorResult rv;
     RefPtr<dom::Promise> response =
-        FetchRequest(global, Constify(request), Constify(options), rv);
+        FetchRequest(global, Constify(request), Constify(options),
+                     callerType, rv);
     if (rv.MaybeSetPendingException(cx)) {
         return false;
     }
 
     args.rval().setObject(*response->PromiseObj());
     return true;
 }
 
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1269,17 +1269,17 @@ XPCJSContext::InterruptCallback(JSContex
     // has finished bootstrapping. Avoid crashing in nsContentUtils below.
     if (!nsContentUtils::IsInitialized())
         return true;
 
     // This is at least the second interrupt callback we've received since
     // returning to the event loop. See how long it's been, and what the limit
     // is.
     TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint;
-    bool chrome = nsContentUtils::IsCallerChrome();
+    bool chrome = nsContentUtils::IsSystemCaller(cx);
     const char* prefName = chrome ? PREF_MAX_SCRIPT_RUN_TIME_CHROME
                                   : PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
     int32_t limit = Preferences::GetInt(prefName, chrome ? 20 : 10);
 
     // If there's no limit, or we're within the limit, let it go.
     if (limit == 0 || duration.ToSeconds() < limit / 2.0)
         return true;
 
@@ -3220,17 +3220,17 @@ ReadSourceFromFilename(JSContext* cx, co
 // The JS engine calls this object's 'load' member function when it needs
 // the source for a chrome JS function. See the comment in the XPCJSContext
 // constructor.
 class XPCJSSourceHook: public js::SourceHook {
     bool load(JSContext* cx, const char* filename, char16_t** src, size_t* length) {
         *src = nullptr;
         *length = 0;
 
-        if (!nsContentUtils::IsCallerChrome())
+        if (!nsContentUtils::IsSystemCaller(cx))
             return true;
 
         if (!filename)
             return true;
 
         nsresult rv = ReadSourceFromFilename(cx, filename, src, length);
         if (NS_FAILED(rv)) {
             xpc::Throw(cx, rv);
--- a/js/xpconnect/src/XPCString.cpp
+++ b/js/xpconnect/src/XPCString.cpp
@@ -43,16 +43,17 @@ XPCStringConvert::ClearZoneCache(JS::Zon
 {
     // Although we clear the cache in FinalizeDOMString if needed, we also clear
     // the cache here to avoid a dangling JSString* pointer when compacting GC
     // moves the external string in memory.
 
     ZoneStringCache* cache = static_cast<ZoneStringCache*>(JS_GetZoneUserData(zone));
     if (cache) {
         cache->mBuffer = nullptr;
+        cache->mLength = 0;
         cache->mString = nullptr;
     }
 }
 
 // static
 void
 XPCStringConvert::FinalizeLiteral(JS::Zone* zone, const JSStringFinalizer* fin, char16_t* chars)
 {
@@ -67,16 +68,17 @@ XPCStringConvert::FinalizeDOMString(JS::
 {
     nsStringBuffer* buf = nsStringBuffer::FromData(chars);
 
     // Clear the ZoneStringCache if needed, as this can be called outside GC
     // when flattening an external string.
     ZoneStringCache* cache = static_cast<ZoneStringCache*>(JS_GetZoneUserData(zone));
     if (cache && cache->mBuffer == buf) {
         cache->mBuffer = nullptr;
+        cache->mLength = 0;
         cache->mString = nullptr;
     }
 
     buf->Release();
 }
 
 const JSStringFinalizer XPCStringConvert::sDOMStringFinalizer =
     { XPCStringConvert::FinalizeDOMString };
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -195,17 +195,17 @@ XPC_WN_DoubleWrappedGetter(JSContext* cx
         // responded to this get property call and now gives no object.
         // XXX Should this throw something at the caller?
         args.rval().setNull();
         return true;
     }
 
     // It is a double wrapped object. This should really never appear in
     // content these days, but addons still do it - see bug 965921.
-    if (MOZ_UNLIKELY(!nsContentUtils::IsCallerChrome())) {
+    if (MOZ_UNLIKELY(!nsContentUtils::IsSystemCaller(cx))) {
         JS_ReportErrorASCII(cx, "Attempt to use .wrappedJSObject in untrusted code");
         return false;
     }
     args.rval().setObject(*realObject);
     return JS_WrapValue(cx, args.rval());
 }
 
 /***************************************************************************/
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -1129,17 +1129,17 @@ ReadScriptOrFunction(nsIObjectInputStrea
     uint8_t flags;
     nsresult rv = stream->Read8(&flags);
     if (NS_FAILED(rv))
         return rv;
 
     // We don't serialize mutedError-ness of scripts, which is fine as long as
     // we only serialize system and XUL-y things. We can detect this by checking
     // where the caller wants us to deserialize.
-    MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome() ||
+    MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemCaller(cx) ||
                        CurrentGlobalOrNull(cx) == xpc::CompilationScope());
 
     uint32_t size;
     rv = stream->Read32(&size);
     if (NS_FAILED(rv))
         return rv;
 
     char* data;
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -224,21 +224,28 @@ class XPCStringConvert
     // One-slot cache, because it turns out it's common for web pages to
     // get the same string a few times in a row.  We get about a 40% cache
     // hit rate on this cache last it was measured.  We'd get about 70%
     // hit rate with a hashtable with removal on finalization, but that
     // would take a lot more machinery.
     struct ZoneStringCache
     {
         // mString owns mBuffer.  mString is a JS thing, so it can only die
-        // during GC.  We clear mString and mBuffer during GC.  As long as
-        // the above holds, mBuffer should not be a dangling pointer, so
+        // during GC, though it can drop its ref to the buffer if it gets
+        // flattened and wasn't null-terminated.  We clear mString and mBuffer
+        // during GC and in our finalizer (to catch the flatterning case).  As
+        // long as the above holds, mBuffer should not be a dangling pointer, so
         // using this as a cache key should be safe.
-        void* mBuffer;
-        JSString* mString;
+        //
+        // We also need to include the string's length in the cache key, because
+        // now that we allow non-null-terminated buffers we can have two strings
+        // with the same mBuffer but different lengths.
+        void* mBuffer = nullptr;
+        uint32_t mLength = 0;
+        JSString* mString = nullptr;
     };
 
 public:
 
     // If the string shares the readable's buffer, that buffer will
     // get assigned to *sharedBuffer.  Otherwise null will be
     // assigned.
     static bool ReadableToJSVal(JSContext* cx, const nsAString& readable,
@@ -247,17 +254,17 @@ public:
 
     // Convert the given stringbuffer/length pair to a jsval
     static MOZ_ALWAYS_INLINE bool
     StringBufferToJSVal(JSContext* cx, nsStringBuffer* buf, uint32_t length,
                         JS::MutableHandleValue rval, bool* sharedBuffer)
     {
         JS::Zone* zone = js::GetContextZone(cx);
         ZoneStringCache* cache = static_cast<ZoneStringCache*>(JS_GetZoneUserData(zone));
-        if (cache && buf == cache->mBuffer) {
+        if (cache && buf == cache->mBuffer && length == cache->mLength) {
             MOZ_ASSERT(JS::GetStringZone(cache->mString) == zone);
             JS::MarkStringAsLive(zone, cache->mString);
             rval.setString(cache->mString);
             *sharedBuffer = false;
             return true;
</