Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 10 Feb 2017 15:23:33 -0800
changeset 388943 855e6b2f6199189f37cea093cbdd1735e297e8aa
parent 388902 7b9d9e4a82a6eed63833171d38667313081641db (current diff)
parent 388942 d22eeada4c35924114e24e4974ace290b3e88613 (diff)
child 388944 c38fe985302709182329d729deb05a9eafef2aaf
child 388984 779d10ed78f56643b57e58bf2e60600abf915b13
child 389018 2c7816419218cf97cf1faeea765e55d657647d97
push id7198
push userjlorenzo@mozilla.com
push dateTue, 18 Apr 2017 12:07:49 +0000
treeherdermozilla-beta@d57aa49c3948 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone54.0a1
first release with
nightly linux32
855e6b2f6199 / 54.0a1 / 20170211110225 / files
nightly linux64
855e6b2f6199 / 54.0a1 / 20170211110225 / files
nightly mac
855e6b2f6199 / 54.0a1 / 20170211030205 / files
nightly win32
855e6b2f6199 / 54.0a1 / 20170211030205 / files
nightly win64
855e6b2f6199 / 54.0a1 / 20170211030205 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: FNYBhhHr4dg
dom/media/webaudio/test/browser.ini
dom/media/webaudio/test/browser_bug1181073.js
js/src/jit/BaselineCacheIRCompiler.cpp
netwerk/protocol/http/HttpChannelParent.cpp
security/manager/ssl/security-prefs.js
testing/web-platform/meta/XMLHttpRequest/open-url-bogus.htm.ini
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -540,16 +540,17 @@ DocAccessibleParent::SetCOMProxy(const R
   if (outerDoc) {
     outerDoc->GetNativeInterface((void**) &rawNative);
     MOZ_ASSERT(rawNative);
   }
 
   IAccessibleHolder::COMPtrType ptr(rawNative);
   IAccessibleHolder holder(Move(ptr));
 
+  IAccessibleHolder hWndAccHolder;
   if (nsWinUtils::IsWindowEmulationStarted()) {
     RootAccessible* rootDocument = outerDoc->RootAccessible();
     MOZ_ASSERT(rootDocument);
 
     bool isActive = true;
     nsIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0);
     if (Compatibility::IsDolphin()) {
       rect = Bounds();
@@ -563,20 +564,26 @@ DocAccessibleParent::SetCOMProxy(const R
     HWND hWnd = nsWinUtils::CreateNativeWindow(kClassNameTabContent,
                                                parentWnd, rect.x, rect.y,
                                                rect.width, rect.height,
                                                isActive);
     if (hWnd) {
       // Attach accessible document to the emulated native window
       ::SetPropW(hWnd, kPropNameDocAccParent, (HANDLE)this);
       SetEmulatedWindowHandle(hWnd);
+      IAccessible* rawHWNDAcc = nullptr;
+      if (SUCCEEDED(::AccessibleObjectFromWindow(hWnd, OBJID_WINDOW,
+                                                 IID_IAccessible,
+                                                 (void**)&rawHWNDAcc))) {
+        hWndAccHolder.Set(IAccessibleHolder::COMPtrType(rawHWNDAcc));
+      }
     }
   }
   Unused << SendParentCOMProxy(holder, reinterpret_cast<uintptr_t>(
-                               mEmulatedWindowHandle));
+                               mEmulatedWindowHandle), hWndAccHolder);
 }
 
 void
 DocAccessibleParent::SetEmulatedWindowHandle(HWND aWindowHandle)
 {
   if (!aWindowHandle && mEmulatedWindowHandle && IsTopLevel()) {
     ::DestroyWindow(mEmulatedWindowHandle);
   }
--- a/accessible/ipc/win/DocAccessibleChild.cpp
+++ b/accessible/ipc/win/DocAccessibleChild.cpp
@@ -42,22 +42,28 @@ DocAccessibleChild::Shutdown()
   }
 
   PushDeferredEvent(MakeUnique<SerializedShutdown>(this));
   DetachDocument();
 }
 
 ipc::IPCResult
 DocAccessibleChild::RecvParentCOMProxy(const IAccessibleHolder& aParentCOMProxy,
-                                       const WindowsHandle& aEmulatedWindowHandle)
+                                       const WindowsHandle& aEmulatedWindowHandle,
+                                       const IAccessibleHolder& aEmulatedWindowCOMProxy)
 {
   MOZ_ASSERT(!mParentProxy && !aParentCOMProxy.IsNull());
   mParentProxy.reset(const_cast<IAccessibleHolder&>(aParentCOMProxy).Release());
   SetConstructedInParentProcess();
   mEmulatedWindowHandle = reinterpret_cast<HWND>(aEmulatedWindowHandle);
+  if (!aEmulatedWindowCOMProxy.IsNull()) {
+    MOZ_ASSERT(!mEmulatedWindowProxy);
+    mEmulatedWindowProxy.reset(
+      const_cast<IAccessibleHolder&>(aEmulatedWindowCOMProxy).Release());
+  }
 
   for (uint32_t i = 0, l = mDeferredEvents.Length(); i < l; ++i) {
     mDeferredEvents[i]->Dispatch();
   }
 
   mDeferredEvents.Clear();
 
   return IPC_OK();
--- a/accessible/ipc/win/DocAccessibleChild.h
+++ b/accessible/ipc/win/DocAccessibleChild.h
@@ -24,19 +24,21 @@ class DocAccessibleChild : public DocAcc
 public:
   explicit DocAccessibleChild(DocAccessible* aDoc);
   ~DocAccessibleChild();
 
   virtual void Shutdown() override;
 
   virtual ipc::IPCResult
   RecvParentCOMProxy(const IAccessibleHolder& aParentCOMProxy,
-                     const WindowsHandle& aEmulatedWindowHandle) override;
+                     const WindowsHandle& aEmulatedWindowHandle,
+                     const IAccessibleHolder& aEmulatedWindowCOMProxy) override;
 
   HWND GetEmulatedWindowHandle() const { return mEmulatedWindowHandle; }
+  IAccessible* GetEmulatedWindowIAccessible() const { return mEmulatedWindowProxy.get(); }
 
   IAccessible* GetParentIAccessible() const { return mParentProxy.get(); }
 
   bool SendEvent(const uint64_t& aID, const uint32_t& type);
   bool SendHideEvent(const uint64_t& aRootID, const bool& aFromUser);
   bool SendStateChangeEvent(const uint64_t& aID, const uint64_t& aState,
                             const bool& aEnabled);
   bool SendCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset);
@@ -307,16 +309,17 @@ private:
     void Dispatch(DocAccessibleChild* aIPCDoc) override
     {
       aIPCDoc->Shutdown();
     }
   };
 
   bool mIsRemoteConstructed;
   mscom::ProxyUniquePtr<IAccessible> mParentProxy;
+  mscom::ProxyUniquePtr<IAccessible> mEmulatedWindowProxy;
   nsTArray<UniquePtr<DeferredEvent>> mDeferredEvents;
   HWND mEmulatedWindowHandle;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_DocAccessibleChild_h
--- a/accessible/ipc/win/PDocAccessible.ipdl
+++ b/accessible/ipc/win/PDocAccessible.ipdl
@@ -62,15 +62,16 @@ parent:
    */
   async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID);
 
   sync GetWindowedPluginIAccessible(WindowsHandle aHwnd)
     returns (IAccessibleHolder aPluginCOMProxy);
 
 child:
   async ParentCOMProxy(IAccessibleHolder aParentCOMProxy,
-                       WindowsHandle aEmulatedWindowHandle);
+                       WindowsHandle aEmulatedWindowHandle,
+                       IAccessibleHolder aEmulatedWindowCOMProxy);
 
   async __delete__();
 };
 
 }
 }
--- a/accessible/windows/msaa/DocAccessibleWrap.cpp
+++ b/accessible/windows/msaa/DocAccessibleWrap.cpp
@@ -48,17 +48,24 @@ STDMETHODIMP
 DocAccessibleWrap::get_accParent(
       /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispParent)
 {
   // We might be a top-level document in a content process.
   DocAccessibleChild* ipcDoc = IPCDoc();
   if (!ipcDoc) {
     return DocAccessible::get_accParent(ppdispParent);
   }
-  IAccessible* dispParent = ipcDoc->GetParentIAccessible();
+
+  // Emulated window proxy is only set for the top level content document when
+  // emulation is enabled.
+  IAccessible* dispParent = ipcDoc->GetEmulatedWindowIAccessible();
+  if (!dispParent) {
+    dispParent = ipcDoc->GetParentIAccessible();
+  }
+
   if (!dispParent) {
     return S_FALSE;
   }
 
   dispParent->AddRef();
   *ppdispParent = static_cast<IDispatch*>(dispParent);
   return S_OK;
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -146,19 +146,26 @@ XPCOMUtils.defineLazyGetter(this, "PageM
   Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
   return new tmp.PageMenuParent();
 });
 
 XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function() {
   let tmp = {};
   Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
   try {
+    // Hide all notifications while the URL is being edited and the address bar
+    // has focus, including the virtual focus in the results popup.
+    let shouldSuppress = () => {
+      return gURLBar.getAttribute("pageproxystate") != "valid" &&
+             gURLBar.focused;
+    };
     return new tmp.PopupNotifications(gBrowser,
                                       document.getElementById("notification-popup"),
-                                      document.getElementById("notification-popup-box"));
+                                      document.getElementById("notification-popup-box"),
+                                      { shouldSuppress });
   } catch (ex) {
     Cu.reportError(ex);
     return null;
   }
 });
 
 XPCOMUtils.defineLazyGetter(this, "Win7Features", function() {
   if (AppConstants.platform != "win")
@@ -2436,27 +2443,18 @@ function BrowserPageInfo(documentURL, in
                     "chrome,toolbar,dialog=no,resizable", args);
 }
 
 /**
  * Sets the URI to display in the location bar.
  *
  * @param aURI [optional]
  *        nsIURI to set. If this is unspecified, the current URI will be used.
- * @param aOptions [optional]
- *        An object with the following properties:
- *        {
- *          isForLocationChange:
- *            Set to true to indicate that the function was invoked to respond
- *            to a location change event, rather than to reset the current URI
- *            value. This is useful to avoid calling PopupNotifications.jsm
- *            multiple times.
- *        }
  */
-function URLBarSetURI(aURI, aOptions = {}) {
+function URLBarSetURI(aURI) {
   var value = gBrowser.userTypedValue;
   var valid = false;
 
   if (value == null) {
     let uri = aURI || gBrowser.currentURI;
     // Strip off "wyciwyg://" and passwords for the location bar
     try {
       uri = Services.uriFixup.createExposableURI(uri);
@@ -2479,17 +2477,17 @@ function URLBarSetURI(aURI, aOptions = {
       }
     }
 
     valid = !isBlankPageURL(uri.spec);
   }
 
   gURLBar.value = value;
   gURLBar.valueIsTyped = !valid;
-  SetPageProxyState(valid ? "valid" : "invalid", aOptions);
+  SetPageProxyState(valid ? "valid" : "invalid");
 }
 
 function losslessDecodeURI(aURI) {
   let scheme = aURI.scheme;
   if (scheme == "moz-action")
     throw new Error("losslessDecodeURI should never get a moz-action URI");
 
   var value = aURI.spec;
@@ -2586,48 +2584,56 @@ function UpdatePageProxyState() {
  * different than the loaded page, because it's being edited or because a search
  * result is currently selected and is displayed in the location bar.
  *
  * @param aState
  *        The string "valid" indicates that the security indicators and other
  *        related user interface elments should be shown because the URI in the
  *        location bar matches the loaded page. The string "invalid" indicates
  *        that the URI in the location bar is different than the loaded page.
- * @param aOptions [optional]
- *        An object with the following properties:
- *        {
- *          isForLocationChange:
- *            Set to true to indicate that the function was invoked to respond
- *            to a location change event. This is useful to avoid calling
- *            PopupNotifications.jsm multiple times.
- *        }
  */
-function SetPageProxyState(aState, aOptions = {}) {
+function SetPageProxyState(aState) {
   if (!gURLBar)
     return;
 
+  let oldPageProxyState = gURLBar.getAttribute("pageproxystate");
+  // The "browser_urlbar_stop_pending.js" test uses a MutationObserver to do
+  // some verifications at this point, and it breaks if we don't write the
+  // attribute, even if it hasn't changed (bug 1338115).
   gURLBar.setAttribute("pageproxystate", aState);
 
   // the page proxy state is set to valid via OnLocationChange, which
   // gets called when we switch tabs.
   if (aState == "valid") {
     gLastValidURLStr = gURLBar.value;
     gURLBar.addEventListener("input", UpdatePageProxyState);
   } else if (aState == "invalid") {
     gURLBar.removeEventListener("input", UpdatePageProxyState);
   }
 
-  // Only need to call anchorVisibilityChange if the PopupNotifications object
-  // for this window has already been initialized (i.e. its getter no
-  // longer exists). If this is the result of a locations change, then we will
-  // already invoke PopupNotifications.locationChange separately.
-  if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get &&
-      !aOptions.isForLocationChange) {
-    PopupNotifications.anchorVisibilityChange();
-  }
+  // After we've ensured that we've applied the listeners and updated the value
+  // of gLastValidURLStr, return early if the actual state hasn't changed.
+  if (oldPageProxyState == aState) {
+    return;
+  }
+
+  UpdatePopupNotificationsVisibility();
+}
+
+function UpdatePopupNotificationsVisibility() {
+  // Only need to do something if the PopupNotifications object for this window
+  // has already been initialized (i.e. its getter no longer exists).
+  if (Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
+    return;
+  }
+
+  // Notify PopupNotifications that the visible anchors may have changed. This
+  // also checks the suppression state according to the "shouldSuppress"
+  // function defined earlier in this file.
+  PopupNotifications.anchorVisibilityChange();
 }
 
 function PageProxyClickHandler(aEvent) {
   if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
     middleMousePaste(aEvent);
 }
 
 var gMenuButtonBadgeManager = {
@@ -4536,17 +4542,17 @@ var XULBrowserWindow = {
       if ((location == "about:blank" && checkEmptyPageOrigin()) ||
           location == "") {  // Second condition is for new tabs, otherwise
                              // reload function is enabled until tab is refreshed.
         this.reloadCommand.setAttribute("disabled", "true");
       } else {
         this.reloadCommand.removeAttribute("disabled");
       }
 
-      URLBarSetURI(aLocationURI, { isForLocationChange: true });
+      URLBarSetURI(aLocationURI);
 
       BookmarkingUI.onLocationChange();
 
       gIdentityHandler.onLocationChange();
 
       SocialUI.updateState();
 
       UITour.onLocationChange(location);
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_no_anchors.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_no_anchors.js
@@ -97,57 +97,134 @@ var tests = [
       dismissNotification(popup);
     },
     onHidden(popup) {
       this.notification.remove();
       gBrowser.removeTab(gBrowser.selectedTab);
       gBrowser.selectedTab = this.oldSelectedTab;
     }
   },
-  // Test that popupnotifications are anchored to the identity icon while
-  // editing the URL in the location bar, and restored to their anchors when the
-  // URL is reverted.
+  // Test that popupnotifications are hidden while editing the URL in the
+  // location bar, anchored to the identity icon when the focus is moved away
+  // from the location bar, and restored when the URL is reverted.
   { id: "Test#4",
     *run() {
-      this.oldSelectedTab = gBrowser.selectedTab;
-      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+      for (let persistent of [false, true]) {
+        let shown = waitForNotificationPanel();
+        this.notifyObj = new BasicNotification(this.id);
+        this.notifyObj.anchorID = "geo-notification-icon";
+        this.notifyObj.addOptions({ persistent });
+        this.notification = showNotification(this.notifyObj);
+        yield shown;
+
+        checkPopup(PopupNotifications.panel, this.notifyObj);
+
+        // Typing in the location bar should hide the notification.
+        let hidden = waitForNotificationPanelHidden();
+        gURLBar.select();
+        EventUtils.synthesizeKey("*", {});
+        yield hidden;
+
+        is(document.getElementById("geo-notification-icon").boxObject.width, 0,
+           "geo anchor shouldn't be visible");
+
+        // Moving focus to the next control should show the notifications again,
+        // anchored to the identity icon. We clear the URL bar before moving the
+        // focus so that the awesomebar popup doesn't get in the way.
+        shown = waitForNotificationPanel();
+        EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+        EventUtils.synthesizeKey("VK_TAB", {});
+        yield shown;
+
+        is(PopupNotifications.panel.anchorNode.id, "identity-icon",
+           "notification anchored to identity icon");
+
+        // Moving focus to the location bar should hide the notification again.
+        hidden = waitForNotificationPanelHidden();
+        EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
+        yield hidden;
+
+        // Reverting the URL should show the notification again.
+        shown = waitForNotificationPanel();
+        EventUtils.synthesizeKey("VK_ESCAPE", {});
+        yield shown;
+
+        checkPopup(PopupNotifications.panel, this.notifyObj);
 
-      let shownInitially = waitForNotificationPanel();
+        hidden = waitForNotificationPanelHidden();
+        this.notification.remove();
+        yield hidden;
+      }
+      goNext();
+    }
+  },
+  // Test that popupnotifications triggered while editing the URL in the
+  // location bar are only shown later when the URL is reverted.
+  { id: "Test#5",
+    *run() {
+      for (let persistent of [false, true]) {
+        // Start editing the URL, ensuring that the awesomebar popup is hidden.
+        gURLBar.select();
+        EventUtils.synthesizeKey("*", {});
+        EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+
+        // Trying to show a notification should display nothing.
+        let notShowing = promiseTopicObserved("PopupNotifications-updateNotShowing");
+        this.notifyObj = new BasicNotification(this.id);
+        this.notifyObj.anchorID = "geo-notification-icon";
+        this.notifyObj.addOptions({ persistent });
+        this.notification = showNotification(this.notifyObj);
+        yield notShowing;
+
+        // Reverting the URL should show the notification.
+        let shown = waitForNotificationPanel();
+        EventUtils.synthesizeKey("VK_ESCAPE", {});
+        yield shown;
+
+        checkPopup(PopupNotifications.panel, this.notifyObj);
+
+        let hidden = waitForNotificationPanelHidden();
+        this.notification.remove();
+        yield hidden;
+      }
+
+      goNext();
+    }
+  },
+  // Test that persistent panels are still open after switching to another tab
+  // and back, even while editing the URL in the new tab.
+  { id: "Test#6",
+    *run() {
+      let shown = waitForNotificationPanel();
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.anchorID = "geo-notification-icon";
       this.notifyObj.addOptions({
         persistent: true,
       });
       this.notification = showNotification(this.notifyObj);
-      yield shownInitially;
+      yield shown;
+
+      // Switching to a new tab should hide the notification.
+      let hidden = waitForNotificationPanelHidden();
+      this.oldSelectedTab = gBrowser.selectedTab;
+      yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+      yield hidden;
+
+      // Start editing the URL.
+      gURLBar.select();
+      EventUtils.synthesizeKey("*", {});
+
+      // Switching to the old tab should show the notification again.
+      shown = waitForNotificationPanel();
+      gBrowser.removeTab(gBrowser.selectedTab);
+      gBrowser.selectedTab = this.oldSelectedTab;
+      yield shown;
 
       checkPopup(PopupNotifications.panel, this.notifyObj);
 
-      let shownAgain = waitForNotificationPanel();
-      // This will cause the popup to hide and show again.
-      gURLBar.select();
-      EventUtils.synthesizeKey("*", {});
-      // Keep the URL bar empty, so we don't show the awesomebar.
-      EventUtils.synthesizeKey("VK_BACK_SPACE", {});
-      yield shownAgain;
-
-      is(document.getElementById("geo-notification-icon").boxObject.width, 0,
-         "geo anchor shouldn't be visible");
-      is(PopupNotifications.panel.anchorNode.id, "identity-icon",
-         "notification anchored to identity icon");
-
-      let shownLastTime = waitForNotificationPanel();
-      // This will cause the popup to hide and show again.
-      EventUtils.synthesizeKey("VK_ESCAPE", {});
-      yield shownLastTime;
-
-      checkPopup(PopupNotifications.panel, this.notifyObj);
-
-      let hidden = new Promise(resolve => onPopupEvent("popuphidden", resolve));
+      hidden = waitForNotificationPanelHidden();
       this.notification.remove();
-      gBrowser.removeTab(gBrowser.selectedTab);
-      gBrowser.selectedTab = this.oldSelectedTab;
       yield hidden;
 
       goNext();
     }
   },
 ];
--- a/browser/base/content/test/popupNotifications/head.js
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -259,16 +259,24 @@ function onPopupEvent(eventName, callbac
 function waitForNotificationPanel() {
   return new Promise(resolve => {
     onPopupEvent("popupshown", function() {
       resolve(this);
     });
   });
 }
 
+function waitForNotificationPanelHidden() {
+  return new Promise(resolve => {
+    onPopupEvent("popuphidden", function() {
+      resolve(this);
+    });
+  });
+}
+
 function triggerMainCommand(popup) {
   let notifications = popup.childNodes;
   ok(notifications.length > 0, "at least one notification displayed");
   let notification = notifications[0];
   info("Triggering main command for notification " + notification.id);
   EventUtils.synthesizeMouseAtCenter(notification.button, {});
 }
 
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1222,23 +1222,29 @@ file, You can obtain one at http://mozil
             this._clearNoActions();
         }
       ]]></handler>
 
       <handler event="focus"><![CDATA[
         if (event.originalTarget == this.inputField) {
           this._hideURLTooltip();
           this.formatValue();
+          if (this.getAttribute("pageproxystate") != "valid") {
+            UpdatePopupNotificationsVisibility();
+          }
         }
       ]]></handler>
 
       <handler event="blur"><![CDATA[
         if (event.originalTarget == this.inputField) {
           this._clearNoActions();
           this.formatValue();
+          if (this.getAttribute("pageproxystate") != "valid") {
+            UpdatePopupNotificationsVisibility();
+          }
         }
         if (ExtensionSearchHandler.hasActiveInputSession()) {
           ExtensionSearchHandler.handleInputCancelled();
         }
       ]]></handler>
 
       <handler event="dragstart" phase="capturing"><![CDATA[
         // Drag only if the gesture starts from the input field.
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -1483,39 +1483,41 @@ var PlacesControllerDragHelper = {
       // expected cases, which are either unsupported flavors, or items which
       // cannot be dropped in the current insertionpoint. The last case will
       // likely force us to use unwrapNodes for the private data types of
       // places.
       if (flavor == TAB_DROP_TYPE)
         continue;
 
       let data = dt.mozGetDataAt(flavor, i);
-      let dragged;
+      let nodes;
       try {
-        dragged = PlacesUtils.unwrapNodes(data, flavor)[0];
+        nodes = PlacesUtils.unwrapNodes(data, flavor);
       } catch (e) {
         return false;
       }
 
-      // Only bookmarks and urls can be dropped into tag containers.
-      if (ip.isTag &&
-          dragged.type != PlacesUtils.TYPE_X_MOZ_URL &&
-          (dragged.type != PlacesUtils.TYPE_X_MOZ_PLACE ||
-           (dragged.uri && dragged.uri.startsWith("place:")) ))
-        return false;
+      for (let dragged of nodes) {
+        // Only bookmarks and urls can be dropped into tag containers.
+        if (ip.isTag &&
+            dragged.type != PlacesUtils.TYPE_X_MOZ_URL &&
+            (dragged.type != PlacesUtils.TYPE_X_MOZ_PLACE ||
+             (dragged.uri && dragged.uri.startsWith("place:")) ))
+          return false;
 
-      // The following loop disallows the dropping of a folder on itself or
-      // on any of its descendants.
-      if (dragged.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER ||
-          (dragged.uri && dragged.uri.startsWith("place:")) ) {
-        let parentId = ip.itemId;
-        while (parentId != PlacesUtils.placesRootId) {
-          if (dragged.concreteId == parentId || dragged.id == parentId)
-            return false;
-          parentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId);
+        // The following loop disallows the dropping of a folder on itself or
+        // on any of its descendants.
+        if (dragged.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER ||
+            (dragged.uri && dragged.uri.startsWith("place:")) ) {
+          let parentId = ip.itemId;
+          while (parentId != PlacesUtils.placesRootId) {
+            if (dragged.concreteId == parentId || dragged.id == parentId)
+              return false;
+            parentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId);
+          }
         }
       }
     }
     return true;
   },
 
   /**
    * Determines if an unwrapped node can be moved.
@@ -1571,73 +1573,87 @@ var PlacesControllerDragHelper = {
     let doCopy = ["copy", "link"].includes(dt.dropEffect);
 
     let transactions = [];
     let dropCount = dt.mozItemCount;
     let movedCount = 0;
     let parentGuid = PlacesUIUtils.useAsyncTransactions ?
                        (yield insertionPoint.promiseGuid()) : null;
     let tagName = insertionPoint.tagName;
+
+    // Following flavors may contain duplicated data.
+    let duplicable = new Map();
+    duplicable.set(PlacesUtils.TYPE_UNICODE, new Set());
+    duplicable.set(PlacesUtils.TYPE_X_MOZ_URL, new Set());
+
     for (let i = 0; i < dropCount; ++i) {
       let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
       if (!flavor)
         return;
 
       let data = dt.mozGetDataAt(flavor, i);
-      let unwrapped;
+      if (duplicable.has(flavor)) {
+        let handled = duplicable.get(flavor);
+        if (handled.has(data))
+          continue;
+        handled.add(data);
+      }
+
+      let nodes;
       if (flavor != TAB_DROP_TYPE) {
-        // There's only ever one in the D&D case.
-        unwrapped = PlacesUtils.unwrapNodes(data, flavor)[0];
+        nodes = PlacesUtils.unwrapNodes(data, flavor);
       } else if (data instanceof XULElement && data.localName == "tab" &&
                data.ownerGlobal instanceof ChromeWindow) {
         let uri = data.linkedBrowser.currentURI;
         let spec = uri ? uri.spec : "about:blank";
-        unwrapped = { uri: spec,
-                      title: data.label,
-                      type: PlacesUtils.TYPE_X_MOZ_URL};
+        nodes = [{ uri: spec,
+                   title: data.label,
+                   type: PlacesUtils.TYPE_X_MOZ_URL}];
       } else
         throw new Error("bogus data was passed as a tab");
 
-      let index = insertionPoint.index;
+      for (let unwrapped of nodes) {
+        let index = insertionPoint.index;
 
-      // Adjust insertion index to prevent reversal of dragged items. When you
-      // drag multiple elts upward: need to increment index or each successive
-      // elt will be inserted at the same index, each above the previous.
-      let dragginUp = insertionPoint.itemId == unwrapped.parent &&
-                      index < PlacesUtils.bookmarks.getItemIndex(unwrapped.id);
-      if (index != -1 && dragginUp)
-        index += movedCount++;
+        // Adjust insertion index to prevent reversal of dragged items. When you
+        // drag multiple elts upward: need to increment index or each successive
+        // elt will be inserted at the same index, each above the previous.
+        let dragginUp = insertionPoint.itemId == unwrapped.parent &&
+                        index < PlacesUtils.bookmarks.getItemIndex(unwrapped.id);
+        if (index != -1 && dragginUp)
+          index += movedCount++;
 
-      // If dragging over a tag container we should tag the item.
-      if (insertionPoint.isTag) {
-        let uri = NetUtil.newURI(unwrapped.uri);
-        let tagItemId = insertionPoint.itemId;
-        if (PlacesUIUtils.useAsyncTransactions)
-          transactions.push(PlacesTransactions.Tag({ uri, tag: tagName }));
-        else
-          transactions.push(new PlacesTagURITransaction(uri, [tagItemId]));
-      } else {
-        // If this is not a copy, check for safety that we can move the source,
-        // otherwise report an error and fallback to a copy.
-        if (!doCopy && !PlacesControllerDragHelper.canMoveUnwrappedNode(unwrapped)) {
-          Components.utils.reportError("Tried to move an unmovable Places " +
-                                       "node, reverting to a copy operation.");
-          doCopy = true;
-        }
-        if (PlacesUIUtils.useAsyncTransactions) {
-          transactions.push(
-            PlacesUIUtils.getTransactionForData(unwrapped,
-                                                flavor,
-                                                parentGuid,
-                                                index,
-                                                doCopy));
+        // If dragging over a tag container we should tag the item.
+        if (insertionPoint.isTag) {
+          let uri = NetUtil.newURI(unwrapped.uri);
+          let tagItemId = insertionPoint.itemId;
+          if (PlacesUIUtils.useAsyncTransactions)
+            transactions.push(PlacesTransactions.Tag({ uri, tag: tagName }));
+          else
+            transactions.push(new PlacesTagURITransaction(uri, [tagItemId]));
         } else {
-          transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
-                              flavor, insertionPoint.itemId,
-                              index, doCopy));
+          // If this is not a copy, check for safety that we can move the
+          // source, otherwise report an error and fallback to a copy.
+          if (!doCopy && !PlacesControllerDragHelper.canMoveUnwrappedNode(unwrapped)) {
+            Components.utils.reportError("Tried to move an unmovable Places " +
+                                         "node, reverting to a copy operation.");
+            doCopy = true;
+          }
+          if (PlacesUIUtils.useAsyncTransactions) {
+            transactions.push(
+              PlacesUIUtils.getTransactionForData(unwrapped,
+                                                  flavor,
+                                                  parentGuid,
+                                                  index,
+                                                  doCopy));
+          } else {
+            transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
+                                flavor, insertionPoint.itemId,
+                                index, doCopy));
+          }
         }
       }
     }
 
     if (PlacesUIUtils.useAsyncTransactions) {
       yield PlacesTransactions.batch(transactions);
     } else {
       let txn = new PlacesAggregatedTransaction("DropItems", transactions);
--- a/browser/components/places/tests/browser/browser_475045.js
+++ b/browser/components/places/tests/browser/browser_475045.js
@@ -47,19 +47,59 @@ add_task(function* test() {
     let bookmarkIds = PlacesUtils.bookmarks
                       .getBookmarkIdsForURI(uri);
     ok(bookmarkIds.length == 1, "There should be exactly one bookmark");
 
     PlacesUtils.bookmarks.removeItem(bookmarkIds[0]);
 
     // Verify that we removed the bookmark successfully.
     ok(!PlacesUtils.bookmarks.isBookmarked(uri), "URI should be removed");
-  }
+  };
+
+  /**
+   * Simulates a drop of multiple URIs onto the bookmarks bar.
+   *
+   * @param aEffect
+   *        The effect to use for the drop operation: move, copy, or link.
+   * @param aMimeType
+   *        The mime type to use for the drop operation.
+   */
+  let simulateDragDropMultiple = function(aEffect, aMimeType) {
+    const uriSpecs = [
+      "http://www.mozilla.org/C54263C6-A484-46CF-8E2B-FE131586348A",
+      "http://www.mozilla.org/71381257-61E6-4376-AF7C-BF3C5FD8870D",
+      "http://www.mozilla.org/091A88BD-5743-4C16-A005-3D2EA3A3B71E"
+    ];
+    let uris = uriSpecs.map(spec => makeURI(spec));
+    let data;
+    if (aMimeType == "text/x-moz-url")
+      data = uriSpecs.map(spec => spec + "\n" + spec).join("\n");
+    else
+      data = uriSpecs.join("\n");
+    EventUtils.synthesizeDrop(placesItems.childNodes[0],
+                              placesItems,
+                              [[{type: aMimeType,
+                                 data}]],
+                              aEffect, window);
+
+    // Verify that the drop produces exactly one bookmark per each URL.
+    for (let uri of uris) {
+      let bookmarkIds = PlacesUtils.bookmarks
+        .getBookmarkIdsForURI(uri);
+      ok(bookmarkIds.length == 1, "There should be exactly one bookmark");
+
+      PlacesUtils.bookmarks.removeItem(bookmarkIds[0]);
+
+      // Verify that we removed the bookmark successfully.
+      ok(!PlacesUtils.bookmarks.isBookmarked(uri), "URI should be removed");
+    }
+  };
 
   // Simulate a bookmark drop for all of the mime types and effects.
   let mimeTypes = ["text/plain", "text/unicode", "text/x-moz-url"];
   let effects = ["move", "copy", "link"];
   effects.forEach(function(effect) {
     mimeTypes.forEach(function(mimeType) {
       simulateDragDrop(effect, mimeType);
+      simulateDragDropMultiple(effect, mimeType);
     });
   });
 });
--- a/browser/components/sessionstore/test/browser_undoCloseById.js
+++ b/browser/components/sessionstore/test/browser_undoCloseById.js
@@ -1,35 +1,35 @@
 "use strict";
 
 /**
  * This test is for the undoCloseById function.
  */
 
 Cu.import("resource:///modules/sessionstore/SessionStore.jsm");
 
-function openAndCloseTab(window, url) {
+async function openAndCloseTab(window, url) {
   let tab = window.gBrowser.addTab(url);
-  yield promiseBrowserLoaded(tab.linkedBrowser, true, url);
-  yield TabStateFlusher.flush(tab.linkedBrowser);
-  yield promiseRemoveTab(tab);
+  await promiseBrowserLoaded(tab.linkedBrowser, true, url);
+  await TabStateFlusher.flush(tab.linkedBrowser);
+  await promiseRemoveTab(tab);
 }
 
-function* openWindow(url) {
-  let win = yield promiseNewWindowLoaded();
+async function openWindow(url) {
+  let win = await promiseNewWindowLoaded();
   let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
   win.gBrowser.selectedBrowser.loadURIWithFlags(url, flags);
-  yield promiseBrowserLoaded(win.gBrowser.selectedBrowser, true, url);
+  await promiseBrowserLoaded(win.gBrowser.selectedBrowser, true, url);
   return win;
 }
 
-function closeWindow(win) {
-  yield BrowserTestUtils.closeWindow(win);
+async function closeWindow(win) {
+  await BrowserTestUtils.closeWindow(win);
   // Wait 20 ms to allow SessionStorage a chance to register the closed window.
-  yield new Promise(resolve => setTimeout(resolve, 20));
+  await new Promise(resolve => setTimeout(resolve, 20));
 }
 
 add_task(function* test_undoCloseById() {
   // Clear the lists of closed windows and tabs.
   forgetClosedWindows();
   while (SessionStore.getClosedTabCount(window)) {
     SessionStore.forgetClosedTab(window, 0);
   }
--- a/browser/extensions/mortar/moz.build
+++ b/browser/extensions/mortar/moz.build
@@ -1,11 +1,13 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+
 SOURCES += [
     'host/rpc.cc',
 ]
 
 SharedLibrary('rpc')
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/test/unit/head.js
@@ -0,0 +1,59 @@
+"use strict";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+// For the following 5 lines of codes, we redirect the
+// path of the "ppapi.js" in addon to the exact file path.
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+let resHandler = Services.io.getProtocolHandler("resource")
+                         .QueryInterface(Components.interfaces.nsISubstitutingProtocolHandler);
+let dataURI = NetUtil.newURI(do_get_file("."));
+resHandler.setSubstitution("ppapi.js", dataURI);
+
+// Load the script
+load("ppapi-runtime.jsm");
+
+let instanceId = 1;
+let url = "http://example.com";
+let info = {
+    documentURL: "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai",
+    url,
+    setupJSInstanceObject: false,
+    isFullFrame: false, //pluginElement.ownerDocument.mozSyntheticDocument,
+    arguments: {
+      keys: ["src", "full-frame", "top-level-url"],
+      values: [url, "", url],
+    },
+};
+
+// Head.js is a shared file for all the test_ppb***.js.
+// Right now, window, process and MessageManager are base classes here.
+// Fill in the classes when you need the functions of them
+// and add more mocked classses if you need them in
+// ppapi-runtime.jsm for your tests.
+class Mock_Window {}
+class Mock_Process {}
+class Mock_MessageManager {
+  addMessageListener () {
+  }
+}
+
+// Here the new PPAPIRuntime, Call_PpbFunc and new PPAPIInstance are the
+// core part to invoke codes in ppapi-runtime.jsm.
+let rt = new PPAPIRuntime(new Mock_Process());
+
+function Call_PpbFunc(obj) {
+  if (!obj || !obj.__interface || !obj.__version || !obj.__method) {
+    ok(false, 'invalid JSON');
+  }
+  let fn = obj.__interface + "_" + obj.__method;
+  return rt.table[fn](obj);
+}
+
+// PPAPIInstance constructor(id, rt, info, window, eventHandler, containerWindow, mm)
+let instance = new PPAPIInstance(instanceId, rt, info, new Mock_Window(), null /*docShell.chromeEventHandler*/, null, new Mock_MessageManager());
+
+do_register_cleanup(function () {
+  resHandler.setSubstitution("ppapi.js", null);
+})
+
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/test/unit/test_ppbkeyboard.js
@@ -0,0 +1,49 @@
+"use strict";
+
+const PPB_TESTCASES = [
+  {"__interface":"PPB_KeyboardInputEvent","__version":"1.2","__method":"IsKeyboardInputEvent","resource":0},
+  {"__interface":"PPB_KeyboardInputEvent","__version":"1.2","__method":"GetKeyCode","key_event":0},
+  {"__interface":"PPB_KeyboardInputEvent","__version":"1.2","__method":"GetCharacterText","character_event":0}
+];
+
+
+class Mock_DomEvent {
+  constructor(eventType) {
+    this.type = eventType;
+    this.timeStamp = 0;
+  }
+}
+
+class Mock_KeyboardInputEvent extends Mock_DomEvent {
+  constructor(eventType, keyCode, charCode) {
+    super(eventType);
+    this.keyCode = keyCode;
+    this.charCode = charCode;
+  }
+}
+
+function run_test() {
+  // We mock a "keydown" event to test "PPB_KeyboardInputEvent".
+  let event = new Mock_KeyboardInputEvent("keydown", 65, 0);
+  // To test PPB_KeyboardInputEvent we need to invoke event resource constructor
+  // in ppapi-runtime.jsm to get a resource id.
+  let eventType = EventTypes.get(event.type);
+  let resource = new eventType.resourceCtor(instance, event);
+  let PP_ResourceID = resource.toJSON();
+
+  PPB_TESTCASES[0].resource = PP_ResourceID;
+  PPB_TESTCASES[1].key_event = PP_ResourceID;
+  Assert.equal(Call_PpbFunc(PPB_TESTCASES[0]), PP_Bool.PP_TRUE);
+  Assert.equal(Call_PpbFunc(PPB_TESTCASES[1]), 65); // 65 is the keyCode when you press 'A'.
+
+  // We mock a "keypress" event to test "PPB_KeyboardInputEvent".
+  event = new Mock_KeyboardInputEvent("keypress", 0, 65);
+  eventType = EventTypes.get(event.type);
+  resource = new eventType.resourceCtor(instance, event);
+  PP_ResourceID = resource.toJSON();
+
+  PPB_TESTCASES[0].resource = PP_ResourceID;
+  PPB_TESTCASES[2].character_event = PP_ResourceID;
+  Assert.equal(Call_PpbFunc(PPB_TESTCASES[0]), PP_Bool.PP_TRUE);
+  Assert.equal(Call_PpbFunc(PPB_TESTCASES[2]).type, PP_VarType.PP_VARTYPE_STRING);
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/mortar/test/unit/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head = head.js
+tail =
+support-files =
+  ../../host/common/opengles2-utils.jsm
+  ../../host/common/ppapi-runtime.jsm
+
+[test_ppbkeyboard.js]
--- a/devtools/client/performance/modules/marker-dom-utils.js
+++ b/devtools/client/performance/modules/marker-dom-utils.js
@@ -220,17 +220,17 @@ exports.MarkerDOMUtils = {
   buildCustom: function (doc, marker, options) {
     let elements = [];
 
     if (options.allocations && shouldShowAllocationsTrigger(marker)) {
       let hbox = doc.createElement("hbox");
       hbox.className = "marker-details-customcontainer";
 
       let label = doc.createElement("label");
-      label.className = "custom-button devtools-button";
+      label.className = "custom-button";
       label.setAttribute("value", "Show allocation triggers");
       label.setAttribute("type", "show-allocations");
       label.setAttribute("data-action", JSON.stringify({
         endTime: marker.start,
         action: "show-allocations"
       }));
 
       hbox.appendChild(label);
--- a/devtools/client/themes/performance.css
+++ b/devtools/client/themes/performance.css
@@ -690,16 +690,22 @@
 
 .marker-details-duration {
   font-weight: bold;
 }
 
 .marker-details-customcontainer .custom-button {
   padding: 2px 5px;
   border-width: 1px;
+  color: var(--theme-highlight-blue);
+  text-decoration: none;
+}
+
+.marker-details-customcontainer .custom-button:hover {
+  text-decoration: underline;
 }
 
 /**
  * Marker colors
  */
 
 menuitem.marker-color-graphs-full-red .menu-iconic-left::after,
 .marker-color-graphs-full-red {
--- a/docshell/test/test_bug385434.html
+++ b/docshell/test/test_bug385434.html
@@ -92,17 +92,17 @@ function eventExpected(msg) {
  *
  * We can't always just wait for page load in order to observe that a
  * hashchange didn't happen.  In these cases, we call longWait() and yield
  * until either a hashchange occurs or longWait's callback is scheduled.  This
  * is something of a hack; it's entirely possible that longWait won't wait long
  * enough, and we won't observe what should have been a failure of the test.
  * But it shouldn't happen that good code will randomly *fail* this test.
  */
-function run_test() {
+function* run_test() {
   /*
    * TEST 1 tests that:
    *    <body onhashchange = ... >  works,
    *    the event is (not) fired at the correct times
    */
   var frame = document.getElementById("frame");
   var frameCw = frame.contentWindow;
 
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -215,16 +215,27 @@ AudioChannelService::GetOrCreate()
     return nullptr;
   }
 
   CreateServiceIfNeeded();
   RefPtr<AudioChannelService> service = gAudioChannelService.get();
   return service.forget();
 }
 
+/* static */ already_AddRefed<AudioChannelService>
+AudioChannelService::Get()
+{
+  if (sXPCOMShuttingDown) {
+    return nullptr;
+  }
+
+  RefPtr<AudioChannelService> service = gAudioChannelService.get();
+  return service.forget();
+}
+
 /* static */ PRLogModuleInfo*
 AudioChannelService::GetAudioChannelLog()
 {
   static PRLogModuleInfo *gAudioChannelLog;
   if (!gAudioChannelLog) {
     gAudioChannelLog = PR_NewLogModule("AudioChannel");
   }
   return gAudioChannelLog;
@@ -928,16 +939,27 @@ AudioChannelService::IsAudioChannelActiv
                                           bool* aActive)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
   *aActive = IsAudioChannelActive(window, (AudioChannel)aAudioChannel);
   return NS_OK;
 }
+
+bool
+AudioChannelService::IsWindowActive(nsPIDOMWindowOuter* aWindow)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
+  AudioChannelWindow* winData = GetOrCreateWindowData(window);
+  return !winData->mAudibleAgents.IsEmpty();
+}
+
 void
 AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel,
                                                     bool aVisible)
 {
   SetDefaultVolumeControlChannelInternal(aChannel, aVisible,
                                          CONTENT_PROCESS_ID_MAIN);
 }
 
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -84,21 +84,27 @@ public:
   enum AudibleChangedReasons : uint32_t {
     eVolumeChanged = 0,
     eDataAudibleChanged = 1,
     ePauseStateChanged = 2
   };
 
   /**
    * Returns the AudioChannelServce singleton.
-   * If AudioChannelServce is not exist, create and return new one.
+   * If AudioChannelService doesn't exist, create and return new one.
    * Only to be called from main thread.
    */
   static already_AddRefed<AudioChannelService> GetOrCreate();
 
+  /**
+   * Returns the AudioChannelService singleton if one exists.
+   * If AudioChannelService doesn't exist, returns null.
+   */
+  static already_AddRefed<AudioChannelService> Get();
+
   static bool IsAudioChannelMutedByDefault();
 
   static PRLogModuleInfo* GetAudioChannelLog();
 
   static bool IsEnableAudioCompeting();
 
   static bool IsServiceStarted();
 
@@ -145,16 +151,18 @@ public:
 
   bool GetAudioChannelMuted(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel);
 
   void SetAudioChannelMuted(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel,
                             bool aMuted);
 
   bool IsAudioChannelActive(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel);
 
+  bool IsWindowActive(nsPIDOMWindowOuter* aWindow);
+
   /**
    * Return true if there is a telephony channel active in this process
    * or one of its subprocesses.
    */
   bool TelephonyChannelIsActive();
 
   /**
    * Return true if a normal or content channel is active for the given
--- a/dom/base/DOMParser.h
+++ b/dom/base/DOMParser.h
@@ -77,17 +77,20 @@ public:
   }
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
   {
     return mozilla::dom::DOMParserBinding::Wrap(aCx, this, aGivenProto);
   }
 
 private:
-  explicit DOMParser(nsISupports* aOwner) : mOwner(aOwner), mAttemptedInit(false)
+  explicit DOMParser(nsISupports* aOwner)
+    : mOwner(aOwner)
+    , mAttemptedInit(false)
+    , mOriginalPrincipalWasSystem(false)
   {
     MOZ_ASSERT(aOwner);
   }
 
   nsresult InitInternal(nsISupports* aOwner, nsIPrincipal* prin,
                         nsIURI* documentURI, nsIURI* baseURI);
 
   nsresult SetUpDocument(DocumentFlavor aFlavor, nsIDOMDocument** aResult);
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -29,19 +29,20 @@ static int32_t gMinTimeoutValue = 0;
 static int32_t gMinBackgroundTimeoutValue = 0;
 static int32_t gMinTrackingTimeoutValue = 0;
 static int32_t gMinTrackingBackgroundTimeoutValue = 0;
 static bool    gAnnotateTrackingChannels = false;
 int32_t
 TimeoutManager::DOMMinTimeoutValue(bool aIsTracking) const {
   // First apply any back pressure delay that might be in effect.
   int32_t value = std::max(mBackPressureDelayMS, 0);
-  // Don't use the background timeout value when there are audio contexts
-  // present, so that background audio can keep running smoothly. (bug 1181073)
-  bool isBackground = !mWindow.AsInner()->HasAudioContexts() &&
+  // Don't use the background timeout value when the tab is playing audio.
+  // Until bug 1336484 we only used to do this for pages that use Web Audio.
+  // The original behavior was implemented in bug 11811073.
+  bool isBackground = !mWindow.AsInner()->IsPlayingAudio() &&
     mWindow.IsBackgroundInternal();
   auto minValue = aIsTracking ? (isBackground ? gMinTrackingBackgroundTimeoutValue
                                               : gMinTrackingTimeoutValue)
                               : (isBackground ? gMinBackgroundTimeoutValue
                                               : gMinTimeoutValue);
   return std::max(minValue, value);
 }
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -4147,19 +4147,23 @@ nsPIDOMWindowInner::Thaw()
 
 void
 nsPIDOMWindowInner::SyncStateFromParentWindow()
 {
   nsGlobalWindow::Cast(this)->SyncStateFromParentWindow();
 }
 
 bool
-nsPIDOMWindowInner::HasAudioContexts() const
-{
-  return !mAudioContexts.IsEmpty();
+nsPIDOMWindowInner::IsPlayingAudio()
+{
+  RefPtr<AudioChannelService> acs = AudioChannelService::Get();
+  if (!acs) {
+    return false;
+  }
+  return acs->IsWindowActive(GetOuterWindow());
 }
 
 mozilla::dom::TimeoutManager&
 nsPIDOMWindowInner::TimeoutManager()
 {
   return *mTimeoutManager;
 }
 
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -873,17 +873,17 @@ public:
   // calls.
   void Freeze();
   void Thaw();
 
   // Apply the parent window's suspend, freeze, and modal state to the current
   // window.
   void SyncStateFromParentWindow();
 
-  bool HasAudioContexts() const;
+  bool IsPlayingAudio();
 
   mozilla::dom::TimeoutManager& TimeoutManager();
 
   bool IsRunningTimeout();
 
 protected:
   void CreatePerformanceObjectIfNeeded();
 };
--- a/dom/base/test/browser.ini
+++ b/dom/base/test/browser.ini
@@ -1,29 +1,36 @@
 [DEFAULT]
 support-files =
+  audio.ogg
   empty.html
+  file_audioLoop.html
+  file_audioLoopInIframe.html
   file_bug1011748_redirect.sjs
   file_bug1011748_OK.sjs
   file_messagemanager_unload.html
+  file_pluginAudio.html
   file_use_counter_outer.html
   file_use_counter_svg_getElementById.svg
   file_use_counter_svg_currentScale.svg
   file_use_counter_svg_fill_pattern_definition.svg
   file_use_counter_svg_fill_pattern.svg
   file_use_counter_svg_fill_pattern_internal.svg
   file_use_counter_svg_fill_pattern_data.svg
+  file_webaudioLoop.html
+  plugin.js
 
 [browser_bug593387.js]
 [browser_bug902350.js]
 tags = mcb
 [browser_bug1011748.js]
 [browser_bug1058164.js]
 [browser_messagemanager_loadprocessscript.js]
 skip-if = e10s # Bug 1315042
 [browser_messagemanager_targetframeloader.js]
 [browser_messagemanager_unload.js]
 [browser_pagehide_on_tab_close.js]
 skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s.
 [browser_state_notifications.js]
 skip-if = true # Bug 1271028
 [browser_use_counters.js]
 [browser_bug1307747.js]
+[browser_timeout_throttling_with_audio_playback.js]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/browser_timeout_throttling_with_audio_playback.js
@@ -0,0 +1,61 @@
+const kBaseURI = "http://mochi.test:8888/browser/dom/base/test/empty.html";
+const kPluginJS = "chrome://mochitests/content/browser/dom/base/test/plugin.js";
+var testURLs = [
+  "http://mochi.test:8888/browser/dom/base/test/file_audioLoop.html",
+  "http://mochi.test:8888/browser/dom/base/test/file_audioLoopInIframe.html",
+  "http://mochi.test:8888/browser/dom/base/test/file_pluginAudio.html",
+  "http://mochi.test:8888/browser/dom/base/test/file_webaudioLoop.html",
+];
+
+// We want to ensure that while audio is being played back, a background tab is
+// treated the same as a foreground tab as far as timeout throttling is concerned.
+// So we use a 10ms minimum timeout value for foreground tabs and a 100,000 second
+// minimum timeout value for background tabs.  This means that in case the test
+// fails, it will time out in practice, but just for sanity the test condition
+// ensures that the observed timeout delay falls in this range.
+const kMinTimeoutForeground = 10;
+const kMinTimeoutBackground = 100 * 1000 * 1000;
+
+Services.scriptloader.loadSubScript(kPluginJS, this);
+
+function* runTest(url) {
+  let currentTab = gBrowser.selectedTab;
+  let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, kBaseURI);
+  let newBrowser = gBrowser.getBrowserForTab(newTab);
+
+  // Wait for the UI to indicate that audio is being played back.
+  let promise = BrowserTestUtils.waitForAttribute("soundplaying", newTab, "true");
+  newBrowser.loadURI(url);
+  yield promise;
+
+  // Put the tab in the background.
+  yield BrowserTestUtils.switchTab(gBrowser, currentTab);
+
+  let timeout = yield ContentTask.spawn(newBrowser, {}, function() {
+    return new Promise(resolve => {
+      let before = new Date();
+      content.window.setTimeout(function() {
+        let after = new Date();
+        resolve(after - before);
+      }, 0);
+    });
+  });
+  ok(timeout >= kMinTimeoutForeground &&
+     timeout <= kMinTimeoutBackground, `Got the correct timeout (${timeout})`);
+
+  // All done.
+  yield BrowserTestUtils.removeTab(newTab);
+}
+
+add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({"set": [
+    ["dom.min_timeout_value", kMinTimeoutForeground],
+    ["dom.min_background_timeout_value", kMinTimeoutBackground],
+  ]});
+});
+
+add_task(function* test() {
+  for (var url of testURLs) {
+    yield runTest(url);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_audioLoopInIframe.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<iframe src="file_audioLoop.html"></iframe>
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -184,16 +184,17 @@ support-files =
   file_youtube_flash_embed.html
   forRemoval.resource
   forRemoval.resource^headers^
   formReset.html
   invalid_accesscontrol.resource
   invalid_accesscontrol.resource^headers^
   mutationobserver_dialog.html
   orientationcommon.js
+  plugin.js
   script-1_bug597345.sjs
   script-2_bug597345.js
   script_bug602838.sjs
   send_gzip_content.sjs
   somedatas.resource
   somedatas.resource^headers^
   variable_style_sheet.sjs
   viewport_helpers.js
new file mode 100644
--- /dev/null
+++ b/dom/base/test/plugin.js
@@ -0,0 +1,32 @@
+// Copied from /dom/plugins/test/mochitest/utils.js
+function getTestPlugin(pluginName) {
+  var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+                                 .getService(SpecialPowers.Ci.nsIPluginHost);
+  var tags = ph.getPluginTags();
+  var name = pluginName || "Test Plug-in";
+  for (var tag of tags) {
+    if (tag.name == name) {
+      return tag;
+    }
+  }
+
+  ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+  return null;
+}
+// Copied from /dom/plugins/test/mochitest/utils.js
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+  var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
+  if (!oldEnabledState) {
+    return;
+  }
+  var plugin = getTestPlugin(pluginName);
+  while (plugin.enabledState != newEnabledState) {
+    // Run a nested event loop to wait for the preference change to
+    // propagate to the child. Yuck!
+    SpecialPowers.Services.tm.currentThread.processNextEvent(true);
+  }
+  SimpleTest.registerCleanupFunction(function() {
+    SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
+  });
+}
+setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
--- a/dom/base/test/test_pluginAudioNotification.html
+++ b/dom/base/test/test_pluginAudioNotification.html
@@ -1,55 +1,23 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test for audio controller in windows</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="plugin.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <pre id="test">
 </pre>
 <iframe></iframe>
 
 <script type="application/javascript">
 
-// Copied from /dom/plugins/test/mochitest/utils.js
-function getTestPlugin(pluginName) {
-  var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
-                                 .getService(SpecialPowers.Ci.nsIPluginHost);
-  var tags = ph.getPluginTags();
-  var name = pluginName || "Test Plug-in";
-  for (var tag of tags) {
-    if (tag.name == name) {
-      return tag;
-    }
-  }
-
-  ok(false, "Could not find plugin tag with plugin name '" + name + "'");
-  return null;
-}
-// Copied from /dom/plugins/test/mochitest/utils.js
-function setTestPluginEnabledState(newEnabledState, pluginName) {
-  var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
-  if (!oldEnabledState) {
-    return;
-  }
-  var plugin = getTestPlugin(pluginName);
-  while (plugin.enabledState != newEnabledState) {
-    // Run a nested event loop to wait for the preference change to
-    // propagate to the child. Yuck!
-    SpecialPowers.Services.tm.currentThread.processNextEvent(true);
-  }
-  SimpleTest.registerCleanupFunction(function() {
-    SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
-  });
-}
-setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
-
 SimpleTest.waitForExplicitFinish();
 
 var expectedNotification = null;
 var iframe = null;
 
 var observer = {
   observe: function(subject, topic, data) {
     is(topic, "audio-playback", "audio-playback received");
--- a/dom/media/MP3Demuxer.cpp
+++ b/dom/media/MP3Demuxer.cpp
@@ -254,17 +254,19 @@ MP3TrackDemuxer::ScanUntil(const TimeUni
          aTime.ToMicroseconds(), AverageFrameLength(), mNumParsedFrames,
          mFrameIndex, mOffset);
 
   if (!aTime.ToMicroseconds()) {
     return FastSeek(aTime);
   }
 
   if (Duration(mFrameIndex) > aTime) {
-    FastSeek(aTime);
+    // We've seeked past the target time, rewind back a little to correct it.
+    const int64_t rewind = aTime.ToMicroseconds() / 100;
+    FastSeek(aTime - TimeUnit::FromMicroseconds(rewind));
   }
 
   if (Duration(mFrameIndex + 1) > aTime) {
     return SeekPosition();
   }
 
   MediaByteRange nextRange = FindNextFrame();
   while (SkipNextFrame(nextRange) && Duration(mFrameIndex + 1) < aTime) {
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -11,20 +11,16 @@ DIRS += ['blink']
 
 TEST_DIRS += ['gtest']
 
 MOCHITEST_MANIFESTS += [
     'test/blink/mochitest.ini',
     'test/mochitest.ini',
 ]
 
-BROWSER_CHROME_MANIFESTS += [
-    'test/browser.ini',
-]
-
 TEST_HARNESS_FILES.testing.mochitest.tests.dom.media.webaudio.test.blink += [
     'test/blink/audio-testing.js',
     'test/blink/convolution-testing.js',
     'test/blink/panner-model-testing.js',
 ]
 
 EXPORTS += [
     'AlignedTArray.h',
deleted file mode 100644
--- a/dom/media/webaudio/test/browser.ini
+++ /dev/null
@@ -1,1 +0,0 @@
-[browser_bug1181073.js]
\ No newline at end of file
deleted file mode 100644
--- a/dom/media/webaudio/test/browser_bug1181073.js
+++ /dev/null
@@ -1,40 +0,0 @@
-add_task(function*() {
-  // Make the min_background_timeout_value very high to avoid problems on slow machines
-  yield SpecialPowers.pushPrefEnv({
-    'set': [['dom.min_background_timeout_value', 3000]]
-  });
-
-  // Make a new tab, and put it in the background
-  yield BrowserTestUtils.withNewTab("about:blank", function*(browser) {
-    yield BrowserTestUtils.withNewTab("about:blank", function*() {
-      let time = yield ContentTask.spawn(browser, null, function () {
-        return new Promise(resolve => {
-          let start = content.performance.now();
-          let id = content.window.setInterval(function() {
-            let end = content.performance.now();
-            content.window.clearInterval(id);
-            resolve(end - start);
-          }, 0);
-        });
-      });
-
-      ok(time > 2000, "Interval is throttled with no webaudio (" + time + " ms)");
-
-      time = yield ContentTask.spawn(browser, null, function () {
-        return new Promise(resolve => {
-          // Create an audio context, and save it on the window so it doesn't get GCed
-          content.window._audioCtx = new content.window.AudioContext();
-
-          let start = content.performance.now();
-          let id = content.window.setInterval(function() {
-            let end = content.performance.now();
-            content.window.clearInterval(id);
-            resolve(end - start);
-          }, 0);
-        });
-      });
-
-      ok(time < 1000, "Interval is not throttled with an audio context present (" + time + " ms)");
-    });
-  });
-});
--- a/dom/security/test/hsts/head.js
+++ b/dom/security/test/hsts/head.js
@@ -421,21 +421,21 @@ function build_test_uri(base_uri, host, 
           "?host=" + escape(host) +
           "&id=" + escape(test_id) +
           "&type=" + escape(type) +
           "&timeout=" + escape(timeout)
     ;
 }
 
 // open a new tab, load the test, and wait for it to finish
-function execute_test(test, mimetype) {
+async function execute_test(test, mimetype) {
   var src = build_test_uri(TOP_URI, test_servers[test].host,
       test, test_settings[which_test].type,
       test_settings[which_test].timeout);
 
   let tab = openTab(src);
   test_servers[test]['tab'] = tab;
 
   let browser = gBrowser.getBrowserForTab(tab);
-  yield BrowserTestUtils.browserLoaded(browser);
+  await BrowserTestUtils.browserLoaded(browser);
 
-  yield BrowserTestUtils.removeTab(tab);
+  await BrowserTestUtils.removeTab(tab);
 }
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -1242,17 +1242,18 @@ private:
       nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup();
       MOZ_ASSERT(loadGroup);
 
       mozilla::DebugOnly<bool> equal = false;
       MOZ_ASSERT(responsePrincipal && NS_SUCCEEDED(responsePrincipal->Equals(principal, &equal)));
       MOZ_ASSERT(equal);
 
       mWorkerPrivate->InitChannelInfo(aChannelInfo);
-      mWorkerPrivate->SetPrincipalOnMainThread(responsePrincipal, loadGroup);
+      rv = mWorkerPrivate->SetPrincipalOnMainThread(responsePrincipal, loadGroup);
+      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     }
 
     if (NS_SUCCEEDED(rv)) {
       DataReceived();
     }
 
     LoadingFinished(aIndex, rv);
   }
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -1452,27 +1452,20 @@ public:
     mRequestCredentials = InternalRequest::MapChannelToRequestCredentials(channel);
 
     rv = httpChannel->VisitNonDefaultRequestHeaders(this);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
     if (uploadChannel) {
       MOZ_ASSERT(!mUploadStream);
-      bool bodyHasHeaders = false;
-      rv = uploadChannel->GetUploadStreamHasHeaders(&bodyHasHeaders);
-      NS_ENSURE_SUCCESS(rv, rv);
       nsCOMPtr<nsIInputStream> uploadStream;
       rv = uploadChannel->CloneUploadStream(getter_AddRefs(uploadStream));
       NS_ENSURE_SUCCESS(rv, rv);
-      if (bodyHasHeaders) {
-        HandleBodyWithHeaders(uploadStream);
-      } else {
-        mUploadStream = uploadStream;
-      }
+      mUploadStream = uploadStream;
     }
 
     return NS_OK;
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
@@ -1595,62 +1588,16 @@ private:
         runnable = new ResumeRequest(mInterceptedChannel);
       }
 
       MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
     }
 
     return true;
   }
-
-  nsresult
-  HandleBodyWithHeaders(nsIInputStream* aUploadStream)
-  {
-    // We are dealing with an nsMIMEInputStream which uses string input streams
-    // under the hood, so all of the data is available synchronously.
-    bool nonBlocking = false;
-    nsresult rv = aUploadStream->IsNonBlocking(&nonBlocking);
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (NS_WARN_IF(!nonBlocking)) {
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-    nsAutoCString body;
-    rv = NS_ConsumeStream(aUploadStream, UINT32_MAX, body);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // Extract the headers in the beginning of the buffer
-    nsAutoCString::const_iterator begin, end;
-    body.BeginReading(begin);
-    body.EndReading(end);
-    const nsAutoCString::const_iterator body_end = end;
-    nsAutoCString headerName, headerValue;
-    bool emptyHeader = false;
-    while (FetchUtil::ExtractHeader(begin, end, headerName,
-                                    headerValue, &emptyHeader) &&
-           !emptyHeader) {
-      mHeaderNames.AppendElement(headerName);
-      mHeaderValues.AppendElement(headerValue);
-      headerName.Truncate();
-      headerValue.Truncate();
-    }
-
-    // Replace the upload stream with one only containing the body text.
-    nsCOMPtr<nsIStringInputStream> strStream =
-      do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-    // Skip past the "\r\n" that separates the headers and the body.
-    ++begin;
-    ++begin;
-    body.Assign(Substring(begin, body_end));
-    rv = strStream->SetData(body.BeginReading(), body.Length());
-    NS_ENSURE_SUCCESS(rv, rv);
-    mUploadStream = strStream;
-
-    return NS_OK;
-  }
 };
 
 NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)
 
 } // anonymous namespace
 
 nsresult
 ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel,
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1830,53 +1830,56 @@ WorkerLoadInfo::StealFrom(WorkerLoadInfo
   mReportCSPViolations = aOther.mReportCSPViolations;
   mXHRParamsAllowed = aOther.mXHRParamsAllowed;
   mPrincipalIsSystem = aOther.mPrincipalIsSystem;
   mStorageAllowed = aOther.mStorageAllowed;
   mServiceWorkersTestingInWindow = aOther.mServiceWorkersTestingInWindow;
   mOriginAttributes = aOther.mOriginAttributes;
 }
 
-void
+nsresult
 WorkerLoadInfo::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
                                          nsILoadGroup* aLoadGroup)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadGroup, aPrincipal));
   MOZ_ASSERT(!mPrincipalInfo);
 
   mPrincipal = aPrincipal;
   mPrincipalIsSystem = nsContentUtils::IsSystemPrincipal(aPrincipal);
 
-  aPrincipal->GetCsp(getter_AddRefs(mCSP));
+  nsresult rv = aPrincipal->GetCsp(getter_AddRefs(mCSP));
+  NS_ENSURE_SUCCESS(rv, rv);
 
   if (mCSP) {
     mCSP->GetAllowsEval(&mReportCSPViolations, &mEvalAllowed);
     // Set ReferrerPolicy
     bool hasReferrerPolicy = false;
     uint32_t rp = mozilla::net::RP_Unset;
 
-    nsresult rv = mCSP->GetReferrerPolicy(&rp, &hasReferrerPolicy);
-    NS_ENSURE_SUCCESS_VOID(rv);
+    rv = mCSP->GetReferrerPolicy(&rp, &hasReferrerPolicy);
+    NS_ENSURE_SUCCESS(rv, rv);
 
     if (hasReferrerPolicy) {
       mReferrerPolicy = static_cast<net::ReferrerPolicy>(rp);
     }
   } else {
     mEvalAllowed = true;
     mReportCSPViolations = false;
   }
 
   mLoadGroup = aLoadGroup;
 
   mPrincipalInfo = new PrincipalInfo();
   mOriginAttributes = nsContentUtils::GetOriginAttributes(aLoadGroup);
 
-  MOZ_ALWAYS_SUCCEEDS(
-    PrincipalToPrincipalInfo(aPrincipal, mPrincipalInfo));
+  rv = PrincipalToPrincipalInfo(aPrincipal, mPrincipalInfo);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
 }
 
 nsresult
 WorkerLoadInfo::GetPrincipalAndLoadGroupFromChannel(nsIChannel* aChannel,
                                                     nsIPrincipal** aPrincipalOut,
                                                     nsILoadGroup** aLoadGroupOut)
 {
   AssertIsOnMainThread();
@@ -1948,18 +1951,17 @@ WorkerLoadInfo::SetPrincipalFromChannel(
 
   nsCOMPtr<nsIPrincipal> principal;
   nsCOMPtr<nsILoadGroup> loadGroup;
   nsresult rv = GetPrincipalAndLoadGroupFromChannel(aChannel,
                                                     getter_AddRefs(principal),
                                                     getter_AddRefs(loadGroup));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  SetPrincipalOnMainThread(principal, loadGroup);
-  return NS_OK;
+  return SetPrincipalOnMainThread(principal, loadGroup);
 }
 
 #if defined(DEBUG) || !defined(RELEASE_OR_BETA)
 bool
 WorkerLoadInfo::FinalChannelPrincipalIsValid(nsIChannel* aChannel)
 {
   AssertIsOnMainThread();
 
@@ -3798,21 +3800,21 @@ WorkerPrivateParent<Derived>::SetBaseURI
   else {
     mLocationInfo.mHost.Assign(mLocationInfo.mHostname);
   }
 
   nsContentUtils::GetUTFOrigin(aBaseURI, mLocationInfo.mOrigin);
 }
 
 template <class Derived>
-void
+nsresult
 WorkerPrivateParent<Derived>::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
                                                        nsILoadGroup* aLoadGroup)
 {
-  mLoadInfo.SetPrincipalOnMainThread(aPrincipal, aLoadGroup);
+  return mLoadInfo.SetPrincipalOnMainThread(aPrincipal, aLoadGroup);
 }
 
 template <class Derived>
 nsresult
 WorkerPrivateParent<Derived>::SetPrincipalFromChannel(nsIChannel* aChannel)
 {
   return mLoadInfo.SetPrincipalFromChannel(aChannel);
 }
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -602,17 +602,17 @@ public:
   // Principals are main-thread objects so the caller must ensure that all
   // access occurs on the main thread.
   nsIPrincipal*
   GetPrincipalDontAssertMainThread() const
   {
       return mLoadInfo.mPrincipal;
   }
 
-  void
+  nsresult
   SetPrincipalOnMainThread(nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup);
 
   nsresult
   SetPrincipalFromChannel(nsIChannel* aChannel);
 
 #if defined(DEBUG) || !defined(RELEASE_OR_BETA)
   bool
   FinalChannelPrincipalIsValid(nsIChannel* aChannel);
--- a/dom/workers/Workers.h
+++ b/dom/workers/Workers.h
@@ -273,17 +273,17 @@ struct WorkerLoadInfo
   bool mServiceWorkersTestingInWindow;
   OriginAttributes mOriginAttributes;
 
   WorkerLoadInfo();
   ~WorkerLoadInfo();
 
   void StealFrom(WorkerLoadInfo& aOther);
 
-  void
+  nsresult
   SetPrincipalOnMainThread(nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup);
 
   nsresult
   GetPrincipalAndLoadGroupFromChannel(nsIChannel* aChannel,
                                       nsIPrincipal** aPrincipalOut,
                                       nsILoadGroup** aLoadGroupOut);
 
   nsresult
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -2293,16 +2293,17 @@ XMLHttpRequestMainThread::OnStopRequest(
   NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
 
   channel->SetNotificationCallbacks(nullptr);
   mNotificationCallbacks = nullptr;
   mChannelEventSink = nullptr;
   mProgressEventSink = nullptr;
 
   mFlagSyncLooping = false;
+  mRequestSentTime = 0;
 
   // update our charset and decoder to match mResponseXML,
   // before it is possibly nulled out
   MatchCharsetAndDecoderToResponseDocument();
 
   if (NS_FAILED(status)) {
     // This can happen if the server is unreachable. Other possible
     // reasons are that the user leaves the page or hits the ESC key.
--- a/extensions/spellcheck/hunspell/tests/unit/test_hunspell.js
+++ b/extensions/spellcheck/hunspell/tests/unit/test_hunspell.js
@@ -119,17 +119,17 @@ const tests = [
     ["opentaal-cpdpat", "iso-8859-1"],
     ["opentaal-cpdpat2", "iso-8859-1"],
     ["2999225", "iso-8859-1"],
     ["onlyincompound2", "iso-8859-1"],
     ["forceucase", "iso-8859-1"],
     ["warn", "iso-8859-1"]
 ];
 
-function do_get_file_by_line(file, charset) {
+function* do_get_file_by_line(file, charset) {
   dump("getting file by line for file " + file.path + "\n");
   dump("using charset " + charset +"\n");
   let fis = Cc["@mozilla.org/network/file-input-stream;1"].
               createInstance(Ci.nsIFileInputStream);
   fis.init(file, 0x1 /* READONLY */,
            0o444, Ci.nsIFileInputStream.CLOSE_ON_EOF);
 
   let lis = Cc["@mozilla.org/intl/converter-input-stream;1"].
@@ -158,17 +158,17 @@ function do_run_test(checker, name, char
   dump("Need some expected output\n")
   do_check_true(good.exists() || bad.exists() || sug.exists());
 
   dump("Setting dictionary to " + name + "\n");
   checker.dictionary = name;
 
   if (good.exists()) {
     var good_counter = 0;
-    for (val in do_get_file_by_line(good, charset)) {
+    for (val of do_get_file_by_line(good, charset)) {
       let todo = false;
       good_counter++;
       if (todo_good && todo_good[good_counter]) {
         todo = true;
         dump("TODO\n");
       }
 
       dump("Expect word " + val + " is spelled correctly\n");
@@ -177,17 +177,17 @@ function do_run_test(checker, name, char
       } else {
         do_check_true(checker.check(val));
       }
     }
   }
 
   if (bad.exists()) {
     var bad_counter = 0;
-    for (val in do_get_file_by_line(bad, charset)) {
+    for (val of do_get_file_by_line(bad, charset)) {
       let todo = false;
       bad_counter++;
       if (todo_bad && todo_bad[bad_counter]) {
         todo = true;
         dump("TODO\n");
       }
 
       dump("Expect word " + val + " is spelled wrong\n");
--- a/gfx/2d/FilterNodeSoftware.cpp
+++ b/gfx/2d/FilterNodeSoftware.cpp
@@ -3325,17 +3325,17 @@ FilterNodeLightingSoftware<LightType, Li
 {
   if (mLight.SetAttribute(aIndex, aValue) ||
       mLighting.SetAttribute(aIndex, aValue)) {
     Invalidate();
     return;
   }
   switch (aIndex) {
     case ATT_LIGHTING_SURFACE_SCALE:
-      mSurfaceScale = aValue;
+      mSurfaceScale = std::fpclassify(aValue) == FP_SUBNORMAL ? 0.0 : aValue;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute float");
   }
   Invalidate();
 }
 
 template<typename LightType, typename LightingType>
--- a/ipc/glue/WindowsMessageLoop.cpp
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -392,17 +392,17 @@ ProcessOrDeferMessage(HWND hwnd,
       return 0;
 
     // We only support a query for our IAccessible or UIA pointers.
     // This should be safe, and needs to be sync.
 #if defined(ACCESSIBILITY)
    case WM_GETOBJECT: {
       if (!::GetPropW(hwnd, k3rdPartyWindowProp)) {
         DWORD objId = static_cast<DWORD>(lParam);
-        if ((objId == OBJID_CLIENT || objId == MOZOBJID_UIAROOT)) {
+        if (objId == OBJID_CLIENT || objId == MOZOBJID_UIAROOT) {
           WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
           if (oldWndProc) {
             return CallWindowProcW(oldWndProc, hwnd, uMsg, wParam, lParam);
           }
         }
       }
       return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
@@ -1016,17 +1016,17 @@ MessageChannel::WaitForSyncNotifyWithA11
 bool
 MessageChannel::WaitForSyncNotify(bool aHandleWindowsMessages)
 {
   mMonitor->AssertCurrentThreadOwns();
 
   MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!");
 
 #if defined(ACCESSIBILITY)
-  if ((mFlags & REQUIRE_A11Y_REENTRY)) {
+  if (mFlags & REQUIRE_A11Y_REENTRY) {
     MOZ_ASSERT(!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION));
     return WaitForSyncNotifyWithA11yReentry();
   }
 #endif
 
   // Use a blocking wait if this channel does not require
   // Windows message deferral behavior.
   if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION) || !aHandleWindowsMessages) {
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -624,18 +624,16 @@ namespace js {
 namespace gc {
 
 extern JS_FRIEND_API(bool)
 BarriersAreAllowedOnCurrentThread();
 
 static MOZ_ALWAYS_INLINE void
 ExposeGCThingToActiveJS(JS::GCCellPtr thing)
 {
-    MOZ_ASSERT(thing.kind() != JS::TraceKind::Shape);
-
     // GC things residing in the nursery cannot be gray: they have no mark bits.
     // All live objects in the nursery are moved to tenured at the beginning of
     // each GC slice, so the gray marker never sees nursery things.
     if (IsInsideNursery(thing.asCell()))
         return;
 
     // There's nothing to do for permanent GC things that might be owned by
     // another runtime.
--- a/js/src/gc/GCInternals.h
+++ b/js/src/gc/GCInternals.h
@@ -42,17 +42,16 @@ class MOZ_RAII AutoTraceSession
     JSRuntime* runtime;
 
   private:
     AutoTraceSession(const AutoTraceSession&) = delete;
     void operator=(const AutoTraceSession&) = delete;
 
     JS::HeapState prevState;
     AutoGeckoProfilerEntry pseudoFrame;
-    JSRuntime::AutoProhibitActiveContextChange prohibitActiveContextChange;
 };
 
 class MOZ_RAII AutoPrepareForTracing
 {
     mozilla::Maybe<AutoTraceSession> session_;
 
   public:
     AutoPrepareForTracing(JSContext* cx, ZoneSelector selector);
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -626,16 +626,18 @@ class GCRuntime
     void gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason);
     void startGC(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0);
     void gcSlice(JS::gcreason::Reason reason, int64_t millis = 0);
     void finishGC(JS::gcreason::Reason reason);
     void abortGC();
     void startDebugGC(JSGCInvocationKind gckind, SliceBudget& budget);
     void debugGCSlice(SliceBudget& budget);
 
+    bool canChangeActiveContext(JSContext* cx);
+
     void triggerFullGCForAtoms() {
         MOZ_ASSERT(fullGCForAtomsRequested_);
         fullGCForAtomsRequested_ = false;
         MOZ_RELEASE_ASSERT(triggerGC(JS::gcreason::ALLOC_TRIGGER));
     }
 
     void runDebugGC();
     inline void poke();
@@ -686,18 +688,16 @@ class GCRuntime
     bool isForegroundSweeping() const { return state() == State::Sweep; }
     bool isBackgroundSweeping() { return helperState.isBackgroundSweeping(); }
     void waitBackgroundSweepEnd() { helperState.waitBackgroundSweepEnd(); }
     void waitBackgroundSweepOrAllocEnd() {
         helperState.waitBackgroundSweepEnd();
         allocTask.cancel(GCParallelTask::CancelAndWait);
     }
 
-    void requestMinorGC(JS::gcreason::Reason reason);
-
 #ifdef DEBUG
     bool onBackgroundThread() { return helperState.onBackgroundThread(); }
 #endif // DEBUG
 
     void lockGC() {
         lock.lock();
     }
 
@@ -772,19 +772,17 @@ class GCRuntime
 
     bool isIncrementalGc() const { return isIncremental; }
     bool isFullGc() const { return isFull; }
     bool isCompactingGc() const { return isCompacting; }
 
     bool areGrayBitsValid() const { return grayBitsValid; }
     void setGrayBitsInvalid() { grayBitsValid = false; }
 
-    bool minorGCRequested() const { return minorGCTriggerReason != JS::gcreason::NO_REASON; }
     bool majorGCRequested() const { return majorGCTriggerReason != JS::gcreason::NO_REASON; }
-    bool isGcNeeded() { return minorGCRequested() || majorGCRequested(); }
 
     bool fullGCForAtomsRequested() const { return fullGCForAtomsRequested_; }
 
     double computeHeapGrowthFactor(size_t lastBytes);
     size_t computeTriggerBytes(double growthFactor, size_t lastBytes);
 
     JSGCMode gcMode() const { return mode; }
     void setGCMode(JSGCMode m) {
@@ -935,17 +933,18 @@ class GCRuntime
     MOZ_MUST_USE bool findZoneEdgesForWeakMaps();
     void getNextZoneGroup();
     void endMarkingZoneGroup();
     void beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock);
     bool shouldReleaseObservedTypes();
     void endSweepingZoneGroup();
     IncrementalProgress sweepPhase(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
     void endSweepPhase(bool lastGC, AutoLockForExclusiveAccess& lock);
-    void sweepZones(FreeOp* fop, bool lastGC);
+    void sweepZones(FreeOp* fop, ZoneGroup* group, bool lastGC);
+    void sweepZoneGroups(FreeOp* fop, bool destroyingRuntime);
     void decommitAllWithoutUnlocking(const AutoLockGC& lock);
     void startDecommit();
     void queueZonesForBackgroundSweep(ZoneList& zones);
     void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks);
     void assertBackgroundSweepingFinished();
     bool shouldCompact();
     void beginCompactPhase();
     IncrementalProgress compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
@@ -1079,19 +1078,16 @@ class GCRuntime
     /*
      * The gray bits can become invalid if UnmarkGray overflows the stack. A
      * full GC will reset this bit, since it fills in all the gray bits.
      */
     UnprotectedData<bool> grayBitsValid;
 
     mozilla::Atomic<JS::gcreason::Reason, mozilla::Relaxed> majorGCTriggerReason;
 
-  public:
-    ActiveThreadData<JS::gcreason::Reason> minorGCTriggerReason;
-
   private:
     /* Perform full GC if rt->keepAtoms() becomes false. */
     ActiveThreadData<bool> fullGCForAtomsRequested_;
 
     /* Incremented at the start of every minor GC. */
     ActiveThreadData<uint64_t> minorGCNumber;
 
     /* Incremented at the start of every major GC. */
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2716,17 +2716,17 @@ template <>
 /* static */ bool
 IsMarkedInternal(JSRuntime* rt, JSObject** thingp)
 {
     if (IsOwnedByOtherRuntime(rt, *thingp))
         return true;
 
     if (IsInsideNursery(*thingp)) {
         MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
-        return rt->zoneGroupFromMainThread()->nursery().getForwardedPointer(thingp);
+        return Nursery::getForwardedPointer(thingp);
     }
     return IsMarkedInternalCommon(thingp);
 }
 
 template <typename S>
 struct IsMarkedFunctor : public IdentityDefaultAdaptor<S> {
     template <typename T> S operator()(T* t, JSRuntime* rt, bool* rv) {
         *rv = IsMarkedInternal(rt, &t);
--- a/js/src/gc/Nursery-inl.h
+++ b/js/src/gc/Nursery-inl.h
@@ -75,11 +75,18 @@ ReallocateObjectBuffer(JSContext* cx, JS
                                                                     newCount * sizeof(T)));
         if (!buffer)
             ReportOutOfMemory(cx);
         return buffer;
     }
     return obj->zone()->pod_realloc<T>(oldBuffer, oldCount, newCount);
 }
 
+static inline void
+EvictAllNurseries(JSRuntime* rt, JS::gcreason::Reason reason = JS::gcreason::EVICT_NURSERY)
+{
+    for (ZoneGroupsIter group(rt); !group.done(); group.next())
+        group->evictNursery(reason);
+}
+
 } // namespace js
 
 #endif /* gc_Nursery_inl_h */
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -79,26 +79,26 @@ struct js::Nursery::SweepAction
 struct js::Nursery::Canary
 {
     uintptr_t magicValue;
     Canary* next;
 };
 #endif
 
 inline void
-js::Nursery::NurseryChunk::poisonAndInit(JSRuntime* rt, uint8_t poison)
+js::Nursery::NurseryChunk::poisonAndInit(ZoneGroup* group, uint8_t poison)
 {
     JS_POISON(this, poison, ChunkSize);
-    init(rt);
+    init(group);
 }
 
 inline void
-js::Nursery::NurseryChunk::init(JSRuntime* rt)
+js::Nursery::NurseryChunk::init(ZoneGroup* group)
 {
-    new (&trailer) gc::ChunkTrailer(rt, &rt->zoneGroupFromMainThread()->storeBuffer());
+    new (&trailer) gc::ChunkTrailer(group->runtime, &group->storeBuffer());
 }
 
 /* static */ inline js::Nursery::NurseryChunk*
 js::Nursery::NurseryChunk::fromChunk(Chunk* chunk)
 {
     return reinterpret_cast<NurseryChunk*>(chunk);
 }
 
@@ -117,16 +117,17 @@ js::Nursery::Nursery(ZoneGroup* group)
   , currentStartPosition_(0)
   , currentEnd_(0)
   , currentChunk_(0)
   , maxNurseryChunks_(0)
   , previousPromotionRate_(0)
   , profileThreshold_(0)
   , enableProfiling_(false)
   , reportTenurings_(0)
+  , minorGCTriggerReason_(JS::gcreason::NO_REASON)
   , minorGcCount_(0)
   , freeMallocedBuffersTask(nullptr)
   , sweepActions_(nullptr)
 #ifdef JS_GC_ZEAL
   , lastCanary_(nullptr)
 #endif
 {}
 
@@ -479,22 +480,19 @@ js::TenuringTracer::TenuringTracer(JSRun
   : JSTracer(rt, JSTracer::TracerKindTag::Tenuring, TraceWeakMapKeysValues)
   , nursery_(*nursery)
   , tenuredSize(0)
   , head(nullptr)
   , tail(&head)
 {
 }
 
-void
+/* static */ void
 js::Nursery::printProfileHeader()
 {
-    if (!enableProfiling_)
-        return;
-
     fprintf(stderr, "MinorGC:               Reason  PRate Size ");
 #define PRINT_HEADER(name, text)                                              \
     fprintf(stderr, " %6s", text);
 FOR_EACH_NURSERY_PROFILE_TIME(PRINT_HEADER)
 #undef PRINT_HEADER
     fprintf(stderr, "\n");
 }
 
@@ -510,16 +508,25 @@ void
 js::Nursery::printTotalProfileTimes()
 {
     if (enableProfiling_) {
         fprintf(stderr, "MinorGC TOTALS: %7" PRIu64 " collections:      ", minorGcCount_);
         printProfileDurations(totalDurations_);
     }
 }
 
+void
+js::Nursery::maybeClearProfileDurations()
+{
+    if (enableProfiling_) {
+        for (auto& duration : profileDurations_)
+            duration = mozilla::TimeDuration();
+    }
+}
+
 inline void
 js::Nursery::startProfile(ProfileKey key)
 {
     startTimes_[key] = TimeStamp::Now();
 }
 
 inline void
 js::Nursery::endProfile(ProfileKey key)
@@ -568,16 +575,17 @@ js::Nursery::collect(JS::gcreason::Reaso
             MOZ_ASSERT(canary->magicValue == CanaryMagicValue);
     }
     lastCanary_ = nullptr;
 #endif
 
     rt->gc.stats().beginNurseryCollection(reason);
     TraceMinorGCStart();
 
+    maybeClearProfileDurations();
     startProfile(ProfileKey::Total);
 
     // The hazard analysis thinks doCollection can invalidate pointers in
     // tenureCounts below.
     JS::AutoSuppressGCAnalysis nogc;
 
     TenureCountCache tenureCounts;
     double promotionRate = 0;
@@ -637,17 +645,17 @@ js::Nursery::collect(JS::gcreason::Reaso
                 JS::gcreason::ExplainReason(reason),
                 promotionRate * 100,
                 numChunks());
         printProfileDurations(profileDurations_);
 
         if (reportTenurings_) {
             for (auto& entry : tenureCounts.entries) {
                 if (entry.count >= reportTenurings_) {
-                    fprintf(stderr, "%d x ", entry.count);
+                    fprintf(stderr, "  %d x ", entry.count);
                     entry.group->print();
                 }
             }
         }
     }
 }
 
 double
@@ -698,17 +706,17 @@ js::Nursery::doCollection(JS::gcreason::
 
     maybeStartProfile(ProfileKey::MarkRuntime);
     rt->gc.traceRuntimeForMinorGC(&mover, session.lock);
     maybeEndProfile(ProfileKey::MarkRuntime);
 
     maybeStartProfile(ProfileKey::MarkDebugger);
     {
         gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_MARK_ROOTS);
-        Debugger::traceAll(&mover);
+        Debugger::traceAllForMovingGC(&mover);
     }
     maybeEndProfile(ProfileKey::MarkDebugger);
 
     maybeStartProfile(ProfileKey::ClearNewObjectCache);
     zoneGroup()->caches().newObjectCache.clearNurseryObjects(zoneGroup());
     maybeEndProfile(ProfileKey::ClearNewObjectCache);
 
     // Most of the work is done here. This loop iterates over objects that have
@@ -824,28 +832,28 @@ js::Nursery::sweep()
     cellsWithUid_.clear();
 
     runSweepActions();
     sweepDictionaryModeObjects();
 
 #ifdef JS_GC_ZEAL
     /* Poison the nursery contents so touching a freed object will crash. */
     for (unsigned i = 0; i < numChunks(); i++)
-        chunk(i).poisonAndInit(zoneGroup()->runtime, JS_SWEPT_NURSERY_PATTERN);
+        chunk(i).poisonAndInit(zoneGroup(), JS_SWEPT_NURSERY_PATTERN);
 
     if (zoneGroup()->runtime->hasZealMode(ZealMode::GenerationalGC)) {
         /* Only reset the alloc point when we are close to the end. */
         if (currentChunk_ + 1 == numChunks())
             setCurrentChunk(0);
     } else
 #endif
     {
 #ifdef JS_CRASH_DIAGNOSTICS
         for (unsigned i = 0; i < numChunks(); ++i)
-            chunk(i).poisonAndInit(zoneGroup()->runtime, JS_SWEPT_NURSERY_PATTERN);
+            chunk(i).poisonAndInit(zoneGroup(), JS_SWEPT_NURSERY_PATTERN);
 #endif
         setCurrentChunk(0);
     }
 
     /* Set current start position for isEmpty checks. */
     setStartPosition();
     MemProfiler::SweepNursery(zoneGroup()->runtime);
 }
@@ -869,17 +877,17 @@ js::Nursery::spaceToEnd() const
 MOZ_ALWAYS_INLINE void
 js::Nursery::setCurrentChunk(unsigned chunkno)
 {
     MOZ_ASSERT(chunkno < maxChunks());
     MOZ_ASSERT(chunkno < numChunks());
     currentChunk_ = chunkno;
     position_ = chunk(chunkno).start();
     currentEnd_ = chunk(chunkno).end();
-    chunk(chunkno).poisonAndInit(zoneGroup()->runtime, JS_FRESH_NURSERY_PATTERN);
+    chunk(chunkno).poisonAndInit(zoneGroup(), JS_FRESH_NURSERY_PATTERN);
 }
 
 MOZ_ALWAYS_INLINE void
 js::Nursery::setStartPosition()
 {
     currentStartChunk_ = currentChunk_;
     currentStartPosition_ = position();
 }
@@ -969,17 +977,17 @@ js::Nursery::updateNumChunksLocked(unsig
     for (unsigned i = priorCount; i < newCount; i++) {
         auto newChunk = zoneGroup()->runtime->gc.getOrAllocChunk(lock, maybeBgAlloc);
         if (!newChunk) {
             chunks_.shrinkTo(i);
             return;
         }
 
         chunks_[i] = NurseryChunk::fromChunk(newChunk);
-        chunk(i).poisonAndInit(zoneGroup()->runtime, JS_FRESH_NURSERY_PATTERN);
+        chunk(i).poisonAndInit(zoneGroup(), JS_FRESH_NURSERY_PATTERN);
     }
 }
 
 void
 js::Nursery::queueSweepAction(SweepThunk thunk, void* data)
 {
     static_assert(sizeof(SweepAction) % CellSize == 0,
                   "SweepAction size must be a multiple of cell size");
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -255,34 +255,42 @@ class Nursery
     }
 
 #ifdef JS_GC_ZEAL
     void enterZealMode();
     void leaveZealMode();
 #endif
 
     /* Print header line for profile times. */
-    void printProfileHeader();
+    static void printProfileHeader();
 
     /* Print total profile times on shutdown. */
     void printTotalProfileTimes();
 
     void* addressOfCurrentEnd() const { return (void*)&currentEnd_; }
     void* addressOfPosition() const { return (void*)&position_; }
 
+    void requestMinorGC(JS::gcreason::Reason reason) const;
+
+    bool minorGCRequested() const { return minorGCTriggerReason_ != JS::gcreason::NO_REASON; }
+    JS::gcreason::Reason minorGCTriggerReason() const { return minorGCTriggerReason_; }
+    void clearMinorGCRequest() { minorGCTriggerReason_ = JS::gcreason::NO_REASON; }
+
+    bool enableProfiling() const { return enableProfiling_; }
+
   private:
     /* The amount of space in the mapped nursery available to allocations. */
     static const size_t NurseryChunkUsableSize = gc::ChunkSize - sizeof(gc::ChunkTrailer);
 
     struct NurseryChunk {
         char data[NurseryChunkUsableSize];
         gc::ChunkTrailer trailer;
         static NurseryChunk* fromChunk(gc::Chunk* chunk);
-        void init(JSRuntime* rt);
-        void poisonAndInit(JSRuntime* rt, uint8_t poison);
+        void init(ZoneGroup* group);
+        void poisonAndInit(ZoneGroup* group, uint8_t poison);
         uintptr_t start() const { return uintptr_t(&data); }
         uintptr_t end() const { return uintptr_t(&trailer); }
         gc::Chunk* toChunk(JSRuntime* rt);
     };
     static_assert(sizeof(NurseryChunk) == gc::ChunkSize,
                   "Nursery chunk size must match gc::Chunk size.");
 
     // The set of zones which this is the nursery for.
@@ -312,16 +320,23 @@ class Nursery
 
     /* Report minor collections taking at least this long, if enabled. */
     mozilla::TimeDuration profileThreshold_;
     bool enableProfiling_;
 
     /* Report ObjectGroups with at lest this many instances tenured. */
     int64_t reportTenurings_;
 
+    /*
+     * Whether and why a collection of this nursery has been requested. This is
+     * mutable as it is set by the store buffer, which otherwise cannot modify
+     * anything in the nursery.
+     */
+    mutable JS::gcreason::Reason minorGCTriggerReason_;
+
     /* Profiling data. */
 
     enum class ProfileKey
     {
 #define DEFINE_TIME_KEY(name, text)                                           \
         name,
         FOR_EACH_NURSERY_PROFILE_TIME(DEFINE_TIME_KEY)
 #undef DEFINE_TIME_KEY
@@ -450,16 +465,17 @@ class Nursery
 
     /* Change the allocable space provided by the nursery. */
     void maybeResizeNursery(JS::gcreason::Reason reason, double promotionRate);
     void growAllocableSpace();
     void shrinkAllocableSpace();
     void minimizeAllocableSpace();
 
     /* Profile recording and printing. */
+    void maybeClearProfileDurations();
     void startProfile(ProfileKey key);
     void endProfile(ProfileKey key);
     void maybeStartProfile(ProfileKey key);
     void maybeEndProfile(ProfileKey key);
     static void printProfileDurations(const ProfileDurations& times);
 
     friend class TenuringTracer;
     friend class gc::MinorCollectionTracer;
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -81,19 +81,19 @@ JS_FOR_EACH_TRACEKIND(TRACE_ROOTS)
 
 void
 JS::RootingContext::traceStackRoots(JSTracer* trc)
 {
     TraceStackRoots(trc, stackRoots_);
 }
 
 static void
-TraceExactStackRoots(JSRuntime* rt, JSTracer* trc)
+TraceExactStackRoots(const CooperatingContext& target, JSTracer* trc)
 {
-    TlsContext.get()->traceStackRoots(trc);
+    target.context()->traceStackRoots(trc);
 }
 
 template <typename T, TraceFunction<T> TraceFn = TraceNullableRoot>
 static inline void
 TracePersistentRootedList(JSTracer* trc, mozilla::LinkedList<PersistentRooted<void*>>& list,
                          const char* name)
 {
     for (PersistentRooted<void*>* r : list)
@@ -196,28 +196,26 @@ AutoGCRooter::trace(JSTracer* trc)
     }
 
     MOZ_ASSERT(tag_ >= 0);
     if (Value* vp = static_cast<AutoArrayRooter*>(this)->array)
         TraceRootRange(trc, tag_, vp, "JS::AutoArrayRooter.array");
 }
 
 /* static */ void
-AutoGCRooter::traceAll(JSTracer* trc)
+AutoGCRooter::traceAll(const CooperatingContext& target, JSTracer* trc)
 {
-    for (AutoGCRooter* gcr = TlsContext.get()->autoGCRooters_; gcr; gcr = gcr->down)
+    for (AutoGCRooter* gcr = target.context()->autoGCRooters_; gcr; gcr = gcr->down)
         gcr->trace(trc);
 }
 
 /* static */ void
-AutoGCRooter::traceAllWrappers(JSTracer* trc)
+AutoGCRooter::traceAllWrappers(const CooperatingContext& target, JSTracer* trc)
 {
-    JSContext* cx = TlsContext.get();
-
-    for (AutoGCRooter* gcr = cx->autoGCRooters_; gcr; gcr = gcr->down) {
+    for (AutoGCRooter* gcr = target.context()->autoGCRooters_; gcr; gcr = gcr->down) {
         if (gcr->tag_ == WRAPVECTOR || gcr->tag_ == WRAPPER)
             gcr->trace(trc);
     }
 }
 
 void
 StackShape::trace(JSTracer* trc)
 {
@@ -285,17 +283,18 @@ js::gc::GCRuntime::traceRuntimeForMinorG
 }
 
 void
 js::TraceRuntime(JSTracer* trc)
 {
     MOZ_ASSERT(!trc->isMarkingTracer());
 
     JSRuntime* rt = trc->runtime();
-    rt->zoneGroupFromMainThread()->evictNursery();
+    for (ZoneGroupsIter group(rt); !group.done(); group.next())
+        group->evictNursery();
     AutoPrepareForTracing prep(TlsContext.get(), WithAtoms);
     gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_TRACE_HEAP);
     rt->gc.traceRuntime(trc, prep.session().lock);
 }
 
 void
 js::gc::GCRuntime::traceRuntime(JSTracer* trc, AutoLockForExclusiveAccess& lock)
 {
@@ -325,42 +324,42 @@ js::gc::GCRuntime::traceRuntimeCommon(JS
     {
         gcstats::AutoPhase ap(stats(), gcstats::PHASE_MARK_STACK);
 
         JSContext* cx = TlsContext.get();
         for (const CooperatingContext& target : rt->cooperatingContexts()) {
             // Trace active interpreter and JIT stack roots.
             TraceInterpreterActivations(cx, target, trc);
             jit::TraceJitActivations(cx, target, trc);
-        }
+
+            // Trace legacy C stack roots.
+            AutoGCRooter::traceAll(target, trc);
 
-        // Trace legacy C stack roots.
-        AutoGCRooter::traceAll(trc);
+            // Trace C stack roots.
+            TraceExactStackRoots(target, trc);
+        }
 
         for (RootRange r = rootsHash.ref().all(); !r.empty(); r.popFront()) {
             const RootEntry& entry = r.front();
             TraceRoot(trc, entry.key(), entry.value());
         }
-
-        // Trace C stack roots.
-        TraceExactStackRoots(rt, trc);
     }
 
     // Trace runtime global roots.
     TracePersistentRooted(rt, trc);
 
     // Trace the self-hosting global compartment.
     rt->traceSelfHostingGlobal(trc);
 
     // Trace the shared Intl data.
     rt->traceSharedIntlData(trc);
 
-    // Trace anything in the current thread's context. Ignore other JSContexts,
-    // as these will only refer to ZoneGroups which we are not collecting/tracing.
-    TlsContext.get()->trace(trc);
+    // Trace anything in any of the cooperating threads.
+    for (const CooperatingContext& target : rt->cooperatingContexts())
+        target.context()->trace(trc);
 
     // Trace all compartment roots, but not the compartment itself; it is
     // traced via the parent pointer if traceRoots actually traces anything.
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
         c->traceRoots(trc, traceOrMark);
 
     // Trace the Gecko Profiler.
     rt->geckoProfiler().trace(trc);
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -1400,17 +1400,22 @@ Statistics::computeMMU(TimeDuration wind
 }
 
 void
 Statistics::maybePrintProfileHeaders()
 {
     static int printedHeader = 0;
     if ((printedHeader++ % 200) == 0) {
         printProfileHeader();
-        runtime->zoneGroupFromMainThread()->nursery().printProfileHeader();
+        for (ZoneGroupsIter group(runtime); !group.done(); group.next()) {
+            if (group->nursery().enableProfiling()) {
+                Nursery::printProfileHeader();
+                break;
+            }
+        }
     }
 }
 
 void
 Statistics::printProfileHeader()
 {
     if (!enableProfiling_)
         return;
--- a/js/src/gc/StoreBuffer.cpp
+++ b/js/src/gc/StoreBuffer.cpp
@@ -86,17 +86,17 @@ StoreBuffer::clear()
 
 void
 StoreBuffer::setAboutToOverflow()
 {
     if (!aboutToOverflow_) {
         aboutToOverflow_ = true;
         runtime_->gc.stats().count(gcstats::STAT_STOREBUFFER_OVERFLOW);
     }
-    runtime_->gc.requestMinorGC(JS::gcreason::FULL_STORE_BUFFER);
+    nursery_.requestMinorGC(JS::gcreason::FULL_STORE_BUFFER);
 }
 
 void
 StoreBuffer::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::GCSizes
 *sizes)
 {
     sizes->storeBufferVals       += bufferVal.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferCells      += bufferCell.sizeOfExcludingThis(mallocSizeOf);
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -322,17 +322,17 @@ gc::GCRuntime::endVerifyPreBarriers()
 {
     VerifyPreTracer* trc = verifyPreData;
 
     if (!trc)
         return;
 
     MOZ_ASSERT(!JS::IsGenerationalGCEnabled(rt));
 
-    AutoPrepareForTracing prep(rt->contextFromMainThread(), SkipAtoms);
+    AutoPrepareForTracing prep(rt->activeContextFromOwnThread(), SkipAtoms);
 
     bool compartmentCreated = false;
 
     /* We need to disable barriers before tracing, which may invoke barriers. */
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         if (!zone->needsIncrementalBarrier())
             compartmentCreated = true;
 
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -332,17 +332,17 @@ Zone::canCollect()
     return true;
 }
 
 void
 Zone::notifyObservingDebuggers()
 {
     for (CompartmentsInZoneIter comps(this); !comps.done(); comps.next()) {
         JSRuntime* rt = runtimeFromAnyThread();
-        RootedGlobalObject global(rt->contextFromMainThread(), comps->unsafeUnbarrieredMaybeGlobal());
+        RootedGlobalObject global(TlsContext.get(), comps->unsafeUnbarrieredMaybeGlobal());
         if (!global)
             continue;
 
         GlobalObject::DebuggerVector* dbgs = global->getDebuggers();
         if (!dbgs)
             continue;
 
         for (GlobalObject::DebuggerVector::Range r = dbgs->all(); !r.empty(); r.popFront()) {
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -877,21 +877,21 @@ class ZoneAllocPolicy
  * not be safe to do so at the current time.  This policy puts the object on a
  * queue to be destroyed at a safe time.
  */
 template <typename T>
 struct GCManagedDeletePolicy
 {
     void operator()(const T* ptr) {
         if (ptr) {
-            JSRuntime* rt = TlsContext.get()->runtime();
-            if (CurrentThreadCanAccessRuntime(rt) && rt->zoneGroupFromMainThread()->nursery().isEnabled()) {
+            Zone* zone = ptr->zone();
+            if (zone && zone->group()->nursery().isEnabled()) {
                 // The object may contain nursery pointers and must only be
                 // destroyed after a minor GC.
-                rt->zoneGroupFromMainThread()->callAfterMinorGC(deletePtr, const_cast<T*>(ptr));
+                zone->group()->callAfterMinorGC(deletePtr, const_cast<T*>(ptr));
             } else {
                 // The object cannot contain nursery pointers so can be
                 // destroyed immediately.
                 gc::AutoSetThreadIsSweeping threadIsSweeping;
                 js_delete(const_cast<T*>(ptr));
             }
         }
     }
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -743,18 +743,17 @@ BaselineCacheIRCompiler::emitStoreSlotSh
         masm.storeValue(val, slot);
     } else {
         masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch2.ref());
         BaseIndex slot(scratch2.ref(), scratch1, TimesOne);
         EmitPreBarrier(masm, slot, MIRType::Value);
         masm.storeValue(val, slot);
     }
 
-    if (cx_->nursery().exists())
-        BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch1, LiveGeneralRegisterSet(), cx_);
+    BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch1, LiveGeneralRegisterSet(), cx_);
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitStoreFixedSlot()
 {
     return emitStoreSlotShared(true);
 }
@@ -857,18 +856,17 @@ BaselineCacheIRCompiler::emitAddAndStore
     } else {
         MOZ_ASSERT(op == CacheOp::AddAndStoreDynamicSlot ||
                    op == CacheOp::AllocateAndStoreDynamicSlot);
         masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch2);
         BaseIndex slot(scratch2, scratch1, TimesOne);
         masm.storeValue(val, slot);
     }
 
-    if (cx_->nursery().exists())
-        BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch1, LiveGeneralRegisterSet(), cx_);
+    BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch1, LiveGeneralRegisterSet(), cx_);
     return true;
 }
 
 bool
 BaselineCacheIRCompiler::emitAddAndStoreFixedSlot()
 {
     return emitAddAndStoreSlotShared(CacheOp::AddAndStoreFixedSlot);
 }
@@ -1006,16 +1004,147 @@ BaselineCacheIRCompiler::emitStoreTypedO
     Address dest(scratch1, 0);
 
     BaselineStoreToTypedArray(cx_, masm, type, val, dest, scratch2,
                               failure->label(), failure->label());
 
     return true;
 }
 
+bool
+BaselineCacheIRCompiler::emitStoreDenseElement()
+{
+    ObjOperandId objId = reader.objOperandId();
+    Int32OperandId indexId = reader.int32OperandId();
+
+    // Allocate the fixed registers first. These need to be fixed for
+    // callTypeUpdateIC.
+    AutoScratchRegister scratch(allocator, masm, R1.scratchReg());
+    ValueOperand val = allocator.useFixedValueRegister(masm, reader.valOperandId(), R0);
+
+    Register obj = allocator.useRegister(masm, objId);
+    Register index = allocator.useRegister(masm, indexId);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    // Load obj->elements in scratch.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+
+    // Bounds check.
+    Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
+    masm.branch32(Assembler::BelowOrEqual, initLength, index, failure->label());
+
+    // Hole check.
+    BaseObjectElementIndex element(scratch, index);
+    masm.branchTestMagic(Assembler::Equal, element, failure->label());
+
+    // Perform a single test to see if we either need to convert double
+    // elements, clone the copy on write elements in the object or fail
+    // due to a frozen element.
+    Label noSpecialHandling;
+    Address elementsFlags(scratch, ObjectElements::offsetOfFlags());
+    masm.branchTest32(Assembler::Zero, elementsFlags,
+                      Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS |
+                            ObjectElements::COPY_ON_WRITE |
+                            ObjectElements::FROZEN),
+                      &noSpecialHandling);
+
+    // Fail if we need to clone copy on write elements or to throw due
+    // to a frozen element.
+    masm.branchTest32(Assembler::NonZero, elementsFlags,
+                      Imm32(ObjectElements::COPY_ON_WRITE |
+                            ObjectElements::FROZEN),
+                      failure->label());
+
+    // We need to convert int32 values being stored into doubles. Note that
+    // double arrays are only created by IonMonkey, so if we have no FP support
+    // Ion is disabled and there should be no double arrays.
+    if (cx_->runtime()->jitSupportsFloatingPoint) {
+        // It's fine to convert the value in place in Baseline. We can't do
+        // this in Ion.
+        masm.convertInt32ValueToDouble(val);
+    } else {
+        masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
+    }
+
+    masm.bind(&noSpecialHandling);
+
+    // Call the type update IC. After this everything must be infallible as we
+    // don't save all registers here.
+    LiveGeneralRegisterSet saveRegs;
+    saveRegs.add(obj);
+    saveRegs.add(index);
+    saveRegs.add(val);
+    if (!callTypeUpdateIC(obj, val, scratch, saveRegs))
+        return false;
+
+    // Perform the store. Reload obj->elements because callTypeUpdateIC
+    // used the scratch register.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+    EmitPreBarrier(masm, element, MIRType::Value);
+    masm.storeValue(val, element);
+
+    BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch, LiveGeneralRegisterSet(), cx_);
+    return true;
+}
+
+bool
+BaselineCacheIRCompiler::emitStoreUnboxedArrayElement()
+{
+    ObjOperandId objId = reader.objOperandId();
+    Int32OperandId indexId = reader.int32OperandId();
+
+    // Allocate the fixed registers first. These need to be fixed for
+    // callTypeUpdateIC.
+    AutoScratchRegister scratch(allocator, masm, R1.scratchReg());
+    ValueOperand val = allocator.useFixedValueRegister(masm, reader.valOperandId(), R0);
+
+    JSValueType elementType = reader.valueType();
+    Register obj = allocator.useRegister(masm, objId);
+    Register index = allocator.useRegister(masm, indexId);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    // Bounds check.
+    Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+    masm.load32(initLength, scratch);
+    masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratch);
+    masm.branch32(Assembler::BelowOrEqual, scratch, index, failure->label());
+
+    // Call the type update IC. After this everything must be infallible as we
+    // don't save all registers here.
+    if (elementType == JSVAL_TYPE_OBJECT) {
+        LiveGeneralRegisterSet saveRegs;
+        saveRegs.add(obj);
+        saveRegs.add(index);
+        saveRegs.add(val);
+        if (!callTypeUpdateIC(obj, val, scratch, saveRegs))
+            return false;
+    }
+
+    // Load obj->elements.
+    masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratch);
+
+    // Note that the storeUnboxedProperty call here is infallible, as the
+    // IR emitter is responsible for guarding on |val|'s type.
+    BaseIndex element(scratch, index, ScaleFromElemWidth(UnboxedTypeSize(elementType)));
+    EmitUnboxedPreBarrierForBaseline(masm, element, elementType);
+    masm.storeUnboxedProperty(element, elementType,
+                              ConstantOrRegister(TypedOrValueRegister(val)),
+                              /* failure = */ nullptr);
+
+    if (UnboxedTypeNeedsPostBarrier(elementType))
+        BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch, LiveGeneralRegisterSet(), cx_);
+    return true;
+}
+
 typedef bool (*CallNativeSetterFn)(JSContext*, HandleFunction, HandleObject, HandleValue);
 static const VMFunction CallNativeSetterInfo =
     FunctionInfo<CallNativeSetterFn>(CallNativeSetter, "CallNativeSetter");
 
 bool
 BaselineCacheIRCompiler::emitCallNativeSetter()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -320,17 +320,16 @@ DoTypeUpdateFallback(JSContext* cx, Base
                     break;
             }
         }
 
         JSObject* maybeSingleton = obj->isSingleton() ? obj.get() : nullptr;
         AddTypePropertyId(cx, group, maybeSingleton, id, value);
         break;
       }
-      case ICStub::SetElem_DenseOrUnboxedArray:
       case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
         id = JSID_VOID;
         AddTypePropertyId(cx, obj, id, value);
         break;
       }
       default:
         MOZ_CRASH("Invalid stub");
     }
@@ -948,30 +947,20 @@ SetElemAddHasSameShapes(ICSetElem_DenseO
 
     return true;
 }
 
 static bool
 DenseOrUnboxedArraySetElemStubExists(JSContext* cx, ICStub::Kind kind,
                                      ICSetElem_Fallback* stub, HandleObject obj)
 {
-    MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArray ||
-               kind == ICStub::SetElem_DenseOrUnboxedArrayAdd);
+    MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArrayAdd);
 
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
-        if (kind == ICStub::SetElem_DenseOrUnboxedArray && iter->isSetElem_DenseOrUnboxedArray()) {
-            ICSetElem_DenseOrUnboxedArray* nstub = iter->toSetElem_DenseOrUnboxedArray();
-            if (obj->maybeShape() == nstub->shape() &&
-                JSObject::getGroup(cx, obj) == nstub->group())
-            {
-                return true;
-            }
-        }
-
-        if (kind == ICStub::SetElem_DenseOrUnboxedArrayAdd && iter->isSetElem_DenseOrUnboxedArrayAdd()) {
+        if (iter->isSetElem_DenseOrUnboxedArrayAdd()) {
             ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
             if (JSObject::getGroup(cx, obj) == nstub->group() &&
                 SetElemAddHasSameShapes(nstub, obj))
             {
                 return true;
             }
         }
     }
@@ -1099,16 +1088,27 @@ DoSetElemFallback(JSContext* cx, Baselin
                op == JSOP_INITHIDDENELEM ||
                op == JSOP_INITELEM_ARRAY ||
                op == JSOP_INITELEM_INC);
 
     RootedObject obj(cx, ToObjectFromStack(cx, objv));
     if (!obj)
         return false;
 
+    RootedShape oldShape(cx, obj->maybeShape());
+    RootedObjectGroup oldGroup(cx, JSObject::getGroup(cx, obj));
+    if (!oldGroup)
+        return false;
+
+    if (obj->is<UnboxedPlainObject>()) {
+        MOZ_ASSERT(!oldShape);
+        if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
+            oldShape = expando->lastProperty();
+    }
+
     bool isTemporarilyUnoptimizable = false;
 
     bool attached = false;
     if (stub->numOptimizedStubs() < ICSetElem_Fallback::MAX_OPTIMIZED_STUBS &&
         !JitOptions.disableCacheIR)
     {
         SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, &isTemporarilyUnoptimizable,
                                objv, index, rhs);
@@ -1127,27 +1127,16 @@ DoSetElemFallback(JSContext* cx, Baselin
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Updated()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
             }
         }
     }
 
-    RootedShape oldShape(cx, obj->maybeShape());
-    RootedObjectGroup oldGroup(cx, JSObject::getGroup(cx, obj));
-    if (!oldGroup)
-        return false;
-
-    if (obj->is<UnboxedPlainObject>()) {
-        MOZ_ASSERT(!oldShape);
-        if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
-            oldShape = expando->lastProperty();
-    }
-
     // Check the old capacity
     uint32_t oldCapacity = 0;
     uint32_t oldInitLength = 0;
     if (index.isInt32() && index.toInt32() >= 0) {
         oldCapacity = GetAnyBoxedOrUnboxedCapacity(obj);
         oldInitLength = GetAnyBoxedOrUnboxedInitializedLength(obj);
     }
 
@@ -1238,35 +1227,16 @@ DoSetElemFallback(JSContext* cx, Baselin
                     return false;
                 if (compiler.needsUpdateStubs() &&
                     !newStub->addUpdateStubForValue(cx, outerScript, obj, JSID_VOIDHANDLE, rhs))
                 {
                     return false;
                 }
 
                 stub->addNewStub(newStub);
-            } else if (!addingCase &&
-                       !DenseOrUnboxedArraySetElemStubExists(cx,
-                                                             ICStub::SetElem_DenseOrUnboxedArray,
-                                                             stub, obj))
-            {
-                JitSpew(JitSpew_BaselineIC,
-                        "  Generating SetElem_DenseOrUnboxedArray stub (shape=%p, group=%p)",
-                        shape.get(), group.get());
-                ICSetElem_DenseOrUnboxedArray::Compiler compiler(cx, shape, group);
-                ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(outerScript));
-                if (!newStub)
-                    return false;
-                if (compiler.needsUpdateStubs() &&
-                    !newStub->addUpdateStubForValue(cx, outerScript, obj, JSID_VOIDHANDLE, rhs))
-                {
-                    return false;
-                }
-
-                stub->addNewStub(newStub);
             }
         }
 
         return true;
     }
 
     if ((obj->is<TypedArrayObject>() || IsPrimitiveArrayTypedObject(obj)) &&
         index.isNumber() &&
@@ -1383,173 +1353,16 @@ EmitUnboxedPreBarrierForBaseline(MacroAs
     if (type == JSVAL_TYPE_OBJECT)
         EmitPreBarrier(masm, address, MIRType::Object);
     else if (type == JSVAL_TYPE_STRING)
         EmitPreBarrier(masm, address, MIRType::String);
     else
         MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type));
 }
 
-bool
-ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    // R0 = object
-    // R1 = key
-    // Stack = { ... rhs-value, <return-addr>? }
-    Label failure, failurePopR0;
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-    masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
-
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
-    Register scratchReg = regs.takeAny();
-
-    // Unbox R0 and guard on its group and, if this is a native access, its shape.
-    Register obj = masm.extractObject(R0, ExtractTemp0);
-    masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArray::offsetOfGroup()),
-                 scratchReg);
-    masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure);
-    if (unboxedType_ == JSVAL_TYPE_MAGIC) {
-        masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArray::offsetOfShape()),
-                     scratchReg);
-        masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
-    }
-
-    if (needsUpdateStubs()) {
-        // Stow both R0 and R1 (object and key)
-        // But R0 and R1 still hold their values.
-        EmitStowICValues(masm, 2);
-
-        // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR }
-        // Load rhs-value into R0
-        masm.loadValue(Address(masm.getStackPointer(), 2 * sizeof(Value) + ICStackValueOffset), R0);
-
-        // Call the type-update stub.
-        if (!callTypeUpdateIC(masm, sizeof(Value)))
-            return false;
-
-        // Unstow R0 and R1 (object and key)
-        EmitUnstowICValues(masm, 2);
-
-        // Restore object.
-        obj = masm.extractObject(R0, ExtractTemp0);
-
-        // Trigger post barriers here on the value being written. Fields which
-        // objects can be written to also need update stubs.
-        masm.Push(R1);
-        masm.loadValue(Address(masm.getStackPointer(), sizeof(Value) + ICStackValueOffset), R1);
-
-        LiveGeneralRegisterSet saveRegs;
-        saveRegs.add(R0);
-        saveRegs.addUnchecked(obj);
-        saveRegs.add(ICStubReg);
-        BaselineEmitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs, cx);
-
-        masm.Pop(R1);
-    }
-
-    // Unbox key.
-    Register key = masm.extractInt32(R1, ExtractTemp1);
-
-    if (unboxedType_ == JSVAL_TYPE_MAGIC) {
-        // Set element on a native object.
-
-        // Load obj->elements in scratchReg.
-        masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg);
-
-        // Bounds check.
-        Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
-        masm.branch32(Assembler::BelowOrEqual, initLength, key, &failure);
-
-        // Hole check.
-        BaseIndex element(scratchReg, key, TimesEight);
-        masm.branchTestMagic(Assembler::Equal, element, &failure);
-
-        // Perform a single test to see if we either need to convert double
-        // elements, clone the copy on write elements in the object or fail
-        // due to a frozen element.
-        Label noSpecialHandling;
-        Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
-        masm.branchTest32(Assembler::Zero, elementsFlags,
-                          Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS |
-                                ObjectElements::COPY_ON_WRITE |
-                                ObjectElements::FROZEN),
-                          &noSpecialHandling);
-
-        // Fail if we need to clone copy on write elements or to throw due
-        // to a frozen element.
-        masm.branchTest32(Assembler::NonZero, elementsFlags,
-                          Imm32(ObjectElements::COPY_ON_WRITE |
-                                ObjectElements::FROZEN),
-                          &failure);
-
-        // Failure is not possible now.  Free up registers.
-        regs.add(R0);
-        regs.add(R1);
-        regs.takeUnchecked(obj);
-        regs.takeUnchecked(key);
-
-        Address valueAddr(masm.getStackPointer(), ICStackValueOffset);
-
-        // We need to convert int32 values being stored into doubles. In this case
-        // the heap typeset is guaranteed to contain both int32 and double, so it's
-        // okay to store a double. Note that double arrays are only created by
-        // IonMonkey, so if we have no floating-point support Ion is disabled and
-        // there should be no double arrays.
-        if (cx->runtime()->jitSupportsFloatingPoint)
-            masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &noSpecialHandling);
-        else
-            masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
-
-        masm.bind(&noSpecialHandling);
-
-        ValueOperand tmpVal = regs.takeAnyValue();
-        masm.loadValue(valueAddr, tmpVal);
-        EmitPreBarrier(masm, element, MIRType::Value);
-        masm.storeValue(tmpVal, element);
-    } else {
-        // Set element on an unboxed array.
-
-        // Bounds check.
-        Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
-        masm.load32(initLength, scratchReg);
-        masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
-        masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
-
-        // Load obj->elements.
-        masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
-
-        // Compute the address being written to.
-        BaseIndex address(scratchReg, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_)));
-
-        EmitUnboxedPreBarrierForBaseline(masm, address, unboxedType_);
-
-        Address valueAddr(masm.getStackPointer(), ICStackValueOffset + sizeof(Value));
-        masm.Push(R0);
-        masm.loadValue(valueAddr, R0);
-        masm.storeUnboxedProperty(address, unboxedType_,
-                                  ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0);
-        masm.Pop(R0);
-    }
-
-    EmitReturnFromIC(masm);
-
-    if (failurePopR0.used()) {
-        // Failure case: restore the value of R0
-        masm.bind(&failurePopR0);
-        masm.popValue(R0);
-    }
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
 //
 // SetElem_DenseOrUnboxedArrayAdd
 //
 
 ICUpdatedStub*
 ICSetElemDenseOrUnboxedArrayAddCompiler::getStub(ICStubSpace* space)
 {
     Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
@@ -5274,22 +5087,16 @@ ICTypeUpdate_SingleObject::ICTypeUpdate_
     obj_(obj)
 { }
 
 ICTypeUpdate_ObjectGroup::ICTypeUpdate_ObjectGroup(JitCode* stubCode, ObjectGroup* group)
   : ICStub(TypeUpdate_ObjectGroup, stubCode),
     group_(group)
 { }
 
-ICSetElem_DenseOrUnboxedArray::ICSetElem_DenseOrUnboxedArray(JitCode* stubCode, Shape* shape, ObjectGroup* group)
-  : ICUpdatedStub(SetElem_DenseOrUnboxedArray, stubCode),
-    shape_(shape),
-    group_(group)
-{ }
-
 ICSetElem_DenseOrUnboxedArrayAdd::ICSetElem_DenseOrUnboxedArrayAdd(JitCode* stubCode, ObjectGroup* group,
                                                                    size_t protoChainDepth)
   : ICUpdatedStub(SetElem_DenseOrUnboxedArrayAdd, stubCode),
     group_(group)
 {
     MOZ_ASSERT(protoChainDepth <= MAX_PROTO_CHAIN_DEPTH);
     extra_ = protoChainDepth;
 }
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -452,77 +452,16 @@ class ICSetElem_Fallback : public ICFall
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             return newStub<ICSetElem_Fallback>(space, getStubCode());
         }
     };
 };
 
-class ICSetElem_DenseOrUnboxedArray : public ICUpdatedStub
-{
-    friend class ICStubSpace;
-
-    GCPtrShape shape_; // null for unboxed arrays
-    GCPtrObjectGroup group_;
-
-    ICSetElem_DenseOrUnboxedArray(JitCode* stubCode, Shape* shape, ObjectGroup* group);
-
-  public:
-    static size_t offsetOfShape() {
-        return offsetof(ICSetElem_DenseOrUnboxedArray, shape_);
-    }
-    static size_t offsetOfGroup() {
-        return offsetof(ICSetElem_DenseOrUnboxedArray, group_);
-    }
-
-    GCPtrShape& shape() {
-        return shape_;
-    }
-    GCPtrObjectGroup& group() {
-        return group_;
-    }
-
-    class Compiler : public ICStubCompiler {
-        RootedShape shape_;
-        RootedObjectGroup group_;
-        JSValueType unboxedType_;
-
-        MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        virtual int32_t getKey() const {
-            return static_cast<int32_t>(engine_) |
-                  (static_cast<int32_t>(kind) << 1) |
-                  (static_cast<int32_t>(unboxedType_) << 17);
-        }
-
-        Compiler(JSContext* cx, Shape* shape, HandleObjectGroup group)
-          : ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArray, Engine::Baseline),
-            shape_(cx, shape),
-            group_(cx, group),
-            unboxedType_(shape
-                         ? JSVAL_TYPE_MAGIC
-                         : group->unboxedLayoutDontCheckGeneration().elementType())
-        {}
-
-        ICUpdatedStub* getStub(ICStubSpace* space) {
-            ICSetElem_DenseOrUnboxedArray* stub =
-                newStub<ICSetElem_DenseOrUnboxedArray>(space, getStubCode(), shape_, group_);
-            if (!stub || !stub->initUpdatingChain(cx, space))
-                return nullptr;
-            return stub;
-        }
-
-        bool needsUpdateStubs() {
-            return unboxedType_ == JSVAL_TYPE_MAGIC || unboxedType_ == JSVAL_TYPE_OBJECT;
-        }
-    };
-};
-
 template <size_t ProtoChainDepth> class ICSetElem_DenseOrUnboxedArrayAddImpl;
 
 class ICSetElem_DenseOrUnboxedArrayAdd : public ICUpdatedStub
 {
     friend class ICStubSpace;
 
   public:
     static const size_t MAX_PROTO_CHAIN_DEPTH = 4;
--- a/js/src/jit/BaselineICList.h
+++ b/js/src/jit/BaselineICList.h
@@ -46,17 +46,16 @@ namespace jit {
     _(Call_ScriptedApplyArguments)               \
     _(Call_ScriptedFunCall)                      \
     _(Call_StringSplit)                          \
     _(Call_IsSuspendedStarGenerator)             \
                                                  \
     _(GetElem_Fallback)                          \
                                                  \
     _(SetElem_Fallback)                          \
-    _(SetElem_DenseOrUnboxedArray)               \
     _(SetElem_DenseOrUnboxedArrayAdd)            \
     _(SetElem_TypedArray)                        \
                                                  \
     _(In_Fallback)                               \
                                                  \
     _(GetName_Fallback)                          \
                                                  \
     _(BindName_Fallback)                         \
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -53,30 +53,16 @@ SetElemICInspector::sawOOBTypedArrayWrit
             continue;
         if (stub->toSetElem_TypedArray()->expectOutOfBounds())
             return true;
     }
     return false;
 }
 
 bool
-SetElemICInspector::sawDenseWrite() const
-{
-    if (!icEntry_)
-        return false;
-
-    // Check for a SetElem_DenseAdd or SetElem_Dense stub.
-    for (ICStub* stub = icEntry_->firstStub(); stub; stub = stub->next()) {
-        if (stub->isSetElem_DenseOrUnboxedArrayAdd() || stub->isSetElem_DenseOrUnboxedArray())
-            return true;
-    }
-    return false;
-}
-
-bool
 SetElemICInspector::sawTypedArrayWrite() const
 {
     if (!icEntry_)
         return false;
 
     // Check for a SetElem_TypedArray stub.
     for (ICStub* stub = icEntry_->firstStub(); stub; stub = stub->next()) {
         if (stub->isSetElem_TypedArray())
--- a/js/src/jit/BaselineInspector.h
+++ b/js/src/jit/BaselineInspector.h
@@ -32,17 +32,16 @@ class SetElemICInspector : public ICInsp
 {
   public:
     SetElemICInspector(BaselineInspector* inspector, jsbytecode* pc, ICEntry* icEntry)
       : ICInspector(inspector, pc, icEntry)
     { }
 
     bool sawOOBDenseWrite() const;
     bool sawOOBTypedArrayWrite() const;
-    bool sawDenseWrite() const;
     bool sawTypedArrayWrite() const;
 };
 
 class BaselineInspector
 {
   private:
     JSScript* script;
     BaselineICEntry* prevLookedUpEntry;
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -1901,16 +1901,27 @@ SetPropIRGenerator::tryAttachStub()
             if (tryAttachUnboxedProperty(obj, objId, id, rhsValId))
                 return true;
             if (tryAttachSetter(obj, objId, id, rhsValId))
                 return true;
             if (tryAttachTypedObjectProperty(obj, objId, id, rhsValId))
                 return true;
             if (tryAttachSetArrayLength(obj, objId, id, rhsValId))
                 return true;
+            return false;
+        }
+
+        uint32_t index;
+        Int32OperandId indexId;
+        if (maybeGuardInt32Index(idVal_, setElemKeyValueId(), &index, &indexId)) {
+            if (tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId))
+                return true;
+            if (tryAttachSetUnboxedArrayElement(obj, objId, index, indexId, rhsValId))
+                return true;
+            return false;
         }
         return false;
     }
     return false;
 }
 
 static void
 EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId, NativeObject* nobj, Shape* shape,
@@ -2221,16 +2232,68 @@ SetPropIRGenerator::tryAttachSetArrayLen
     writer.callSetArrayLength(objId, IsStrictSetPC(pc_), rhsId);
     writer.returnFromIC();
 
     trackAttached("SetArrayLength");
     return true;
 }
 
 bool
+SetPropIRGenerator::tryAttachSetDenseElement(HandleObject obj, ObjOperandId objId, uint32_t index,
+                                             Int32OperandId indexId, ValOperandId rhsId)
+{
+    if (!obj->isNative())
+        return false;
+
+    NativeObject* nobj = &obj->as<NativeObject>();
+    if (!nobj->containsDenseElement(index) || nobj->getElementsHeader()->isFrozen())
+        return false;
+
+    writer.guardGroup(objId, nobj->group());
+    writer.guardShape(objId, nobj->shape());
+
+    writer.storeDenseElement(objId, indexId, rhsId);
+    writer.returnFromIC();
+
+    // Type inference uses JSID_VOID for the element types.
+    setUpdateStubInfo(nobj->group(), JSID_VOID);
+
+    trackAttached("SetDenseElement");
+    return true;
+}
+
+bool
+SetPropIRGenerator::tryAttachSetUnboxedArrayElement(HandleObject obj, ObjOperandId objId, uint32_t index,
+                                                    Int32OperandId indexId, ValOperandId rhsId)
+{
+    if (!obj->is<UnboxedArrayObject>())
+        return false;
+
+    if (!cx_->runtime()->jitSupportsFloatingPoint)
+        return false;
+
+    if (index >= obj->as<UnboxedArrayObject>().initializedLength())
+        return false;
+
+    writer.guardGroup(objId, obj->group());
+
+    JSValueType elementType = obj->group()->unboxedLayoutDontCheckGeneration().elementType();
+    EmitGuardUnboxedPropertyType(writer, elementType, rhsId);
+
+    writer.storeUnboxedArrayElement(objId, indexId, rhsId, elementType);
+    writer.returnFromIC();
+
+    // Type inference uses JSID_VOID for the element types.
+    setUpdateStubInfo(obj->group(), JSID_VOID);
+
+    trackAttached("SetUnboxedArrayElement");
+    return true;
+}
+
+bool
 SetPropIRGenerator::tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape)
 {
     AutoAssertNoPendingException aanpe(cx_);
 
     ValOperandId objValId(writer.setInputOperandId(0));
     ValOperandId rhsValId;
     if (cacheKind_ == CacheKind::SetProp) {
         rhsValId = ValOperandId(writer.setInputOperandId(1));
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -182,16 +182,18 @@ extern const char* CacheKindNames[];
     _(StoreFixedSlot)                     \
     _(StoreDynamicSlot)                   \
     _(AddAndStoreFixedSlot)               \
     _(AddAndStoreDynamicSlot)             \
     _(AllocateAndStoreDynamicSlot)        \
     _(StoreTypedObjectReferenceProperty)  \
     _(StoreTypedObjectScalarProperty)     \
     _(StoreUnboxedProperty)               \
+    _(StoreDenseElement)                  \
+    _(StoreUnboxedArrayElement)           \
     _(CallNativeSetter)                   \
     _(CallScriptedSetter)                 \
     _(CallSetArrayLength)                 \
                                           \
     /* The *Result ops load a value into the cache's result register. */ \
     _(LoadFixedSlotResult)                \
     _(LoadDynamicSlotResult)              \
     _(LoadUnboxedPropertyResult)          \
@@ -643,16 +645,29 @@ class MOZ_RAII CacheIRWriter : public JS
     void storeUnboxedProperty(ObjOperandId obj, JSValueType type, size_t offset,
                               ValOperandId rhs)
     {
         writeOpWithOperandId(CacheOp::StoreUnboxedProperty, obj);
         buffer_.writeByte(uint32_t(type));
         addStubField(offset, StubField::Type::RawWord);
         writeOperandId(rhs);
     }
+    void storeDenseElement(ObjOperandId obj, Int32OperandId index, ValOperandId rhs) {
+        writeOpWithOperandId(CacheOp::StoreDenseElement, obj);
+        writeOperandId(index);
+        writeOperandId(rhs);
+    }
+    void storeUnboxedArrayElement(ObjOperandId obj, Int32OperandId index, ValOperandId rhs,
+                                  JSValueType elementType)
+    {
+        writeOpWithOperandId(CacheOp::StoreUnboxedArrayElement, obj);
+        writeOperandId(index);
+        writeOperandId(rhs);
+        buffer_.writeByte(uint32_t(elementType));
+    }
     void callScriptedSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) {
         writeOpWithOperandId(CacheOp::CallScriptedSetter, obj);
         addStubField(uintptr_t(setter), StubField::Type::JSObject);
         writeOperandId(rhs);
     }
     void callNativeSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) {
         writeOpWithOperandId(CacheOp::CallNativeSetter, obj);
         addStubField(uintptr_t(setter), StubField::Type::JSObject);
@@ -1016,16 +1031,21 @@ class MOZ_RAII SetPropIRGenerator : publ
                                   ValOperandId rhsId);
     bool tryAttachTypedObjectProperty(HandleObject obj, ObjOperandId objId, HandleId id,
                                       ValOperandId rhsId);
     bool tryAttachSetter(HandleObject obj, ObjOperandId objId, HandleId id,
                          ValOperandId rhsId);
     bool tryAttachSetArrayLength(HandleObject obj, ObjOperandId objId, HandleId id,
                                  ValOperandId rhsId);
 
+    bool tryAttachSetDenseElement(HandleObject obj, ObjOperandId objId, uint32_t index,
+                                  Int32OperandId indexId, ValOperandId rhsId);
+    bool tryAttachSetUnboxedArrayElement(HandleObject obj, ObjOperandId objId, uint32_t index,
+                                         Int32OperandId indexId, ValOperandId rhsId);    
+
     void trackAttached(const char* name);
 
   public:
     SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind,
                        bool* isTemporarilyUnoptimizable, HandleValue lhsVal, HandleValue idVal,
                        HandleValue rhsVal);
 
     bool tryAttachStub();
--- a/js/src/jit/ExecutableAllocator.cpp
+++ b/js/src/jit/ExecutableAllocator.cpp
@@ -319,17 +319,17 @@ ExecutableAllocator::reprotectAll(Protec
         reprotectPool(rt_, r.front(), protection);
 }
 
 /* static */ void
 ExecutableAllocator::reprotectPool(JSRuntime* rt, ExecutablePool* pool, ProtectionSetting protection)
 {
     // Don't race with reprotectAll called from the signal handler.
     MOZ_ASSERT(rt->jitRuntime()->preventBackedgePatching() ||
-               rt->unsafeContextFromAnyThread()->handlingJitInterrupt());
+               rt->activeContext()->handlingJitInterrupt());
 
     char* start = pool->m_allocation.pages;
     if (!ReprotectRegion(start, pool->m_freePtr - start, protection))
         MOZ_CRASH();
 }
 
 /* static */ void
 ExecutableAllocator::poisonCode(JSRuntime* rt, JitPoisonRangeVector& ranges)
--- a/js/src/jit/ExecutableAllocator.h
+++ b/js/src/jit/ExecutableAllocator.h
@@ -221,17 +221,17 @@ class ExecutableAllocator
 
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_SIMULATOR_ARM64)
     static void cacheFlush(void*, size_t)
     {
     }
 #elif defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
     static void cacheFlush(void* code, size_t size)
     {
-        js::jit::Simulator::FlushICache(code, size);
+        js::jit::SimulatorProcess::FlushICache(code, size);
     }
 #elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     static void cacheFlush(void* code, size_t size)
     {
 #if defined(_MIPS_ARCH_LOONGSON3A)
         // On Loongson3-CPUs, The cache flushed automatically
         // by hardware. Just need to execute an instruction hazard.
         uintptr_t tmp;
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -881,16 +881,28 @@ IonCacheIRCompiler::emitStoreTypedObject
 
 bool
 IonCacheIRCompiler::emitStoreTypedObjectScalarProperty()
 {
     MOZ_CRASH("Baseline-specific op");
 }
 
 bool
+IonCacheIRCompiler::emitStoreDenseElement()
+{
+    MOZ_CRASH("Baseline-specific op");
+}
+
+bool
+IonCacheIRCompiler::emitStoreUnboxedArrayElement()
+{
+    MOZ_CRASH("Baseline-specific op");
+}
+
+bool
 IonCacheIRCompiler::emitCallNativeSetter()
 {
     MOZ_CRASH("Baseline-specific op");
 }
 
 bool
 IonCacheIRCompiler::emitCallScriptedSetter()
 {
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1817,16 +1817,27 @@ MacroAssembler::convertInt32ValueToDoubl
 {
     branchTestInt32(Assembler::NotEqual, address, done);
     unboxInt32(address, scratch);
     convertInt32ToDouble(scratch, ScratchDoubleReg);
     storeDouble(ScratchDoubleReg, address);
 }
 
 void
+MacroAssembler::convertInt32ValueToDouble(ValueOperand val)
+{
+    Label done;
+    branchTestInt32(Assembler::NotEqual, val, &done);
+    unboxInt32(val, val.scratchReg());
+    convertInt32ToDouble(val.scratchReg(), ScratchDoubleReg);
+    boxDouble(ScratchDoubleReg, val);
+    bind(&done);
+}
+
+void
 MacroAssembler::convertValueToFloatingPoint(ValueOperand value, FloatRegister output,
                                             Label* fail, MIRType outputType)
 {
     Register tag = splitTagForTest(value);
 
     Label isDouble, isInt32, isBool, isNull, done;
 
     branchTestDouble(Assembler::Equal, tag, &isDouble);
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1955,16 +1955,18 @@ class MacroAssembler : public MacroAssem
                                                                MIRType outputType);
     void convertTypedOrValueToFloatingPoint(TypedOrValueRegister src, FloatRegister output,
                                             Label* fail, MIRType outputType);
 
     void outOfLineTruncateSlow(FloatRegister src, Register dest, bool widenFloatToDouble,
                                bool compilingWasm);
 
     void convertInt32ValueToDouble(const Address& address, Register scratch, Label* done);
+    void convertInt32ValueToDouble(ValueOperand val);
+
     void convertValueToDouble(ValueOperand value, FloatRegister output, Label* fail) {
         convertValueToFloatingPoint(value, output, fail, MIRType::Double);
     }
     MOZ_MUST_USE bool convertValueToDouble(JSContext* cx, const Value& v, FloatRegister output,
                                            Label* fail) {
         return convertValueToFloatingPoint(cx, v, output, fail, MIRType::Double);
     }
     MOZ_MUST_USE bool convertConstantOrRegisterToDouble(JSContext* cx,
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -262,22 +262,16 @@ ICStub::trace(JSTracer* trc)
       }
       case ICStub::Call_StringSplit: {
         ICCall_StringSplit* callStub = toCall_StringSplit();
         TraceEdge(trc, &callStub->templateObject(), "baseline-callstringsplit-template");
         TraceEdge(trc, &callStub->expectedSep(), "baseline-callstringsplit-sep");
         TraceEdge(trc, &callStub->expectedStr(), "baseline-callstringsplit-str");
         break;
       }
-      case ICStub::SetElem_DenseOrUnboxedArray: {
-        ICSetElem_DenseOrUnboxedArray* setElemStub = toSetElem_DenseOrUnboxedArray();
-        TraceNullableEdge(trc, &setElemStub->shape(), "baseline-getelem-dense-shape");
-        TraceEdge(trc, &setElemStub->group(), "baseline-setelem-dense-group");
-        break;
-      }
       case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
         ICSetElem_DenseOrUnboxedArrayAdd* setElemStub = toSetElem_DenseOrUnboxedArrayAdd();
         TraceEdge(trc, &setElemStub->group(), "baseline-setelem-denseadd-group");
 
         JS_STATIC_ASSERT(ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH == 4);
 
         switch (setElemStub->protoChainDepth()) {
           case 0: setElemStub->toImpl<0>()->traceShapes(trc); break;
@@ -652,16 +646,19 @@ ICStubCompiler::PushStubPayload(MacroAss
     masm.adjustFrame(sizeof(intptr_t));
 }
 
 void
 BaselineEmitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val,
                                  Register scratch, LiveGeneralRegisterSet saveRegs,
                                  JSContext* cx)
 {
+    if (!cx->nursery().exists())
+        return;
+
     Label skipBarrier;
     masm.branchPtrInNurseryChunk(Assembler::Equal, obj, scratch, &skipBarrier);
     masm.branchValueIsNurseryObject(Assembler::NotEqual, val, scratch, &skipBarrier);
 
     // void PostWriteBarrier(JSRuntime* rt, JSObject* obj);
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     saveRegs.add(ICTailCallReg);
 #endif
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -360,56 +360,41 @@ class CachePage
 
 // Protects the icache() and redirection() properties of the
 // Simulator.
 class AutoLockSimulatorCache : public LockGuard<Mutex>
 {
     using Base = LockGuard<Mutex>;
 
   public:
-    explicit AutoLockSimulatorCache(Simulator* sim)
-      : Base(sim->cacheLock_)
-      , sim_(sim)
-    {
-        MOZ_ASSERT(sim_->cacheLockHolder_.isNothing());
-#ifdef DEBUG
-        sim_->cacheLockHolder_ = mozilla::Some(ThisThread::GetId());
-#endif
-    }
-
-    ~AutoLockSimulatorCache() {
-        MOZ_ASSERT(sim_->cacheLockHolder_.isSome());
-#ifdef DEBUG
-        sim_->cacheLockHolder_.reset();
-#endif
-    }
-
-  private:
-    Simulator* const sim_;
+    explicit AutoLockSimulatorCache()
+      : Base(SimulatorProcess::singleton_->cacheLock_)
+    {}
 };
 
-bool Simulator::ICacheCheckingEnabled = false;
+mozilla::Atomic<size_t, mozilla::ReleaseAcquire>
+    SimulatorProcess::ICacheCheckingDisableCount(1); // Checking is disabled by default.
+mozilla::Atomic<bool, mozilla::ReleaseAcquire>
+    SimulatorProcess::cacheInvalidatedBySignalHandler_(false);
+SimulatorProcess* SimulatorProcess::singleton_ = nullptr;
 
 int64_t Simulator::StopSimAt = -1L;
 
 Simulator*
 Simulator::Create(JSContext* cx)
 {
     Simulator* sim = js_new<Simulator>(cx);
     if (!sim)
         return nullptr;
 
     if (!sim->init()) {
         js_delete(sim);
         return nullptr;
     }
 
-    if (getenv("ARM_SIM_ICACHE_CHECKS"))
-        Simulator::ICacheCheckingEnabled = true;
-
     char* stopAtStr = getenv("ARM_SIM_STOP_AT");
     int64_t stopAt;
     if (stopAtStr && sscanf(stopAtStr, "%lld", &stopAt) == 1) {
         fprintf(stderr, "\nStopping simulation at icount %lld\n", stopAt);
         Simulator::StopSimAt = stopAt;
     }
 
     return sim;
@@ -993,48 +978,48 @@ static bool
 AllOnOnePage(uintptr_t start, int size)
 {
     intptr_t start_page = (start & ~CachePage::kPageMask);
     intptr_t end_page = ((start + size) & ~CachePage::kPageMask);
     return start_page == end_page;
 }
 
 static CachePage*
-GetCachePageLocked(Simulator::ICacheMap& i_cache, void* page)
+GetCachePageLocked(SimulatorProcess::ICacheMap& i_cache, void* page)
 {
-    Simulator::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
+    SimulatorProcess::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
     if (p)
         return p->value();
 
     AutoEnterOOMUnsafeRegion oomUnsafe;
     CachePage* new_page = js_new<CachePage>();
     if (!new_page || !i_cache.add(p, page, new_page))
         oomUnsafe.crash("Simulator CachePage");
 
     return new_page;
 }
 
 // Flush from start up to and not including start + size.
 static void
-FlushOnePageLocked(Simulator::ICacheMap& i_cache, intptr_t start, int size)
+FlushOnePageLocked(SimulatorProcess::ICacheMap& i_cache, intptr_t start, int size)
 {
     MOZ_ASSERT(size <= CachePage::kPageSize);
     MOZ_ASSERT(AllOnOnePage(start, size - 1));
     MOZ_ASSERT((start & CachePage::kLineMask) == 0);
     MOZ_ASSERT((size & CachePage::kLineMask) == 0);
 
     void* page = reinterpret_cast<void*>(start & (~CachePage::kPageMask));
     int offset = (start & CachePage::kPageMask);
     CachePage* cache_page = GetCachePageLocked(i_cache, page);
     char* valid_bytemap = cache_page->validityByte(offset);
     memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift);
 }
 
 static void
-FlushICacheLocked(Simulator::ICacheMap& i_cache, void* start_addr, size_t size)
+FlushICacheLocked(SimulatorProcess::ICacheMap& i_cache, void* start_addr, size_t size)
 {
     intptr_t start = reinterpret_cast<intptr_t>(start_addr);
     int intra_line = (start & CachePage::kLineMask);
     start -= intra_line;
     size += intra_line;
     size = ((size - 1) | CachePage::kLineMask) + 1;
     int offset = (start & CachePage::kPageMask);
     while (!AllOnOnePage(start, size - 1)) {
@@ -1044,24 +1029,24 @@ FlushICacheLocked(Simulator::ICacheMap& 
         size -= bytes_to_flush;
         MOZ_ASSERT((start & CachePage::kPageMask) == 0);
         offset = 0;
     }
     if (size != 0)
         FlushOnePageLocked(i_cache, start, size);
 }
 
-void
-Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* instr)
+/* static */ void
+SimulatorProcess::checkICacheLocked(SimInstruction* instr)
 {
     intptr_t address = reinterpret_cast<intptr_t>(instr);
     void* page = reinterpret_cast<void*>(address & (~CachePage::kPageMask));
     void* line = reinterpret_cast<void*>(address & (~CachePage::kLineMask));
     int offset = (address & CachePage::kPageMask);
-    CachePage* cache_page = GetCachePageLocked(i_cache, page);
+    CachePage* cache_page = GetCachePageLocked(icache(), page);
     char* cache_valid_byte = cache_page->validityByte(offset);
     bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID);
     char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask);
 
     // Read all state before considering signal handler effects.
     int cmpret = 0;
     if (cache_hit) {
         // Check that the data in memory matches the contents of the I-cache.
@@ -1069,67 +1054,63 @@ Simulator::checkICacheLocked(Simulator::
                         cache_page->cachedData(offset),
                         SimInstruction::kInstrSize);
     }
 
     // Check for signal handler interruption between reading state and asserting.
     // It is safe for the signal to arrive during the !cache_hit path, since it
     // will be cleared the next time this function is called.
     if (cacheInvalidatedBySignalHandler_) {
-        i_cache.clear();
+        icache().clear();
         cacheInvalidatedBySignalHandler_ = false;
         return;
     }
 
     if (cache_hit) {
         MOZ_ASSERT(cmpret == 0);
     } else {
         // Cache miss. Load memory into the cache.
         memcpy(cached_line, line, CachePage::kLineLength);
         *cache_valid_byte = CachePage::LINE_VALID;
     }
 }
 
 HashNumber
-Simulator::ICacheHasher::hash(const Lookup& l)
+SimulatorProcess::ICacheHasher::hash(const Lookup& l)
 {
     return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(l)) >> 2;
 }
 
 bool
-Simulator::ICacheHasher::match(const Key& k, const Lookup& l)
+SimulatorProcess::ICacheHasher::match(const Key& k, const Lookup& l)
 {
     MOZ_ASSERT((reinterpret_cast<intptr_t>(k) & CachePage::kPageMask) == 0);
     MOZ_ASSERT((reinterpret_cast<intptr_t>(l) & CachePage::kPageMask) == 0);
     return k == l;
 }
 
 void
 Simulator::setLastDebuggerInput(char* input)
 {
     js_free(lastDebuggerInput_);
     lastDebuggerInput_ = input;
 }
 
-void
-Simulator::FlushICache(void* start_addr, size_t size)
+/* static */ void
+SimulatorProcess::FlushICache(void* start_addr, size_t size)
 {
     JitSpewCont(JitSpew_CacheFlush, "[%p %" PRIxSIZE "]", start_addr, size);
-    if (Simulator::ICacheCheckingEnabled) {
-        Simulator* sim = Simulator::Current();
-
-        AutoLockSimulatorCache als(sim);
-
-        js::jit::FlushICacheLocked(sim->icache(), start_addr, size);
+    if (!ICacheCheckingDisableCount) {
+        AutoLockSimulatorCache als;
+        js::jit::FlushICacheLocked(icache(), start_addr, size);
     }
 }
 
 Simulator::Simulator(JSContext* cx)
-  : cx_(cx),
-    cacheLock_(mutexid::SimulatorCacheLock)
+  : cx_(cx)
 {
     // Set up simulator support first. Some of this information is needed to
     // setup the architecture state.
 
     // Note, allocation and anything that depends on allocated memory is
     // deferred until init(), in order to handle OOM properly.
 
     stack_ = nullptr;
@@ -1137,17 +1118,16 @@ Simulator::Simulator(JSContext* cx)
     pc_modified_ = false;
     icount_ = 0L;
     resume_pc_ = 0;
     break_pc_ = nullptr;
     break_instr_ = 0;
     single_stepping_ = false;
     single_step_callback_ = nullptr;
     single_step_callback_arg_ = nullptr;
-    cacheInvalidatedBySignalHandler_ = false;
     skipCalleeSavedRegsCheck = false;
 
     // Set up architecture state.
     // All registers are initialized to zero to start with.
     for (int i = 0; i < num_registers; i++)
         registers_[i] = 0;
 
     n_flag_ = false;
@@ -1173,27 +1153,23 @@ Simulator::Simulator(JSContext* cx)
 
     // The lr and pc are initialized to a known bad value that will cause an
     // access violation if the simulator ever tries to execute it.
     registers_[pc] = bad_lr;
     registers_[lr] = bad_lr;
 
     lastDebuggerInput_ = nullptr;
 
-    redirection_ = nullptr;
     exclusiveMonitorHeld_ = false;
     exclusiveMonitor_ = 0;
 }
 
 bool
 Simulator::init()
 {
-    if (!icache_.init())
-        return false;
-
     // Allocate 2MB for the stack. Note that we will only use 1MB, see below.
     static const size_t stackSize = 2 * 1024*1024;
     stack_ = reinterpret_cast<char*>(js_malloc(stackSize));
     if (!stack_)
         return false;
 
     // Leave a safety margin of 1MB to prevent overrunning the stack when
     // pushing values (total stack size is 2MB).
@@ -1210,54 +1186,54 @@ Simulator::init()
 // When the generated code calls a VM function (masm.callWithABI) we need to
 // call that function instead of trying to execute it with the simulator
 // (because it's x86 code instead of arm code). We do that by redirecting the VM
 // call to a svc (Supervisor Call) instruction that is handled by the
 // simulator. We write the original destination of the jump just at a known
 // offset from the svc instruction so the simulator knows what to call.
 class Redirection
 {
-    friend class Simulator;
+    friend class SimulatorProcess;
 
     // sim's lock must already be held.
-    Redirection(void* nativeFunction, ABIFunctionType type, Simulator* sim)
+    Redirection(void* nativeFunction, ABIFunctionType type)
       : nativeFunction_(nativeFunction),
         swiInstruction_(Assembler::AL | (0xf * (1 << 24)) | kCallRtRedirected),
         type_(type),
         next_(nullptr)
     {
-        next_ = sim->redirection();
-        if (Simulator::ICacheCheckingEnabled)
-            FlushICacheLocked(sim->icache(), addressOfSwiInstruction(), SimInstruction::kInstrSize);
-        sim->setRedirection(this);
+        next_ = SimulatorProcess::redirection();
+        if (!SimulatorProcess::ICacheCheckingDisableCount) {
+            FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
+                              SimInstruction::kInstrSize);
+        }
+        SimulatorProcess::setRedirection(this);
     }
 
   public:
     void* addressOfSwiInstruction() { return &swiInstruction_; }
     void* nativeFunction() const { return nativeFunction_; }
     ABIFunctionType type() const { return type_; }
 
     static Redirection* Get(void* nativeFunction, ABIFunctionType type) {
-        Simulator* sim = Simulator::Current();
-
-        AutoLockSimulatorCache als(sim);
-
-        Redirection* current = sim->redirection();
+        AutoLockSimulatorCache als;
+
+        Redirection* current = SimulatorProcess::redirection();
         for (; current != nullptr; current = current->next_) {
             if (current->nativeFunction_ == nativeFunction) {
                 MOZ_ASSERT(current->type() == type);
                 return current;
             }
         }
 
         AutoEnterOOMUnsafeRegion oomUnsafe;
         Redirection* redir = (Redirection*)js_malloc(sizeof(Redirection));
         if (!redir)
             oomUnsafe.crash("Simulator redirection");
-        new(redir) Redirection(nativeFunction, type, sim);
+        new(redir) Redirection(nativeFunction, type);
         return redir;
     }
 
     static Redirection* FromSwiInstruction(SimInstruction* swiInstruction) {
         uint8_t* addrOfSwi = reinterpret_cast<uint8_t*>(swiInstruction);
         uint8_t* addrOfRedirection = addrOfSwi - offsetof(Redirection, swiInstruction_);
         return reinterpret_cast<Redirection*>(addrOfRedirection);
     }
@@ -1267,24 +1243,42 @@ class Redirection
     uint32_t swiInstruction_;
     ABIFunctionType type_;
     Redirection* next_;
 };
 
 Simulator::~Simulator()
 {
     js_free(stack_);
+}
+
+SimulatorProcess::SimulatorProcess()
+  : cacheLock_(mutexid::SimulatorCacheLock)
+  , redirection_(nullptr)
+{}
+
+SimulatorProcess::~SimulatorProcess()
+{
     Redirection* r = redirection_;
     while (r) {
         Redirection* next = r->next_;
         js_delete(r);
         r = next;
     }
 }
 
+bool
+SimulatorProcess::init()
+{
+    if (getenv("ARM_SIM_ICACHE_CHECKS"))
+        ICacheCheckingDisableCount = 0;
+
+    return icache_.init();
+}
+
 /* static */ void*
 Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type)
 {
     Redirection* redirection = Redirection::Get(nativeFunction, type);
     return redirection->addressOfSwiInstruction();
 }
 
 // Sets the register in the architecture state. It will also deal with updating
@@ -4655,19 +4649,19 @@ Simulator::decodeSpecialCondition(SimIns
         MOZ_CRASH();
     }
 }
 
 // Executes the current instruction.
 void
 Simulator::instructionDecode(SimInstruction* instr)
 {
-    if (Simulator::ICacheCheckingEnabled) {
-        AutoLockSimulatorCache als(this);
-        checkICacheLocked(icache(), instr);
+    if (!SimulatorProcess::ICacheCheckingDisableCount) {
+        AutoLockSimulatorCache als;
+        SimulatorProcess::checkICacheLocked(instr);
     }
 
     pc_modified_ = false;
 
     static const uint32_t kSpecialCondition = 15 << 28;
     if (instr->conditionField() == kSpecialCondition) {
         decodeSpecialCondition(instr);
     } else if (conditionallyExecute(instr)) {
@@ -4926,17 +4920,19 @@ Simulator::call(uint8_t* entry, int argu
 
     int32_t result = get_register(r0);
     return result;
 }
 
 Simulator*
 Simulator::Current()
 {
-    return TlsContext.get()->runtime()->unsafeContextFromAnyThread()->simulator();
+    JSContext* cx = TlsContext.get();
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
+    return cx->simulator();
 }
 
 } // namespace jit
 } // namespace js
 
 js::jit::Simulator*
 JSContext::simulator() const
 {
--- a/js/src/jit/arm/Simulator-arm.h
+++ b/js/src/jit/arm/Simulator-arm.h
@@ -66,21 +66,19 @@ enum VFPRoundingMode {
     kRoundToZero = SimRZ
 };
 
 const uint32_t kVFPRoundingModeMask = 3 << 22;
 
 typedef int32_t Instr;
 class SimInstruction;
 
+// Per thread simulator state.
 class Simulator
 {
-    friend class Redirection;
-    friend class AutoLockSimulatorCache;
-
   public:
     friend class ArmDebugger;
     enum Register {
         no_reg = -1,
         r0 = 0, r1, r2, r3, r4, r5, r6, r7,
         r8, r9, r10, r11, r12, r13, r14, r15,
         num_registers,
         sp = 13,
@@ -342,40 +340,17 @@ class Simulator
     void decodeVCVTBetweenFloatingPointAndIntegerFrac(SimInstruction* instr);
 
     // Support for some system functions.
     void decodeType7CoprocessorIns(SimInstruction* instr);
 
     // Executes one instruction.
     void instructionDecode(SimInstruction* instr);
 
-  private:
-    // ICache checking.
-    struct ICacheHasher {
-        typedef void* Key;
-        typedef void* Lookup;
-        static HashNumber hash(const Lookup& l);
-        static bool match(const Key& k, const Lookup& l);
-    };
-
   public:
-    typedef HashMap<void*, CachePage*, ICacheHasher, SystemAllocPolicy> ICacheMap;
-
-  public:
-    static bool ICacheCheckingEnabled;
-    static void FlushICache(void* start, size_t size);
-
-    // Jitcode may be rewritten from a signal handler, but is prevented from
-    // calling FlushICache() because the signal may arrive within the critical
-    // area of an AutoLockSimulatorCache. This flag instructs the Simulator
-    // to remove all cache entries the next time it checks, avoiding false negatives.
-    mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
-
-    void checkICacheLocked(ICacheMap& i_cache, SimInstruction* instr);
-
     static int64_t StopSimAt;
 
     // For testing the MoveResolver code, a MoveResolver is set up, and
     // the VFP registers are loaded with pre-determined values,
     // then the sequence of code is simulated.  In order to test this with the
     // simulator, the callee-saved registers can't be trashed. This flag
     // disables that feature.
     bool skipCalleeSavedRegsCheck;
@@ -468,57 +443,100 @@ class Simulator
     StopCountAndDesc watched_stops_[kNumOfWatchedStops];
 
   public:
     int64_t icount() {
         return icount_;
     }
 
   private:
-    // This lock creates a critical section around 'redirection_' and
-    // 'icache_', which are referenced both by the execution engine
-    // and by the off-thread compiler (see Redirection::Get in the cpp file).
-    Mutex cacheLock_;
-#ifdef DEBUG
-    mozilla::Maybe<Thread::Id> cacheLockHolder_;
-#endif
-
-    Redirection* redirection_;
-    ICacheMap icache_;
-
-  public:
-    ICacheMap& icache() {
-        // Technically we need the lock to access the innards of the
-        // icache, not to take its address, but the latter condition
-        // serves as a useful complement to the former.
-        MOZ_ASSERT(cacheLockHolder_.isSome());
-        return icache_;
-    }
-
-    Redirection* redirection() const {
-        MOZ_ASSERT(cacheLockHolder_.isSome());
-        return redirection_;
-    }
-
-    void setRedirection(js::jit::Redirection* redirection) {
-        MOZ_ASSERT(cacheLockHolder_.isSome());
-        redirection_ = redirection;
-    }
-
-  private:
     // Exclusive access monitor
     void exclusiveMonitorSet(uint64_t value);
     uint64_t exclusiveMonitorGetAndClear(bool* held);
     void exclusiveMonitorClear();
 
     bool exclusiveMonitorHeld_;
     uint64_t exclusiveMonitor_;
 };
 
-#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror)             \
+// Process wide simulator state.
+class SimulatorProcess
+{
+    friend class Redirection;
+    friend class AutoLockSimulatorCache;
+
+  private:
+    // ICache checking.
+    struct ICacheHasher {
+        typedef void* Key;
+        typedef void* Lookup;
+        static HashNumber hash(const Lookup& l);
+        static bool match(const Key& k, const Lookup& l);
+    };
+
+  public:
+    typedef HashMap<void*, CachePage*, ICacheHasher, SystemAllocPolicy> ICacheMap;
+
+    static mozilla::Atomic<size_t, mozilla::ReleaseAcquire> ICacheCheckingDisableCount;
+    static void FlushICache(void* start, size_t size);
+
+    // Jitcode may be rewritten from a signal handler, but is prevented from
+    // calling FlushICache() because the signal may arrive within the critical
+    // area of an AutoLockSimulatorCache. This flag instructs the Simulator
+    // to remove all cache entries the next time it checks, avoiding false negatives.
+    static mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
+
+    static void checkICacheLocked(SimInstruction* instr);
+
+    static bool initialize() {
+        singleton_ = js_new<SimulatorProcess>();
+        return singleton_ && singleton_->init();
+    }
+    static void destroy() {
+        js_delete(singleton_);
+        singleton_ = nullptr;
+    }
+
+    SimulatorProcess();
+    ~SimulatorProcess();
+
+  private:
+    bool init();
+
+    static SimulatorProcess* singleton_;
+
+    // This lock creates a critical section around 'redirection_' and
+    // 'icache_', which are referenced both by the execution engine
+    // and by the off-thread compiler (see Redirection::Get in the cpp file).
+    Mutex cacheLock_;
+
+    Redirection* redirection_;
+    ICacheMap icache_;
+
+  public:
+    static ICacheMap& icache() {
+        // Technically we need the lock to access the innards of the
+        // icache, not to take its address, but the latter condition
+        // serves as a useful complement to the former.
+        MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
+        return singleton_->icache_;
+    }
+
+    static Redirection* redirection() {
+        MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
+        return singleton_->redirection_;
+    }
+
+    static void setRedirection(js::jit::Redirection* redirection) {
+        MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
+        singleton_->redirection_ = redirection;
+    }
+};
+
+#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror)     \
     JS_BEGIN_MACRO                                                              \
         if (cx->simulator()->overRecursedWithExtra(extra)) {                    \
             js::ReportOverRecursed(cx);                                         \
             onerror;                                                            \
         }                                                                       \
     JS_END_MACRO
 
 } // namespace jit
--- a/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
@@ -27,31 +27,33 @@
 #include "mozilla/DebugOnly.h"
 
 #include "jit/arm64/vixl/Debugger-vixl.h"
 #include "jit/arm64/vixl/Simulator-vixl.h"
 #include "jit/IonTypes.h"
 #include "threading/LockGuard.h"
 #include "vm/Runtime.h"
 
+js::jit::SimulatorProcess* js::jit::SimulatorProcess::singleton_ = nullptr;
+
 namespace vixl {
 
 
 using mozilla::DebugOnly;
 using js::jit::ABIFunctionType;
+using js::jit::SimulatorProcess;
 
 Simulator::Simulator(Decoder* decoder, FILE* stream)
   : stream_(nullptr)
   , print_disasm_(nullptr)
   , instrumentation_(nullptr)
   , stack_(nullptr)
   , stack_limit_(nullptr)
   , decoder_(nullptr)
   , oom_(false)
-  , lock_(js::mutexid::Arm64SimulatorLock)
 {
     this->init(decoder, stream);
 }
 
 
 Simulator::~Simulator() {
   js_free(stack_);
   stack_ = nullptr;
@@ -139,23 +141,23 @@ void Simulator::init(Decoder* decoder, F
     oom_ = true;
     return;
   }
 
   // Print a warning about exclusive-access instructions, but only the first
   // time they are encountered. This warning can be silenced using
   // SilenceExclusiveAccessWarning().
   print_exclusive_access_warning_ = true;
-
-  redirection_ = nullptr;
 }
 
 
 Simulator* Simulator::Current() {
-  return js::TlsContext.get()->simulator();
+  JSContext* cx = js::TlsContext.get();
+  MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(cx->runtime()));
+  return cx->simulator();
 }
 
 
 Simulator* Simulator::Create(JSContext* cx) {
   Decoder *decoder = js_new<vixl::Decoder>();
   if (!decoder)
     return nullptr;
 
@@ -289,71 +291,70 @@ int64_t Simulator::call(uint8_t* entry, 
 
 // Protects the icache and redirection properties of the simulator.
 class AutoLockSimulatorCache : public js::LockGuard<js::Mutex>
 {
   friend class Simulator;
   using Base = js::LockGuard<js::Mutex>;
 
  public:
-  explicit AutoLockSimulatorCache(Simulator* sim)
-    : Base(sim->lock_)
+  explicit AutoLockSimulatorCache()
+    : Base(SimulatorProcess::singleton_->lock_)
   {
   }
 };
 
 
 // When the generated code calls a VM function (masm.callWithABI) we need to
 // call that function instead of trying to execute it with the simulator
 // (because it's x64 code instead of AArch64 code). We do that by redirecting the VM
 // call to a svc (Supervisor Call) instruction that is handled by the
 // simulator. We write the original destination of the jump just at a known
 // offset from the svc instruction so the simulator knows what to call.
 class Redirection
 {
   friend class Simulator;
 
-  Redirection(void* nativeFunction, ABIFunctionType type, Simulator* sim)
+  Redirection(void* nativeFunction, ABIFunctionType type)
     : nativeFunction_(nativeFunction),
     type_(type),
     next_(nullptr)
   {
-    next_ = sim->redirection();
+    next_ = SimulatorProcess::redirection();
     // TODO: Flush ICache?
-    sim->setRedirection(this);
+    SimulatorProcess::setRedirection(this);
 
     Instruction* instr = (Instruction*)(&svcInstruction_);
     vixl::Assembler::svc(instr, kCallRtRedirected);
   }
 
  public:
   void* addressOfSvcInstruction() { return &svcInstruction_; }
   void* nativeFunction() const { return nativeFunction_; }
   ABIFunctionType type() const { return type_; }
 
   static Redirection* Get(void* nativeFunction, ABIFunctionType type) {
-    Simulator* sim = Simulator::Current();
-    AutoLockSimulatorCache alsr(sim);
+    AutoLockSimulatorCache alsr;
 
     // TODO: Store srt_ in the simulator for this assertion.
     // VIXL_ASSERT_IF(pt->simulator(), pt->simulator()->srt_ == srt);
 
-    Redirection* current = sim->redirection();
+    Redirection* current = SimulatorProcess::redirection();
     for (; current != nullptr; current = current->next_) {
       if (current->nativeFunction_ == nativeFunction) {
         VIXL_ASSERT(current->type() == type);
         return current;
       }
     }
 
     js::AutoEnterOOMUnsafeRegion oomUnsafe;
     Redirection* redir = (Redirection*)js_malloc(sizeof(Redirection));
     if (!redir)
         oomUnsafe.crash("Simulator redirection");
-    new(redir) Redirection(nativeFunction, type, sim);
+    new(redir) Redirection(nativeFunction, type);
     return redir;
   }
 
   static const Redirection* FromSvcInstruction(const Instruction* svcInstruction) {
     const uint8_t* addrOfSvc = reinterpret_cast<const uint8_t*>(svcInstruction);
     const uint8_t* addrOfRedirection = addrOfSvc - offsetof(Redirection, svcInstruction_);
     return reinterpret_cast<const Redirection*>(addrOfRedirection);
   }
@@ -361,24 +362,16 @@ class Redirection
  private:
   void* nativeFunction_;
   uint32_t svcInstruction_;
   ABIFunctionType type_;
   Redirection* next_;
 };
 
 
-void Simulator::setRedirection(Redirection* redirection) {
-  redirection_ = redirection;
-}
-
-
-Redirection* Simulator::redirection() const {
-  return redirection_;
-}
 
 
 void* Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type) {
   Redirection* redirection = Redirection::Get(nativeFunction, type);
   return redirection->addressOfSvcInstruction();
 }
 
 
--- a/js/src/jit/arm64/vixl/Simulator-vixl.h
+++ b/js/src/jit/arm64/vixl/Simulator-vixl.h
@@ -699,34 +699,30 @@ class SimExclusiveGlobalMonitor {
  private:
   const int kPassProbability;
   uint32_t seed_;
 };
 
 class Redirection;
 
 class Simulator : public DecoderVisitor {
-  friend class AutoLockSimulatorCache;
-
  public:
   explicit Simulator(Decoder* decoder, FILE* stream = stdout);
   ~Simulator();
 
   // Moz changes.
   void init(Decoder* decoder, FILE* stream);
   static Simulator* Current();
   static Simulator* Create(JSContext* cx);
   static void Destroy(Simulator* sim);
   uintptr_t stackLimit() const;
   uintptr_t* addressOfStackLimit();
   bool overRecursed(uintptr_t newsp = 0) const;
   bool overRecursedWithExtra(uint32_t extra) const;
   int64_t call(uint8_t* entry, int argument_count, ...);
-  void setRedirection(Redirection* redirection);
-  Redirection* redirection() const;
   static void* RedirectNativeFunction(void* nativeFunction, js::jit::ABIFunctionType type);
   void setGPR32Result(int32_t result);
   void setGPR64Result(int64_t result);
   void setFP32Result(float result);
   void setFP64Result(double result);
   void VisitCallRedirection(const Instruction* instr);
   static inline uintptr_t StackLimit() {
     return Simulator::Current()->stackLimit();
@@ -2661,17 +2657,55 @@ class Simulator : public DecoderVisitor 
   // Data structures may not be fully allocated.
   bool oom_;
 
  public:
   // True if the simulator ran out of memory during or after construction.
   bool oom() const { return oom_; }
 
  protected:
-  // Moz: Synchronizes access between main thread and compilation threads.
-  js::Mutex lock_;
-  Redirection* redirection_;
   mozilla::Vector<int64_t, 0, js::SystemAllocPolicy> spStack_;
 };
+
 }  // namespace vixl
 
+namespace js {
+namespace jit {
+
+class SimulatorProcess
+{
+ public:
+  static SimulatorProcess* singleton_;
+
+  SimulatorProcess()
+    : lock_(mutexid::Arm64SimulatorLock)
+    , redirection_(nullptr)
+  {}
+
+  // Synchronizes access between main thread and compilation threads.
+  js::Mutex lock_;
+  vixl::Redirection* redirection_;
+
+  static void setRedirection(vixl::Redirection* redirection) {
+    MOZ_ASSERT(singleton_->lock_.ownedByCurrentThread());
+    singleton_->redirection_ = redirection;
+  }
+
+  static vixl::Redirection* redirection() {
+    MOZ_ASSERT(singleton_->lock_.ownedByCurrentThread());
+    return singleton_->redirection_;
+  }
+
+  static bool initialize() {
+    singleton_ = js_new<SimulatorProcess>();
+    return !!singleton_;
+  }
+  static void destroy() {
+    js_delete(singleton_);
+    singleton_ = nullptr;
+  }
+};
+
+} // namespace jit
+} // namespace js
+
 #endif  // JS_SIMULATOR_ARM64
 #endif  // VIXL_A64_SIMULATOR_A64_H_
--- a/js/src/jit/mips32/Simulator-mips32.cpp
+++ b/js/src/jit/mips32/Simulator-mips32.cpp
@@ -494,56 +494,41 @@ class CachePage {
 
 // Protects the icache() and redirection() properties of the
 // Simulator.
 class AutoLockSimulatorCache : public LockGuard<Mutex>
 {
     using Base = LockGuard<Mutex>;
 
   public:
-    explicit AutoLockSimulatorCache(Simulator* sim)
-      : Base(sim->cacheLock_)
-      , sim_(sim)
-    {
-        MOZ_ASSERT(sim_->cacheLockHolder_.isNothing());
-#ifdef DEBUG
-        sim_->cacheLockHolder_ = mozilla::Some(ThisThread::GetId());
-#endif
-    }
-
-    ~AutoLockSimulatorCache() {
-        MOZ_ASSERT(sim_->cacheLockHolder_.isSome());
-#ifdef DEBUG
-        sim_->cacheLockHolder_.reset();
-#endif
-    }
-
-  private:
-    Simulator* const sim_;
+    AutoLockSimulatorCache()
+      : Base(SimulatorProcess::singleton_->cacheLock_)
+    {}
 };
 
-bool Simulator::ICacheCheckingEnabled = false;
+mozilla::Atomic<size_t, mozilla::ReleaseAcquire>
+    SimulatorProcess::ICacheCheckingDisableCount(1); // Checking is disabled by default.
+mozilla::Atomic<bool, mozilla::ReleaseAcquire>
+    SimulatorProcess::cacheInvalidatedBySignalHandler_(false);
+SimulatorProcess* SimulatorProcess::singleton_ = nullptr;
 
 int Simulator::StopSimAt = -1;
 
 Simulator*
 Simulator::Create(JSContext* cx)
 {
     Simulator* sim = js_new<Simulator>();
     if (!sim)
         return nullptr;
 
     if (!sim->init()) {
         js_delete(sim);
         return nullptr;
     }
 
-    if (getenv("MIPS_SIM_ICACHE_CHECKS"))
-        Simulator::ICacheCheckingEnabled = true;
-
     char* stopAtStr = getenv("MIPS_SIM_STOP_AT");
     int64_t stopAt;
     if (stopAtStr && sscanf(stopAtStr, "%lld", &stopAt) == 1) {
         fprintf(stderr, "\nStopping simulation at icount %lld\n", stopAt);
         Simulator::StopSimAt = stopAt;
     }
 
     return sim;
@@ -1146,45 +1131,45 @@ AllOnOnePage(uintptr_t start, int size)
 void
 Simulator::setLastDebuggerInput(char* input)
 {
     js_free(lastDebuggerInput_);
     lastDebuggerInput_ = input;
 }
 
 static CachePage*
-GetCachePageLocked(Simulator::ICacheMap& i_cache, void* page)
+GetCachePageLocked(SimulatorProcess::ICacheMap& i_cache, void* page)
 {
-    Simulator::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
+    SimulatorProcess::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
     if (p)
         return p->value();
 
     CachePage* new_page = js_new<CachePage>();
     if (!i_cache.add(p, page, new_page))
         return nullptr;
     return new_page;
 }
 
 // Flush from start up to and not including start + size.
 static void
-FlushOnePageLocked(Simulator::ICacheMap& i_cache, intptr_t start, int size)
+FlushOnePageLocked(SimulatorProcess::ICacheMap& i_cache, intptr_t start, int size)
 {
     MOZ_ASSERT(size <= CachePage::kPageSize);
     MOZ_ASSERT(AllOnOnePage(start, size - 1));
     MOZ_ASSERT((start & CachePage::kLineMask) == 0);
     MOZ_ASSERT((size & CachePage::kLineMask) == 0);
     void* page = reinterpret_cast<void*>(start & (~CachePage::kPageMask));
     int offset = (start & CachePage::kPageMask);
     CachePage* cache_page = GetCachePageLocked(i_cache, page);
     char* valid_bytemap = cache_page->validityByte(offset);
     memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift);
 }
 
 static void
-FlushICacheLocked(Simulator::ICacheMap& i_cache, void* start_addr, size_t size)
+FlushICacheLocked(SimulatorProcess::ICacheMap& i_cache, void* start_addr, size_t size)
 {
     intptr_t start = reinterpret_cast<intptr_t>(start_addr);
     int intra_line = (start & CachePage::kLineMask);
     start -= intra_line;
     size += intra_line;
     size = ((size - 1) | CachePage::kLineMask) + 1;
     int offset = (start & CachePage::kPageMask);
     while (!AllOnOnePage(start, size - 1)) {
@@ -1195,24 +1180,24 @@ FlushICacheLocked(Simulator::ICacheMap& 
         MOZ_ASSERT((start & CachePage::kPageMask) == 0);
         offset = 0;
     }
     if (size != 0) {
         FlushOnePageLocked(i_cache, start, size);
     }
 }
 
-void
-Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* instr)
+/* static */ void
+SimulatorProcess::checkICacheLocked(SimInstruction* instr)
 {
     intptr_t address = reinterpret_cast<intptr_t>(instr);
     void* page = reinterpret_cast<void*>(address & (~CachePage::kPageMask));
     void* line = reinterpret_cast<void*>(address & (~CachePage::kLineMask));
     int offset = (address & CachePage::kPageMask);
-    CachePage* cache_page = GetCachePageLocked(i_cache, page);
+    CachePage* cache_page = GetCachePageLocked(icache(), page);
     char* cache_valid_byte = cache_page->validityByte(offset);
     bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID);
     char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask);
 
     // Read all state before considering signal handler effects.
     int cmpret = 0;
     if (cache_hit) {
         // Check that the data in memory matches the contents of the I-cache.
@@ -1220,57 +1205,54 @@ Simulator::checkICacheLocked(Simulator::
                         cache_page->cachedData(offset),
                         SimInstruction::kInstrSize);
     }
 
     // Check for signal handler interruption between reading state and asserting.
     // It is safe for the signal to arrive during the !cache_hit path, since it
     // will be cleared the next time this function is called.
     if (cacheInvalidatedBySignalHandler_) {
-        i_cache.clear();
+        icache().clear();
         cacheInvalidatedBySignalHandler_ = false;
         return;
     }
 
     if (cache_hit) {
         MOZ_ASSERT(cmpret == 0);
     } else {
         // Cache miss.  Load memory into the cache.
         memcpy(cached_line, line, CachePage::kLineLength);
         *cache_valid_byte = CachePage::LINE_VALID;
     }
 }
 
 HashNumber
-Simulator::ICacheHasher::hash(const Lookup& l)
+SimulatorProcess::ICacheHasher::hash(const Lookup& l)
 {
     return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(l)) >> 2;
 }
 
 bool
-Simulator::ICacheHasher::match(const Key& k, const Lookup& l)
+SimulatorProcess::ICacheHasher::match(const Key& k, const Lookup& l)
 {
     MOZ_ASSERT((reinterpret_cast<intptr_t>(k) & CachePage::kPageMask) == 0);
     MOZ_ASSERT((reinterpret_cast<intptr_t>(l) & CachePage::kPageMask) == 0);
     return k == l;
 }
 
-void
-Simulator::FlushICache(void* start_addr, size_t size)
+/* static */ void
+SimulatorProcess::FlushICache(void* start_addr, size_t size)
 {
-    if (Simulator::ICacheCheckingEnabled) {
-        Simulator* sim = Simulator::Current();
-        AutoLockSimulatorCache als(sim);
-        js::jit::FlushICacheLocked(sim->icache(), start_addr, size);
+    if (!ICacheCheckingDisableCount) {
+        AutoLockSimulatorCache als;
+        js::jit::FlushICacheLocked(icache(), start_addr, size);
     }
 }
 
 Simulator::Simulator()
-  : cacheLock_(mutexid::SimulatorCacheLock),
-    cacheInvalidatedBySignalHandler_(false)
 {
     // Set up simulator support first. Some of this information is needed to
     // setup the architecture state.
 
     // Note, allocation and anything that depends on allocated memory is
     // deferred until init(), in order to handle OOM properly.
 
     stack_ = nullptr;
@@ -1296,26 +1278,21 @@ Simulator::Simulator()
     // access violation if the simulator ever tries to execute it.
     registers_[pc] = bad_ra;
     registers_[ra] = bad_ra;
 
     for (int i = 0; i < kNumExceptions; i++)
         exceptions[i] = 0;
 
     lastDebuggerInput_ = nullptr;
-
-    redirection_ = nullptr;
 }
 
 bool
 Simulator::init()
 {
-    if (!icache_.init())
-        return false;
-
     // Allocate 2MB for the stack. Note that we will only use 1MB, see below.
     static const size_t stackSize = 2 * 1024 * 1024;
     stack_ = static_cast<char*>(js_malloc(stackSize));
     if (!stack_)
         return false;
 
     // Leave a safety margin of 1MB to prevent overrunning the stack when
     // pushing values (total stack size is 2MB).
@@ -1333,56 +1310,56 @@ Simulator::init()
 // the simulator.  The external reference will be a function compiled for the
 // host architecture.  We need to call that function instead of trying to
 // execute it with the simulator.  We do that by redirecting the external
 // reference to a swi (software-interrupt) instruction that is handled by
 // the simulator.  We write the original destination of the jump just at a known
 // offset from the swi instruction so the simulator knows what to call.
 class Redirection
 {
-    friend class Simulator;
+    friend class SimulatorProcess;
 
     // sim's lock must already be held.
-    Redirection(void* nativeFunction, ABIFunctionType type, Simulator* sim)
+    Redirection(void* nativeFunction, ABIFunctionType type)
       : nativeFunction_(nativeFunction),
         swiInstruction_(kCallRedirInstr),
         type_(type),
         next_(nullptr)
     {
-        next_ = sim->redirection();
-	if (Simulator::ICacheCheckingEnabled)
-	    FlushICacheLocked(sim->icache(), addressOfSwiInstruction(), SimInstruction::kInstrSize);
-        sim->setRedirection(this);
+        next_ = SimulatorProcess::redirection();
+	if (!SimulatorProcess::ICacheCheckingDisableCount) {
+	    FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
+                              SimInstruction::kInstrSize);
+        }
+        SimulatorProcess::setRedirection(this);
     }
 
   public:
     void* addressOfSwiInstruction() { return &swiInstruction_; }
     void* nativeFunction() const { return nativeFunction_; }
     ABIFunctionType type() const { return type_; }
 
     static Redirection* Get(void* nativeFunction, ABIFunctionType type) {
-        Simulator* sim = Simulator::Current();
+        AutoLockSimulatorCache als;
 
-        AutoLockSimulatorCache als(sim);
-
-        Redirection* current = sim->redirection();
+        Redirection* current = SimulatorProcess::redirection();
         for (; current != nullptr; current = current->next_) {
             if (current->nativeFunction_ == nativeFunction) {
                 MOZ_ASSERT(current->type() == type);
                 return current;
             }
         }
 
         Redirection* redir = (Redirection*)js_malloc(sizeof(Redirection));
         if (!redir) {
             MOZ_ReportAssertionFailure("[unhandlable oom] Simulator redirection",
                                        __FILE__, __LINE__);
             MOZ_CRASH();
         }
-        new(redir) Redirection(nativeFunction, type, sim);
+        new(redir) Redirection(nativeFunction, type);
         return redir;
     }
 
     static Redirection* FromSwiInstruction(SimInstruction* swiInstruction) {
         uint8_t* addrOfSwi = reinterpret_cast<uint8_t*>(swiInstruction);
         uint8_t* addrOfRedirection = addrOfSwi - offsetof(Redirection, swiInstruction_);
         return reinterpret_cast<Redirection*>(addrOfRedirection);
     }
@@ -1392,36 +1369,56 @@ class Redirection
     uint32_t swiInstruction_;
     ABIFunctionType type_;
     Redirection* next_;
 };
 
 Simulator::~Simulator()
 {
     js_free(stack_);
+}
+
+SimulatorProcess::SimulatorProcess()
+  : cacheLock_(mutexid::SimulatorCacheLock)
+  , redirection_(nullptr)
+{}
+
+SimulatorProcess::~SimulatorProcess()
+{
     Redirection* r = redirection_;
     while (r) {
         Redirection* next = r->next_;
         js_delete(r);
         r = next;
     }
 }
 
+bool
+SimulatorProcess::init()
+{
+    if (getenv("MIPS_SIM_ICACHE_CHECKS"))
+        ICacheCheckingDisableCount = 0;
+
+    return icache_.init();
+}
+
 /* static */ void*
 Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type)
 {
     Redirection* redirection = Redirection::Get(nativeFunction, type);
     return redirection->addressOfSwiInstruction();
 }
 
 // Get the active Simulator for the current thread.
 Simulator*
 Simulator::Current()
 {
-    return TlsContext.get()->runtime()->unsafeContextFromAnyThread()->simulator();
+    JSContext* cx = TlsContext.get();
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
+    return cx->simulator();
 }
 
 // Sets the register in the architecture state. It will also deal with updating
 // Simulator internal state for special registers such as PC.
 void Simulator::setRegister(int reg, int32_t value)
 {
     MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters));
     if (reg == pc) {
@@ -3355,19 +3352,19 @@ Simulator::decodeTypeJump(SimInstruction
     set_pc(next_pc);
     pc_modified_ = true;
 }
 
 // Executes the current instruction.
 void
 Simulator::instructionDecode(SimInstruction* instr)
 {
-    if (Simulator::ICacheCheckingEnabled) {
-        AutoLockSimulatorCache als(this);
-        checkICacheLocked(icache(), instr);
+    if (!SimulatorProcess::ICacheCheckingDisableCount) {
+        AutoLockSimulatorCache als;
+        SimulatorProcess::checkICacheLocked(instr);
     }
     pc_modified_ = false;
 
     switch (instr->instructionType()) {
       case SimInstruction::kRegisterType:
         decodeTypeRegister(instr);
         break;
       case SimInstruction::kImmediateType:
--- a/js/src/jit/mips32/Simulator-mips32.h
+++ b/js/src/jit/mips32/Simulator-mips32.h
@@ -99,20 +99,19 @@ const uint32_t kMaxWatchpointCode = 31;
 const uint32_t kMaxStopCode = 127;
 
 // -----------------------------------------------------------------------------
 // Utility functions
 
 typedef uint32_t Instr;
 class SimInstruction;
 
+// Per thread simulator state.
 class Simulator {
-    friend class Redirection;
     friend class MipsDebugger;
-    friend class AutoLockSimulatorCache;
   public:
 
     // Registers are declared in order. See "See MIPS Run Linux" chapter 2.
     enum Register {
         no_reg = -1,
         zero_reg = 0,
         at,
         v0, v1,
@@ -209,18 +208,16 @@ class Simulator {
     uintptr_t pushAddress(uintptr_t address);
 
     // Pop an address from the JS stack.
     uintptr_t popAddress();
 
     // Debugger input.
     void setLastDebuggerInput(char* input);
     char* lastDebuggerInput() { return lastDebuggerInput_; }
-    // ICache checking.
-    static void FlushICache(void* start, size_t size);
 
     // Returns true if pc register contains one of the 'SpecialValues' defined
     // below (bad_ra, end_sim_pc).
     bool has_bad_pc() const;
 
   private:
     enum SpecialValues {
         // Known bad pc value to ensure that the simulator does not execute
@@ -360,67 +357,89 @@ class Simulator {
     // instruction, if bit 31 of watchedStops_[code].count is unset.
     // The value watchedStops_[code].count & ~(1 << 31) indicates how many times
     // the breakpoint was hit or gone through.
     struct StopCountAndDesc {
         uint32_t count_;
         char* desc_;
     };
     StopCountAndDesc watchedStops_[kNumOfWatchedStops];
+};
+
+// Process wide simulator state.
+class SimulatorProcess
+{
+    friend class Redirection;
+    friend class AutoLockSimulatorCache;
 
   private:
     // ICache checking.
     struct ICacheHasher {
         typedef void* Key;
         typedef void* Lookup;
         static HashNumber hash(const Lookup& l);
         static bool match(const Key& k, const Lookup& l);
     };
 
   public:
     typedef HashMap<void*, CachePage*, ICacheHasher, SystemAllocPolicy> ICacheMap;
 
-  private:
-    // This lock creates a critical section around 'redirection_' and
-    // 'icache_', which are referenced both by the execution engine
-    // and by the off-thread compiler (see Redirection::Get in the cpp file).
-    Mutex cacheLock_;
-#ifdef DEBUG
-    mozilla::Maybe<Thread::Id> cacheLockHolder_;
-#endif
+    static mozilla::Atomic<size_t, mozilla::ReleaseAcquire> ICacheCheckingDisableCount;
+    static void FlushICache(void* start, size_t size);
 
-    Redirection* redirection_;
-    ICacheMap icache_;
-
-  private:
     // Jitcode may be rewritten from a signal handler, but is prevented from
     // calling FlushICache() because the signal may arrive within the critical
     // area of an AutoLockSimulatorCache. This flag instructs the Simulator
     // to remove all cache entries the next time it checks, avoiding false negatives.
-    mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
+    static mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
+
+    static void checkICacheLocked(SimInstruction* instr);
+
+    static bool initialize() {
+        singleton_ = js_new<SimulatorProcess>();
+        return singleton_ && singleton_->init();
+    }
+    static void destroy() {
+        js_delete(singleton_);
+        singleton_ = nullptr;
+    }
 
-    void checkICacheLocked(ICacheMap& i_cache, SimInstruction* instr);
+    SimulatorProcess();
+    ~SimulatorProcess();
+
+  private:
+    bool init();
+
+    static SimulatorProcess* singleton_;
+
+    // This lock creates a critical section around 'redirection_' and
+    // 'icache_', which are referenced both by the execution engine
+    // and by the off-thread compiler (see Redirection::Get in the cpp file).
+    Mutex cacheLock_;
+
+    Redirection* redirection_;
+    ICacheMap icache_;
 
   public:
-    ICacheMap& icache() {
+    static ICacheMap& icache() {
         // Technically we need the lock to access the innards of the
         // icache, not to take its address, but the latter condition
         // serves as a useful complement to the former.
-        MOZ_ASSERT(cacheLockHolder_.isSome());
-        return icache_;
+        MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
+        return singleton_->icache_;
     }
 
-    Redirection* redirection() const {
-        MOZ_ASSERT(cacheLockHolder_.isSome());
-        return redirection_;
+    static Redirection* redirection() {
+        MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
+        return singleton_->redirection_;
     }
 
-    void setRedirection(js::jit::Redirection* redirection) {
-        MOZ_ASSERT(cacheLockHolder_.isSome());
-        redirection_ = redirection;
+    static void setRedirection(js::jit::Redirection* redirection) {
+        MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
+        singleton_->redirection_ = redirection;
     }
 };
 
 #define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror)             \
     JS_BEGIN_MACRO                                                              \
         if (cx->simulator()->overRecursedWithExtra(extra)) {                    \
             js::ReportOverRecursed(cx);                                         \
             onerror;                                                            \
--- a/js/src/jit/mips64/Simulator-mips64.cpp
+++ b/js/src/jit/mips64/Simulator-mips64.cpp
@@ -526,56 +526,41 @@ class CachePage {
 
 // Protects the icache() and redirection() properties of the
 // Simulator.
 class AutoLockSimulatorCache : public LockGuard<Mutex>
 {
     using Base = LockGuard<Mutex>;
 
   public:
-    explicit AutoLockSimulatorCache(Simulator* sim)
-      : Base(sim->cacheLock_)
-      , sim_(sim)
-    {
-        MOZ_ASSERT(sim_->cacheLockHolder_.isNothing());
-#ifdef DEBUG
-        sim_->cacheLockHolder_ = mozilla::Some(ThisThread::GetId());
-#endif
-    }
-
-    ~AutoLockSimulatorCache() {
-        MOZ_ASSERT(sim_->cacheLockHolder_.isSome());
-#ifdef DEBUG
-        sim_->cacheLockHolder_.reset();
-#endif
-    }
-
-  private:
-    Simulator* const sim_;
+    explicit AutoLockSimulatorCache()
+      : Base(SimulatorProcess::singleton_->cacheLock_)
+    {}
 };
 
-bool Simulator::ICacheCheckingEnabled = false;
+mozilla::Atomic<size_t, mozilla::ReleaseAcquire>
+    SimulatorProcess::ICacheCheckingDisableCount(1);  // Checking is disabled by default.
+mozilla::Atomic<bool, mozilla::ReleaseAcquire>
+    SimulatorProcess::cacheInvalidatedBySignalHandler_(false);
+SimulatorProcess* SimulatorProcess::singleton_ = nullptr;
 
 int64_t Simulator::StopSimAt = -1;
 
 Simulator *
 Simulator::Create(JSContext* cx)
 {
     Simulator* sim = js_new<Simulator>();
     if (!sim)
         return nullptr;
 
     if (!sim->init()) {
         js_delete(sim);
         return nullptr;
     }
 
-    if (getenv("MIPS_SIM_ICACHE_CHECKS"))
-        Simulator::ICacheCheckingEnabled = true;
-
     int64_t stopAt;
     char* stopAtStr = getenv("MIPS_SIM_STOP_AT");
     if (stopAtStr && sscanf(stopAtStr, "%" PRIi64, &stopAt) == 1) {
         fprintf(stderr, "\nStopping simulation at icount %" PRIi64 "\n", stopAt);
         Simulator::StopSimAt = stopAt;
     }
 
     return sim;
@@ -1154,45 +1139,45 @@ AllOnOnePage(uintptr_t start, int size)
 void
 Simulator::setLastDebuggerInput(char* input)
 {
     js_free(lastDebuggerInput_);
     lastDebuggerInput_ = input;
 }
 
 static CachePage*
-GetCachePageLocked(Simulator::ICacheMap& i_cache, void* page)
+GetCachePageLocked(SimulatorProcess::ICacheMap& i_cache, void* page)
 {
-    Simulator::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
+    SimulatorProcess::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
     if (p)
         return p->value();
 
     CachePage* new_page = js_new<CachePage>();
     if (!i_cache.add(p, page, new_page))
         return nullptr;
     return new_page;
 }
 
 // Flush from start up to and not including start + size.
 static void
-FlushOnePageLocked(Simulator::ICacheMap& i_cache, intptr_t start, int size)
+FlushOnePageLocked(SimulatorProcess::ICacheMap& i_cache, intptr_t start, int size)
 {
     MOZ_ASSERT(size <= CachePage::kPageSize);
     MOZ_ASSERT(AllOnOnePage(start, size - 1));
     MOZ_ASSERT((start & CachePage::kLineMask) == 0);
     MOZ_ASSERT((size & CachePage::kLineMask) == 0);
     void* page = reinterpret_cast<void*>(start & (~CachePage::kPageMask));
     int offset = (start & CachePage::kPageMask);
     CachePage* cache_page = GetCachePageLocked(i_cache, page);
     char* valid_bytemap = cache_page->validityByte(offset);
     memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift);
 }
 
 static void
-FlushICacheLocked(Simulator::ICacheMap& i_cache, void* start_addr, size_t size)
+FlushICacheLocked(SimulatorProcess::ICacheMap& i_cache, void* start_addr, size_t size)
 {
     intptr_t start = reinterpret_cast<intptr_t>(start_addr);
     int intra_line = (start & CachePage::kLineMask);
     start -= intra_line;
     size += intra_line;
     size = ((size - 1) | CachePage::kLineMask) + 1;
     int offset = (start & CachePage::kPageMask);
     while (!AllOnOnePage(start, size - 1)) {
@@ -1202,24 +1187,24 @@ FlushICacheLocked(Simulator::ICacheMap& 
         size -= bytes_to_flush;
         MOZ_ASSERT((start & CachePage::kPageMask) == 0);
         offset = 0;
     }
     if (size != 0)
         FlushOnePageLocked(i_cache, start, size);
 }
 
-void
-Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* instr)
+/* static */ void
+SimulatorProcess::checkICacheLocked(SimInstruction* instr)
 {
     intptr_t address = reinterpret_cast<intptr_t>(instr);
     void* page = reinterpret_cast<void*>(address & (~CachePage::kPageMask));
     void* line = reinterpret_cast<void*>(address & (~CachePage::kLineMask));
     int offset = (address & CachePage::kPageMask);
-    CachePage* cache_page = GetCachePageLocked(i_cache, page);
+    CachePage* cache_page = GetCachePageLocked(icache(), page);
     char* cache_valid_byte = cache_page->validityByte(offset);
     bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID);
     char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask);
 
     // Read all state before considering signal handler effects.
     int cmpret = 0;
     if (cache_hit) {
         // Check that the data in memory matches the contents of the I-cache.
@@ -1227,57 +1212,54 @@ Simulator::checkICacheLocked(Simulator::
                         cache_page->cachedData(offset),
                         SimInstruction::kInstrSize);
     }
 
     // Check for signal handler interruption between reading state and asserting.
     // It is safe for the signal to arrive during the !cache_hit path, since it
     // will be cleared the next time this function is called.
     if (cacheInvalidatedBySignalHandler_) {
-        i_cache.clear();
+        icache().clear();
         cacheInvalidatedBySignalHandler_ = false;
         return;
     }
 
     if (cache_hit) {
         MOZ_ASSERT(cmpret == 0);
     } else {
         // Cache miss.  Load memory into the cache.
         memcpy(cached_line, line, CachePage::kLineLength);
         *cache_valid_byte = CachePage::LINE_VALID;
     }
 }
 
 HashNumber
-Simulator::ICacheHasher::hash(const Lookup& l)
+SimulatorProcess::ICacheHasher::hash(const Lookup& l)
 {
     return U32(reinterpret_cast<uintptr_t>(l)) >> 2;
 }
 
 bool
-Simulator::ICacheHasher::match(const Key& k, const Lookup& l)
+SimulatorProcess::ICacheHasher::match(const Key& k, const Lookup& l)
 {
     MOZ_ASSERT((reinterpret_cast<intptr_t>(k) & CachePage::kPageMask) == 0);
     MOZ_ASSERT((reinterpret_cast<intptr_t>(l) & CachePage::kPageMask) == 0);
     return k == l;
 }
 
-void
-Simulator::FlushICache(void* start_addr, size_t size)
+/* static */ void
+SimulatorProcess::FlushICache(void* start_addr, size_t size)
 {
-    if (Simulator::ICacheCheckingEnabled) {
-        Simulator* sim = Simulator::Current();
-        AutoLockSimulatorCache als(sim);
-        js::jit::FlushICacheLocked(sim->icache(), start_addr, size);
+    if (!ICacheCheckingDisableCount) {
+        AutoLockSimulatorCache als;
+        js::jit::FlushICacheLocked(icache(), start_addr, size);
     }
 }
 
 Simulator::Simulator()
-  : cacheLock_(mutexid::SimulatorCacheLock),
-    cacheInvalidatedBySignalHandler_(false)
 {
     // Set up simulator support first. Some of this information is needed to
     // setup the architecture state.
 
     // Note, allocation and anything that depends on allocated memory is
     // deferred until init(), in order to handle OOM properly.
 
     stack_ = nullptr;
@@ -1304,26 +1286,21 @@ Simulator::Simulator()
     // access violation if the simulator ever tries to execute it.
     registers_[pc] = bad_ra;
     registers_[ra] = bad_ra;
 
     for (int i = 0; i < kNumExceptions; i++)
         exceptions[i] = 0;
 
     lastDebuggerInput_ = nullptr;
-
-    redirection_ = nullptr;
 }
 
 bool
 Simulator::init()
 {
-    if (!icache_.init())
-        return false;
-
     // Allocate 2MB for the stack. Note that we will only use 1MB, see below.
     static const size_t stackSize = 2 * 1024 * 1024;
     stack_ = static_cast<char*>(js_malloc(stackSize));
     if (!stack_)
         return false;
 
     // Leave a safety margin of 1MB to prevent overrunning the stack when
     // pushing values (total stack size is 2MB).
@@ -1341,56 +1318,56 @@ Simulator::init()
 // the simulator.  The external reference will be a function compiled for the
 // host architecture.  We need to call that function instead of trying to
 // execute it with the simulator.  We do that by redirecting the external
 // reference to a swi (software-interrupt) instruction that is handled by
 // the simulator.  We write the original destination of the jump just at a known
 // offset from the swi instruction so the simulator knows what to call.
 class Redirection
 {
-    friend class Simulator;
+    friend class SimulatorProcess;
 
     // sim's lock must already be held.
-    Redirection(void* nativeFunction, ABIFunctionType type, Simulator* sim)
+    Redirection(void* nativeFunction, ABIFunctionType type)
       : nativeFunction_(nativeFunction),
         swiInstruction_(kCallRedirInstr),
         type_(type),
         next_(nullptr)
     {
-        next_ = sim->redirection();
-        if (Simulator::ICacheCheckingEnabled)
-	    FlushICacheLocked(sim->icache(), addressOfSwiInstruction(), SimInstruction::kInstrSize);
-        sim->setRedirection(this);
+        next_ = SimulatorProcess::redirection();
+        if (!SimulatorProcess::ICacheCheckingDisableCount) {
+            FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
+                              SimInstruction::kInstrSize);
+        }
+        SimulatorProcess::setRedirection(this);
     }
 
   public:
     void* addressOfSwiInstruction() { return &swiInstruction_; }
     void* nativeFunction() const { return nativeFunction_; }
     ABIFunctionType type() const { return type_; }
 
     static Redirection* Get(void* nativeFunction, ABIFunctionType type) {
-        Simulator* sim = Simulator::Current();
-
-        AutoLockSimulatorCache als(sim);
-
-        Redirection* current = sim->redirection();
+        AutoLockSimulatorCache als;
+
+        Redirection* current = SimulatorProcess::redirection();
         for (; current != nullptr; current = current->next_) {
             if (current->nativeFunction_ == nativeFunction) {
                 MOZ_ASSERT(current->type() == type);
                 return current;
             }
         }
 
         Redirection* redir = (Redirection*)js_malloc(sizeof(Redirection));
         if (!redir) {
             MOZ_ReportAssertionFailure("[unhandlable oom] Simulator redirection",
                                        __FILE__, __LINE__);
             MOZ_CRASH();
         }
-        new(redir) Redirection(nativeFunction, type, sim);
+        new(redir) Redirection(nativeFunction, type);
         return redir;
     }
 
     static Redirection* FromSwiInstruction(SimInstruction* swiInstruction) {
         uint8_t* addrOfSwi = reinterpret_cast<uint8_t*>(swiInstruction);
         uint8_t* addrOfRedirection = addrOfSwi - offsetof(Redirection, swiInstruction_);
         return reinterpret_cast<Redirection*>(addrOfRedirection);
     }
@@ -1400,36 +1377,56 @@ class Redirection
     uint32_t swiInstruction_;
     ABIFunctionType type_;
     Redirection* next_;
 };
 
 Simulator::~Simulator()
 {
     js_free(stack_);
+}
+
+SimulatorProcess::SimulatorProcess()
+  : cacheLock_(mutexid::SimulatorCacheLock)
+  , redirection_(nullptr)
+{}
+
+SimulatorProcess::~SimulatorProcess()
+{
     Redirection* r = redirection_;
     while (r) {
         Redirection* next = r->next_;
         js_delete(r);
         r = next;
     }
 }
 
+bool
+SimulatorProcess::init()
+{
+    if (getenv("MIPS_SIM_ICACHE_CHECKS"))
+        ICacheCheckingDisableCount = 0;
+
+    return icache_.init();
+}
+
 /* static */ void*
 Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type)
 {
     Redirection* redirection = Redirection::Get(nativeFunction, type);
     return redirection->addressOfSwiInstruction();
 }
 
 // Get the active Simulator for the current thread.
 Simulator*
 Simulator::Current()
 {
-    return TlsContext.get()->runtime()->unsafeContextFromAnyThread()->simulator();
+    JSContext* cx = TlsContext.get();
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
+    return cx->simulator();
 }
 
 // Sets the register in the architecture state. It will also deal with updating
 // Simulator internal state for special registers such as PC.
 void
 Simulator::setRegister(int reg, int64_t value)
 {
     MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters));
@@ -3649,19 +3646,19 @@ Simulator::decodeTypeJump(SimInstruction
     set_pc(next_pc);
     pc_modified_ = true;
 }
 
 // Executes the current instruction.
 void
 Simulator::instructionDecode(SimInstruction* instr)
 {
-    if (Simulator::ICacheCheckingEnabled) {
-        AutoLockSimulatorCache als(this);
-        checkICacheLocked(icache(), instr);
+    if (!SimulatorProcess::ICacheCheckingDisableCount) {
+        AutoLockSimulatorCache als;
+        SimulatorProcess::checkICacheLocked(instr);
     }
     pc_modified_ = false;
 
     switch (instr->instructionType()) {
       case SimInstruction::kRegisterType:
         decodeTypeRegister(instr);
         break;
       case SimInstruction::kImmediateType:
--- a/js/src/jit/mips64/Simulator-mips64.h
+++ b/js/src/jit/mips64/Simulator-mips64.h
@@ -105,20 +105,19 @@ const uint32_t kMaxWatchpointCode = 31;
 const uint32_t kMaxStopCode = 127;
 
 // -----------------------------------------------------------------------------
 // Utility functions
 
 typedef uint32_t Instr;
 class SimInstruction;
 
+// Per thread simulator state.
 class Simulator {
-    friend class Redirection;
     friend class MipsDebugger;
-    friend class AutoLockSimulatorCache;
   public:
 
     // Registers are declared in order. See "See MIPS Run Linux" chapter 2.
     enum Register {
         no_reg = -1,
         zero_reg = 0,
         at,
         v0, v1,
@@ -217,18 +216,16 @@ class Simulator {
     uintptr_t pushAddress(uintptr_t address);
 
     // Pop an address from the JS stack.
     uintptr_t popAddress();
 
     // Debugger input.
     void setLastDebuggerInput(char* input);
     char* lastDebuggerInput() { return lastDebuggerInput_; }
-    // ICache checking.
-    static void FlushICache(void* start, size_t size);
 
     // Returns true if pc register contains one of the 'SpecialValues' defined
     // below (bad_ra, end_sim_pc).
     bool has_bad_pc() const;
 
   private:
     enum SpecialValues {
         // Known bad pc value to ensure that the simulator does not execute
@@ -302,18 +299,16 @@ class Simulator {
 
 
     // Executes one instruction.
     void instructionDecode(SimInstruction* instr);
     // Execute one instruction placed in a branch delay slot.
     void branchDelayInstructionDecode(SimInstruction* instr);
 
   public:
-    static bool ICacheCheckingEnabled;
-
     static int64_t StopSimAt;
 
     // Runtime call support.
     static void* RedirectNativeFunction(void* nativeFunction, ABIFunctionType type);
 
   private:
     enum Exception {
         kNone,
@@ -376,71 +371,93 @@ class Simulator {
     // instruction, if bit 31 of watchedStops_[code].count is unset.
     // The value watchedStops_[code].count & ~(1 << 31) indicates how many times
     // the breakpoint was hit or gone through.
     struct StopCountAndDesc {
         uint32_t count_;
         char* desc_;
     };
     StopCountAndDesc watchedStops_[kNumOfWatchedStops];
+};
+
+// Process wide simulator state.
+class SimulatorProcess
+{
+    friend class Redirection;
+    friend class AutoLockSimulatorCache;
 
   private:
     // ICache checking.
     struct ICacheHasher {
         typedef void* Key;
         typedef void* Lookup;
         static HashNumber hash(const Lookup& l);
         static bool match(const Key& k, const Lookup& l);
     };
 
   public:
     typedef HashMap<void*, CachePage*, ICacheHasher, SystemAllocPolicy> ICacheMap;
 
-  private:
-    // This lock creates a critical section around 'redirection_' and
-    // 'icache_', which are referenced both by the execution engine
-    // and by the off-thread compiler (see Redirection::Get in the cpp file).
-    Mutex cacheLock_;
-#ifdef DEBUG
-    mozilla::Maybe<Thread::Id> cacheLockHolder_;
-#endif
+    static mozilla::Atomic<size_t, mozilla::ReleaseAcquire> ICacheCheckingDisableCount;
+    static void FlushICache(void* start, size_t size);
 
-    Redirection* redirection_;
-    ICacheMap icache_;
-
-  private:
     // Jitcode may be rewritten from a signal handler, but is prevented from
     // calling FlushICache() because the signal may arrive within the critical
     // area of an AutoLockSimulatorCache. This flag instructs the Simulator
     // to remove all cache entries the next time it checks, avoiding false negatives.
-    mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
+    static mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
+
+    static void checkICacheLocked(SimInstruction* instr);
+
+    static bool initialize() {
+        singleton_ = js_new<SimulatorProcess>();
+        return singleton_ && singleton_->init();
+    }
+    static void destroy() {
+        js_delete(singleton_);
+        singleton_ = nullptr;
+    }
 
-    void checkICacheLocked(ICacheMap& i_cache, SimInstruction* instr);
+    SimulatorProcess();
+    ~SimulatorProcess();
+
+  private:
+    bool init();
+
+    static SimulatorProcess* singleton_;
+
+    // This lock creates a critical section around 'redirection_' and
+    // 'icache_', which are referenced both by the execution engine
+    // and by the off-thread compiler (see Redirection::Get in the cpp file).
+    Mutex cacheLock_;
+
+    Redirection* redirection_;
+    ICacheMap icache_;
 
   public:
-    ICacheMap& icache() {
+    static ICacheMap& icache() {
         // Technically we need the lock to access the innards of the
         // icache, not to take its address, but the latter condition
         // serves as a useful complement to the former.
-        MOZ_ASSERT(cacheLockHolder_.isSome());
-        return icache_;
+        MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
+        return singleton_->icache_;
     }
 
-    Redirection* redirection() const {
-        MOZ_ASSERT(cacheLockHolder_.isSome());
-        return redirection_;
+    static Redirection* redirection() {
+        MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
+        return singleton_->redirection_;
     }
 
-    void setRedirection(js::jit::Redirection* redirection) {
-        MOZ_ASSERT(cacheLockHolder_.isSome());
-        redirection_ = redirection;
+    static void setRedirection(js::jit::Redirection* redirection) {
+        MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
+        singleton_->redirection_ = redirection;
     }
 };
 
-#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror)             \
+#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror)     \
     JS_BEGIN_MACRO                                                              \
         if (cx->simulator()->overRecursedWithExtra(extra)) {                    \
             js::ReportOverRecursed(cx);                                         \
             onerror;                                                            \
         }                                                                       \
     JS_END_MACRO
 
 } // namespace jit
--- a/js/src/jsapi-tests/testGCHeapPostBarriers.cpp
+++ b/js/src/jsapi-tests/testGCHeapPostBarriers.cpp
@@ -23,17 +23,17 @@ struct TestStruct
 };
 
 // A specialized version for GCPtr that adds a zone() method.
 template <typename T>
 struct TestStruct<js::GCPtr<T>>
 {
     js::GCPtr<T> wrapper;
 
-    JS::Zone* zone() { return wrapper->zone(); }
+    JS::Zone* zone() const { return wrapper->zone(); }
 };
 
 // Give the GCPtr version GCManagedDeletePolicy as required.
 namespace JS {
 template <typename T>
 struct DeletePolicy<TestStruct<js::GCPtr<T>>>
     : public js::GCManagedDeletePolicy<TestStruct<js::GCPtr<T>>>
 {};
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4118,17 +4118,17 @@ JS::FinishOffThreadScript(JSContext* cx,
     return HelperThreadState().finishScriptParseTask(cx, token);
 }
 
 JS_PUBLIC_API(void)
 JS::CancelOffThreadScript(JSContext* cx, void* token)
 {
     MOZ_ASSERT(cx);
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
-    HelperThreadState().cancelParseTask(cx, ParseTaskKind::Script, token);
+    HelperThreadState().cancelParseTask(cx->runtime(), ParseTaskKind::Script, token);
 }
 
 JS_PUBLIC_API(bool)
 JS::CompileOffThreadModule(JSContext* cx, const ReadOnlyCompileOptions& options,
                            const char16_t* chars, size_t length,
                            OffThreadCompileCallback callback, void* callbackData)
 {
     MOZ_ASSERT(CanCompileOffThread(cx, options, length));
@@ -4143,17 +4143,17 @@ JS::FinishOffThreadModule(JSContext* cx,
     return HelperThreadState().finishModuleParseTask(cx, token);
 }
 
 JS_PUBLIC_API(void)
 JS::CancelOffThreadModule(JSContext* cx, void* token)
 {
     MOZ_ASSERT(cx);
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
-    HelperThreadState().cancelParseTask(cx, ParseTaskKind::Module, token);
+    HelperThreadState().cancelParseTask(cx->runtime(), ParseTaskKind::Module, token);
 }
 
 JS_PUBLIC_API(bool)
 JS::DecodeOffThreadScript(JSContext* cx, const ReadOnlyCompileOptions& options,
                           mozilla::Vector<uint8_t>& buffer /* TranscodeBuffer& */, size_t cursor,
                           OffThreadCompileCallback callback, void* callbackData)
 {
     MOZ_ASSERT(CanCompileOffThread(cx, options, buffer.length() - cursor));
@@ -4168,17 +4168,17 @@ JS::FinishOffThreadScriptDecoder(JSConte
     return HelperThreadState().finishScriptDecodeTask(cx, token);
 }
 
 JS_PUBLIC_API(void)
 JS::CancelOffThreadScriptDecoder(JSContext* cx, void* token)
 {
     MOZ_ASSERT(cx);
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
-    HelperThreadState().cancelParseTask(cx, ParseTaskKind::ScriptDecode, token);
+    HelperThreadState().cancelParseTask(cx->runtime(), ParseTaskKind::ScriptDecode, token);
 }
 
 JS_PUBLIC_API(bool)
 JS_CompileScript(JSContext* cx, const char* ascii, size_t length,
                  const JS::CompileOptions& options, MutableHandleScript script)
 {
     return Compile(cx, options, ascii, length, script);
 }
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -881,16 +881,23 @@ JSContext::boolToResult(bool ok)
     if (MOZ_LIKELY(ok)) {
         MOZ_ASSERT(!isExceptionPending());
         MOZ_ASSERT(!isPropagatingForcedReturn());
         return JS::Ok();
     }
     return JS::Result<>(reportedError);
 }
 
+inline JSContext*
+JSRuntime::activeContextFromOwnThread()
+{
+    MOZ_ASSERT(activeContext() == js::TlsContext.get());
+    return activeContext();
+}
+
 namespace js {
 
 struct MOZ_RAII AutoResolving {
   public:
     enum Kind {
         LOOKUP,
         WATCH
     };
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -480,17 +480,17 @@ inline void
 JSContext::setCompartment(JSCompartment* comp,
                           const js::AutoLockForExclusiveAccess* maybeLock /* = nullptr */)
 {
     // Contexts operating on helper threads can only be in the atoms zone or in exclusive zones.
     MOZ_ASSERT_IF(helperThread() && !runtime_->isAtomsCompartment(comp),
                   comp->zone()->usedByExclusiveThread);
 
     // Normal JSContexts cannot enter exclusive zones.
-    MOZ_ASSERT_IF(this == runtime()->unsafeContextFromAnyThread() && comp,
+    MOZ_ASSERT_IF(this == runtime()->activeContext() && comp,
                   !comp->zone()->usedByExclusiveThread);
 
     // Only one thread can be in the atoms compartment at a time.
     MOZ_ASSERT_IF(runtime_->isAtomsCompartment(comp), maybeLock != nullptr);
 
     // Make sure that the atoms compartment has its own zone.
     MOZ_ASSERT_IF(comp && !runtime_->isAtomsCompartment(comp),
                   !comp->zone()->isAtomsZone());
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -1052,25 +1052,16 @@ AddLazyFunctionsForCompartment(JSContext
         // are about to be finalized. GC things referenced by objects that are
         // about to be finalized (e.g., in slots) may already be freed.
         if (gc::IsAboutToBeFinalizedUnbarriered(&fun) ||
             fun->compartment() != cx->compartment())
         {
             continue;
         }
 
-        // This creates a new reference to an object that an ongoing incremental
-        // GC may find to be unreachable. Treat as if we're reading a weak
-        // reference and trigger the read barrier.
-        if (cx->zone()->needsIncrementalBarrier())
-            fun->readBarrier(fun);
-
-        // TODO: The above checks should be rolled into the cell iterator (see
-        // bug 1322971).
-
         if (fun->isInterpretedLazy()) {
             LazyScript* lazy = fun->lazyScriptOrNull();
             if (lazy && lazy->sourceObject() && !lazy->hasUncompiledEnclosingScript()) {
                 if (!lazyFunctions.append(fun))
                     return false;
             }
         }
     }
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1159,18 +1159,20 @@ DumpHeapTracer::onChild(const JS::GCCell
     char buffer[1024];
     getTracingEdgeName(buffer, sizeof(buffer));
     fprintf(output, "%s%p %c %s\n", prefix, thing.asCell(), MarkDescriptor(thing.asCell()), buffer);
 }
 
 void
 js::DumpHeap(JSContext* cx, FILE* fp, js::DumpHeapNurseryBehaviour nurseryBehaviour)
 {
-    if (nurseryBehaviour == js::CollectNurseryBeforeDump)
-        cx->runtime()->zoneGroupFromMainThread()->evictNursery(JS::gcreason::API);
+    if (nurseryBehaviour == js::CollectNurseryBeforeDump) {
+        for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next())
+            group->evictNursery(JS::gcreason::API);
+    }
 
     DumpHeapTracer dtrc(fp, cx);
 
     fprintf(dtrc.output, "# Roots.\n");
     {
         JSRuntime* rt = cx->runtime();
         js::gc::AutoPrepareForTracing prep(cx, WithAtoms);
         gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_TRACE_HEAP);
@@ -1217,17 +1219,17 @@ js::GetAnyCompartmentInZone(JS::Zone* zo
     CompartmentsInZoneIter comp(zone);
     MOZ_ASSERT(!comp.done());
     return comp.get();
 }
 
 void
 JS::ObjectPtr::finalize(JSRuntime* rt)
 {
-    if (IsIncrementalBarrierNeeded(rt->contextFromMainThread()))
+    if (IsIncrementalBarrierNeeded(rt->activeContextFromOwnThread()))
         IncrementalObjectBarrier(value);
     value = nullptr;
 }
 
 void
 JS::ObjectPtr::finalize(JSContext* cx)
 {
     finalize(cx->runtime());
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -238,16 +238,17 @@
 #include "vm/Time.h"
 #include "vm/TraceLogging.h"
 #include "vm/WrapperObject.h"
 
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "gc/Heap-inl.h"
+#include "gc/Nursery-inl.h"
 #include "vm/GeckoProfiler-inl.h"
 #include "vm/Stack-inl.h"
 #include "vm/String-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::ArrayLength;
@@ -821,17 +822,16 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
     chunkAllocationSinceLastGC(false),
     lastGCTime(PRMJ_Now()),
     mode(JSGC_MODE_INCREMENTAL),
     numActiveZoneIters(0),
     cleanUpEverything(false),
     grayBufferState(GCRuntime::GrayBufferState::Unused),
     grayBitsValid(false),
     majorGCTriggerReason(JS::gcreason::NO_REASON),
-    minorGCTriggerReason(JS::gcreason::NO_REASON),
     fullGCForAtomsRequested_(false),
     minorGCNumber(0),
     majorGCNumber(0),
     jitReleaseNumber(0),
     number(0),
     startNumber(0),
     isFull(false),
     incrementalState(gc::State::NotActive),
@@ -915,23 +915,27 @@ void
 GCRuntime::setZeal(uint8_t zeal, uint32_t frequency)
 {
     MOZ_ASSERT(zeal <= unsigned(ZealMode::Limit));
 
     if (verifyPreData)
         VerifyBarriers(rt, PreBarrierVerifier);
 
     if (zeal == 0 && hasZealMode(ZealMode::GenerationalGC)) {
-        rt->zoneGroupFromMainThread()->evictNursery(JS::gcreason::DEBUG_GC);
-        rt->zoneGroupFromMainThread()->nursery().leaveZealMode();
+        for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
+            group->evictNursery(JS::gcreason::DEBUG_GC);
+            group->nursery().leaveZealMode();
+        }
     }
 
     ZealMode zealMode = ZealMode(zeal);
-    if (zealMode == ZealMode::GenerationalGC)
-        rt->zoneGroupFromMainThread()->nursery().enterZealMode();
+    if (zealMode == ZealMode::GenerationalGC) {
+        for (ZoneGroupsIter group(rt); !group.done(); group.next())
+            group->nursery().enterZealMode();
+    }
 
     // Zeal modes 8-10 are mutually exclusive. If we're setting one of those,
     // we first reset all of them.
     if (zealMode >= ZealMode::IncrementalRootsThenFinish &&
         zealMode <= ZealMode::IncrementalMultipleSlices)
     {
         clearZealMode(ZealMode::IncrementalRootsThenFinish);
         clearZealMode(ZealMode::IncrementalMarkAllThenFinish);
@@ -1063,18 +1067,20 @@ GCRuntime::init(uint32_t maxbytes, uint3
 
     return true;
 }
 
 void
 GCRuntime::finish()
 {
     /* Wait for the nursery sweeping to end. */
-    if (rt->zoneGroupFromMainThread()->nursery().isEnabled())
-        rt->zoneGroupFromMainThread()->nursery().waitBackgroundFreeEnd();
+    for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
+        if (group->nursery().isEnabled())
+            group->nursery().waitBackgroundFreeEnd();
+    }
 
     /*
      * Wait until the background finalization and allocation stops and the
      * helper thread shuts down before we forcefully release any remaining GC
      * memory.
      */
     helperState.finish();
     allocTask.cancel(GCParallelTask::CancelAndWait);
@@ -1098,17 +1104,18 @@ GCRuntime::finish()
     groups.ref().clear();
 
     FreeChunkPool(rt, fullChunks_.ref());
     FreeChunkPool(rt, availableChunks_.ref());
     FreeChunkPool(rt, emptyChunks_.ref());
 
     FinishTrace();
 
-    rt->zoneGroupFromMainThread()->nursery().printTotalProfileTimes();
+    for (ZoneGroupsIter group(rt); !group.done(); group.next())
+        group->nursery().printTotalProfileTimes();
     stats().printTotalProfileTimes();
 }
 
 bool
 GCRuntime::setParameter(JSGCParamKey key, uint32_t value, AutoLockGC& lock)
 {
     switch (key) {
       case JSGC_MAX_MALLOC_BYTES:
@@ -2544,17 +2551,17 @@ GCRuntime::updateRuntimePointersToReloca
 
     rt->geckoProfiler().fixupStringsMapAfterMovingGC();
 
     traceRuntimeForMajorGC(&trc, lock);
 
     // Mark roots to update them.
     {
         gcstats::AutoPhase ap2(stats(), gcstats::PHASE_MARK_ROOTS);
-        Debugger::traceAll(&trc);
+        Debugger::traceAllForMovingGC(&trc);
         Debugger::traceIncomingCrossCompartmentEdges(&trc);
 
         // Mark all gray roots, making sure we call the trace callback to get the
         // current set.
         if (JSTraceDataOp op = grayRootTracer.op)
             (*op)(&trc, grayRootTracer.data);
     }
 
@@ -2946,25 +2953,25 @@ GCRuntime::requestMajorGC(JS::gcreason::
 
     // There's no need to use RequestInterruptUrgent here. It's slower because
     // it has to interrupt (looping) Ion code, but loops in Ion code that
     // affect GC will have an explicit interrupt check.
     TlsContext.get()->requestInterrupt(JSContext::RequestInterruptCanWait);
 }
 
 void
-GCRuntime::requestMinorGC(JS::gcreason::Reason reason)
-{
-    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
+Nursery::requestMinorGC(JS::gcreason::Reason reason) const
+{
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(zoneGroup()->runtime));
     MOZ_ASSERT(!CurrentThreadIsPerformingGC());
 
     if (minorGCRequested())
         return;
 
-    minorGCTriggerReason = reason;
+    minorGCTriggerReason_ = reason;
 
     // See comment in requestMajorGC.
     TlsContext.get()->requestInterrupt(JSContext::RequestInterruptCanWait);
 }
 
 bool
 GCRuntime::triggerGC(JS::gcreason::Reason reason)
 {
@@ -2974,17 +2981,17 @@ GCRuntime::triggerGC(JS::gcreason::Reaso
      */
     if (!CurrentThreadCanAccessRuntime(rt))
         return false;
 
     /* GC is already running. */
     if (JS::CurrentThreadIsHeapCollecting())
         return false;
 
-    JS::PrepareForFullGC(rt->contextFromMainThread());
+    JS::PrepareForFullGC(rt->activeContextFromOwnThread());
     requestMajorGC(reason);
     return true;
 }
 
 void
 GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, const AutoLockGC& lock)
 {
     size_t usedBytes = zone->usage.gcBytes();
@@ -3055,17 +3062,17 @@ GCRuntime::triggerZoneGC(Zone* zone, JS:
 
 void
 GCRuntime::maybeGC(Zone* zone)
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
 #ifdef JS_GC_ZEAL
     if (hasZealMode(ZealMode::Alloc) || hasZealMode(ZealMode::Poke)) {
-        JS::PrepareForFullGC(rt->contextFromMainThread());
+        JS::PrepareForFullGC(rt->activeContextFromOwnThread());
         gc(GC_NORMAL, JS::gcreason::DEBUG_GC);
         return;
     }
 #endif
 
     if (gcIfRequested())
         return;
 
@@ -3473,30 +3480,30 @@ Zone::sweepCompartments(FreeOp* fop, boo
             foundOne = true;
         }
     }
     compartments().shrinkTo(write - compartments().begin());
     MOZ_ASSERT_IF(keepAtleastOne, !compartments().empty());
 }
 
 void
-GCRuntime::sweepZones(FreeOp* fop, bool destroyingRuntime)
+GCRuntime::sweepZones(FreeOp* fop, ZoneGroup* group, bool destroyingRuntime)
 {
     MOZ_ASSERT_IF(destroyingRuntime, numActiveZoneIters == 0);
     MOZ_ASSERT_IF(destroyingRuntime, arenasEmptyAtShutdown);
 
     if (rt->gc.numActiveZoneIters)
         return;
 
     assertBackgroundSweepingFinished();
 
     JSZoneCallback callback = rt->destroyZoneCallback;
 
-    Zone** read = rt->zoneGroupFromMainThread()->zones().begin();
-    Zone** end = rt->zoneGroupFromMainThread()->zones().end();
+    Zone** read = group->zones().begin();
+    Zone** end = group->zones().end();
     Zone** write = read;
 
     while (read < end) {
         Zone* zone = *read++;
 
         if (zone->wasGCStarted()) {
             MOZ_ASSERT(!zone->isQueuedForBackgroundSweep());
             const bool zoneIsDead = zone->arenas.arenaListsAreEmpty() &&
@@ -3524,17 +3531,40 @@ GCRuntime::sweepZones(FreeOp* fop, bool 
                 fop->delete_(zone);
                 stats().sweptZone();
                 continue;
             }
             zone->sweepCompartments(fop, true, destroyingRuntime);
         }
         *write++ = zone;
     }
-    rt->zoneGroupFromMainThread()->zones().shrinkTo(write - rt->zoneGroupFromMainThread()->zones().begin());
+    group->zones().shrinkTo(write - group->zones().begin());
+}
+
+void
+GCRuntime::sweepZoneGroups(FreeOp* fop, bool destroyingRuntime)
+{
+    ZoneGroup** read = groups.ref().begin();
+    ZoneGroup** end = groups.ref().end();
+    ZoneGroup** write = read;
+
+    while (read < end) {
+        ZoneGroup* group = *read++;
+        sweepZones(fop, group, destroyingRuntime);
+
+        // For now, the singleton zone group is not destroyed until the runtime
+        // itself is, bug 1323066.
+        if (group->zones().empty() && group != rt->zoneGroupFromMainThread()) {
+            MOZ_ASSERT(numActiveZoneIters == 0);
+            fop->delete_(group);
+        } else {
+            *write++ = group;
+        }
+    }
+    groups.ref().shrinkTo(write - groups.ref().begin());
 }
 
 #ifdef DEBUG
 static const char*
 AllocKindToAscii(AllocKind kind)
 {
     switch(kind) {
 #define MAKE_CASE(allocKind, traceKind, type, sizedType) \
@@ -3555,18 +3585,18 @@ ArenaLists::checkEmptyArenaList(AllocKin
 #ifdef DEBUG
     if (!arenaLists(kind).isEmpty()) {
         size_t max_cells = 20;
         char *env = getenv("JS_GC_MAX_LIVE_CELLS");
         if (env && *env)
             max_cells = atol(env);
         for (Arena* current = arenaLists(kind).head(); current; current = current->next) {
             for (ArenaCellIterUnderGC i(current); !i.done(); i.next()) {
-                Cell* t = i.get<Cell>();
-                MOZ_ASSERT(t->asTenured().isMarked(), "unmarked cells should have been finalized");
+                TenuredCell* t = i.getCell();
+                MOZ_ASSERT(t->isMarked(), "unmarked cells should have been finalized");
                 if (++num_live <= max_cells) {
                     fprintf(stderr, "ERROR: GC found live Cell %p of kind %s at shutdown\n",
                             t, AllocKindToAscii(kind));
                 }
             }
         }
         fprintf(stderr, "ERROR: GC found %" PRIuSIZE " live Cells at shutdown\n", num_live);
     }
@@ -3575,29 +3605,31 @@ ArenaLists::checkEmptyArenaList(AllocKin
 }
 
 void
 GCRuntime::purgeRuntime(AutoLockForExclusiveAccess& lock)
 {
     for (GCCompartmentsIter comp(rt); !comp.done(); comp.next())
         comp->purge();
 
-    JSContext* cx = TlsContext.get();
-
-    freeUnusedLifoBlocksAfterSweeping(&cx->tempLifoAlloc());
-    cx->interpreterStack().purge(rt);
-    cx->frontendCollectionPool().purge();
-
-    rt->zoneGroupFromMainThread()->caches().gsnCache.purge();
-    rt->zoneGroupFromMainThread()->caches().envCoordinateNameCache.purge();
-    rt->zoneGroupFromMainThread()->caches().newObjectCache.purge();
-    rt->zoneGroupFromMainThread()->caches().nativeIterCache.purge();
-    rt->zoneGroupFromMainThread()->caches().uncompressedSourceCache.purge();
-    if (rt->zoneGroupFromMainThread()->caches().evalCache.initialized())
-        rt->zoneGroupFromMainThread()->caches().evalCache.clear();
+    for (const CooperatingContext& target : rt->cooperatingContexts()) {
+        freeUnusedLifoBlocksAfterSweeping(&target.context()->tempLifoAlloc());
+        target.context()->interpreterStack().purge(rt);
+        target.context()->frontendCollectionPool().purge();
+    }
+
+    for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
+        group->caches().gsnCache.purge();
+        group->caches().envCoordinateNameCache.purge();
+        group->caches().newObjectCache.purge();
+        group->caches().nativeIterCache.purge();
+        group->caches().uncompressedSourceCache.purge();
+        if (group->caches().evalCache.initialized())
+            group->caches().evalCache.clear();
+    }
 
     if (auto cache = rt->maybeThisRuntimeSharedImmutableStrings())
         cache->purge();
 
     rt->promiseTasksToDestroy.lock()->clear();
 }
 
 bool
@@ -5503,17 +5535,16 @@ GCRuntime::beginCompactPhase()
     MOZ_ASSERT(!relocatedArenasToRelease);
     startedCompacting = true;
 }
 
 GCRuntime::IncrementalProgress
 GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
                         AutoLockForExclusiveAccess& lock)
 {
-    MOZ_ASSERT(rt->zoneGroupFromMainThread()->nursery().isEmpty());
     assertBackgroundSweepingFinished();
     MOZ_ASSERT(startedCompacting);
 
     gcstats::AutoPhase ap(stats(), gcstats::PHASE_COMPACT);
 
     // TODO: JSScripts can move. If the sampler interrupts the GC in the
     // middle of relocating an arena, invalid JSScript pointers may be
     // accessed. Suppress all sampling until a finer-grained solution can be
@@ -5522,16 +5553,17 @@ GCRuntime::compactPhase(JS::gcreason::Re
 
     ZoneList relocatedZones;
     Arena* relocatedArenas = nullptr;
     while (!zonesToMaybeCompact.ref().isEmpty()) {
 
         Zone* zone = zonesToMaybeCompact.ref().front();
         zonesToMaybeCompact.ref().removeFront();
 
+        MOZ_ASSERT(zone->group()->nursery().isEmpty());
         MOZ_ASSERT(zone->isGCFinished());
         zone->setGCState(Zone::Compact);
 
         if (relocateArenas(zone, reason, relocatedArenas, sliceBudget)) {
             updateZonePointersToRelocatedCells(zone, lock);
             relocatedZones.append(zone);
         } else {
             zone->setGCState(Zone::Finished);
@@ -5553,20 +5585,22 @@ GCRuntime::compactPhase(JS::gcreason::Re
     }
 
     if (ShouldProtectRelocatedArenas(reason))
         protectAndHoldArenas(relocatedArenas);
     else
         releaseRelocatedArenas(relocatedArenas);
 
     // Clear caches that can contain cell pointers.
-    rt->zoneGroupFromMainThread()->caches().newObjectCache.purge();
-    rt->zoneGroupFromMainThread()->caches().nativeIterCache.purge();
-    if (rt->zoneGroupFromMainThread()->caches().evalCache.initialized())
-        rt->zoneGroupFromMainThread()->caches().evalCache.clear();
+    for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
+        group->caches().newObjectCache.purge();
+        group->caches().nativeIterCache.purge();
+        if (group->caches().evalCache.initialized())
+            group->caches().evalCache.clear();
+    }
 
 #ifdef DEBUG
     CheckHashTablesAfterMovingGC(rt);
 #endif
 
     return zonesToMaybeCompact.ref().isEmpty() ? Finished : NotFinished;
 }
 
@@ -5616,42 +5650,68 @@ HeapStateToLabel(JS::HeapState heapState
       case JS::HeapState::Idle:
       case JS::HeapState::CycleCollecting:
         MOZ_CRASH("Should never have an Idle or CC heap state when pushing GC pseudo frames!");
     }
     MOZ_ASSERT_UNREACHABLE("Should have exhausted every JS::HeapState variant!");
     return nullptr;
 }
 
+#ifdef DEBUG
+static bool
+AllNurseriesAreEmpty(JSRuntime* rt)
+{
+    for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
+        if (!group->nursery().isEmpty())
+            return false;
+    }
+    return true;
+}
+#endif
+
 /* Start a new heap session. */
 AutoTraceSession::AutoTraceSession(JSRuntime* rt, JS::HeapState heapState)
   : lock(rt),
     runtime(rt),
     prevState(TlsContext.get()->heapState),
-    pseudoFrame(rt, HeapStateToLabel(heapState), ProfileEntry::Category::GC),
-    prohibitActiveContextChange(rt)
+    pseudoFrame(rt, HeapStateToLabel(heapState), ProfileEntry::Category::GC)
 {
     MOZ_ASSERT(prevState == JS::HeapState::Idle);
     MOZ_ASSERT(heapState != JS::HeapState::Idle);
-    MOZ_ASSERT_IF(heapState == JS::HeapState::MajorCollecting, rt->zoneGroupFromMainThread()->nursery().isEmpty());
+    MOZ_ASSERT_IF(heapState == JS::HeapState::MajorCollecting, AllNurseriesAreEmpty(rt));
     TlsContext.get()->heapState = heapState;
 }
 
 AutoTraceSession::~AutoTraceSession()
 {
     MOZ_ASSERT(JS::CurrentThreadIsHeapBusy());
     TlsContext.get()->heapState = prevState;
 }
 
 JS_PUBLIC_API(JS::HeapState)
 JS::CurrentThreadHeapState()
 {
     return TlsContext.get()->heapState;
 }
 
+bool
+GCRuntime::canChangeActiveContext(JSContext* cx)
+{
+    // Threads cannot be in the middle of any operation that affects GC
+    // behavior when execution transfers to another thread for cooperative
+    // scheduling.
+    return cx->heapState == JS::HeapState::Idle
+        && !cx->suppressGC
+        && cx->allowGCBarriers
+        && !cx->inUnsafeRegion
+        && !cx->generationalDisabled
+        && !cx->compactingDisabledCount
+        && !cx->keepAtoms;
+}
+
 GCRuntime::IncrementalResult
 GCRuntime::resetIncrementalGC(gc::AbortReason reason, AutoLockForExclusiveAccess& lock)
 {
     MOZ_ASSERT(reason != gc::AbortReason::None);
 
     switch (incrementalState) {
       case State::NotActive:
           return IncrementalResult::Ok;
@@ -5892,17 +5952,18 @@ GCRuntime::incrementalCollectSlice(Slice
         incrementalState = State::Mark;
 
         if (isIncremental && useZeal && hasZealMode(ZealMode::IncrementalRootsThenFinish))
             break;
 
         MOZ_FALLTHROUGH;
 
       case State::Mark:
-        AutoGCRooter::traceAllWrappers(&marker);
+        for (const CooperatingContext& target : rt->cooperatingContexts())
+            AutoGCRooter::traceAllWrappers(target, &marker);
 
         /* If we needed delayed marking for gray roots, then collect until done. */
         if (!hasBufferedGrayRoots()) {
             budget.makeUnlimited();
             isIncremental = false;
         }
 
         if (drainMarkStack(budget, gcstats::PHASE_MARK) == NotFinished)
@@ -5977,17 +6038,17 @@ GCRuntime::incrementalCollectSlice(Slice
 
         {
             // Re-sweep the zones list, now that background finalization is
             // finished to actually remove and free dead zones.
             gcstats::AutoPhase ap1(stats(), gcstats::PHASE_SWEEP);
             gcstats::AutoPhase ap2(stats(), gcstats::PHASE_DESTROY);
             AutoSetThreadIsSweeping threadIsSweeping;
             FreeOp fop(rt);
-            sweepZones(&fop, destroyingRuntime);
+            sweepZoneGroups(&fop, destroyingRuntime);
         }
 
         MOZ_ASSERT(!startedCompacting);
         incrementalState = State::Compact;
 
         // Always yield before compacting since it is not incremental.
         if (isCompacting && isIncremental)
             break;
@@ -6186,17 +6247,17 @@ GCRuntime::gcCycle(bool nonincrementalBy
 {
     // Note that the following is allowed to re-enter GC in the finalizer.
     AutoNotifyGCActivity notify(*this);
 
     gcstats::AutoGCSlice agc(stats(), scanZonesBeforeGC(), invocationKind, budget, reason);
 
     AutoExposeLiveCrossZoneEdges aelcze(&foundBlackGrayEdges.ref());
 
-    rt->zoneGroupFromMainThread()->evictNursery(reason);
+    EvictAllNurseries(rt, reason);
 
     AutoTraceSession session(rt, JS::HeapState::MajorCollecting);
 
     majorGCTriggerReason = JS::gcreason::NO_REASON;
     interFrameGC = true;
 
     number++;
     if (!isIncrementalGCInProgress())
@@ -6312,17 +6373,17 @@ GCRuntime::maybeDoCycleCollection()
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         ++compartmentsTotal;
         GlobalObject* global = c->unsafeUnbarrieredMaybeGlobal();
         if (global && global->asTenured().isMarked(GRAY))
             ++compartmentsGray;
     }
     double grayFraction = double(compartmentsGray) / double(compartmentsTotal);
     if (grayFraction > ExcessiveGrayCompartments || compartmentsGray > LimitGrayCompartments)
-        callDoCycleCollectionCallback(rt->contextFromMainThread());
+        callDoCycleCollectionCallback(rt->activeContextFromOwnThread());
 }
 
 void
 GCRuntime::checkCanCallAPI()
 {
     MOZ_RELEASE_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
     /* If we attempt to invoke the GC while we are running in the GC, assert. */
@@ -6389,17 +6450,17 @@ GCRuntime::collect(bool nonincrementalBy
         if (reason == JS::gcreason::ABORT_GC) {
             MOZ_ASSERT(!isIncrementalGCInProgress());
             break;
         }
 
         bool repeatForDeadZone = false;
         if (poked && cleanUpEverything) {
             /* Need to re-schedule all zones for GC. */
-            JS::PrepareForFullGC(rt->contextFromMainThread());
+            JS::PrepareForFullGC(rt->activeContextFromOwnThread());
         } else if (shouldRepeatForDeadZone(reason) && !wasReset) {
             /*
              * This code makes an extra effort to collect compartments that we
              * thought were dead at the start of the GC. See the large comment
              * in beginMarkPhase.
              */
             repeatForDeadZone = true;
             reason = JS::gcreason::COMPARTMENT_REVIVED;
@@ -6507,24 +6568,24 @@ GCRuntime::notifyDidPaint()
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
 #ifdef JS_GC_ZEAL
     if (hasZealMode(ZealMode::FrameVerifierPre))
         verifyPreBarriers();
 
     if (hasZealMode(ZealMode::FrameGC)) {
-        JS::PrepareForFullGC(rt->contextFromMainThread());
+        JS::PrepareForFullGC(rt->activeContextFromOwnThread());
         gc(GC_NORMAL, JS::gcreason::REFRESH_FRAME);
         return;
     }
 #endif
 
     if (isIncrementalGCInProgress() && !interFrameGC && tunables.areRefreshFrameSlicesEnabled()) {
-        JS::PrepareForIncrementalGC(rt->contextFromMainThread());
+        JS::PrepareForIncrementalGC(rt->activeContextFromOwnThread());
         gcSlice(JS::gcreason::REFRESH_FRAME);
     }
 
     interFrameGC = false;
 }
 
 static bool
 ZonesSelected(JSRuntime* rt)
@@ -6536,49 +6597,50 @@ ZonesSelected(JSRuntime* rt)
     return false;
 }
 
 void
 GCRuntime::startDebugGC(JSGCInvocationKind gckind, SliceBudget& budget)
 {
     MOZ_ASSERT(!isIncrementalGCInProgress());
     if (!ZonesSelected(rt))
-        JS::PrepareForFullGC(rt->contextFromMainThread());
+        JS::PrepareForFullGC(rt->activeContextFromOwnThread());
     invocationKind = gckind;
     collect(false, budget, JS::gcreason::DEBUG_GC);
 }
 
 void
 GCRuntime::debugGCSlice(SliceBudget& budget)
 {
     MOZ_ASSERT(isIncrementalGCInProgress());
     if (!ZonesSelected(rt))
-        JS::PrepareForIncrementalGC(rt->contextFromMainThread());
+        JS::PrepareForIncrementalGC(rt->activeContextFromOwnThread());
     collect(false, budget, JS::gcreason::DEBUG_GC);
 }
 
 /* Schedule a full GC unless a zone will already be collected. */
 void
 js::PrepareForDebugGC(JSRuntime* rt)
 {
     if (!ZonesSelected(rt))
-        JS::PrepareForFullGC(rt->contextFromMainThread());
+        JS::PrepareForFullGC(rt->activeContextFromOwnThread());
 }
 
 void
 GCRuntime::onOutOfMallocMemory()
 {
     // Stop allocating new chunks.
     allocTask.cancel(GCParallelTask::CancelAndWait);
 
     // Make sure we release anything queued for release.
     decommitTask.join();
 
     // Wait for background free of nursery huge slots to finish.
-    rt->zoneGroupFromMainThread()->nursery().waitBackgroundFreeEnd();
+    for (ZoneGroupsIter group(rt); !group.done(); group.next())
+        group->nursery().waitBackgroundFreeEnd();
 
     AutoLockGC lock(rt);
     onOutOfMallocMemory(lock);
 }
 
 void
 GCRuntime::onOutOfMallocMemory(const AutoLockGC& lock)
 {
@@ -6600,17 +6662,17 @@ ZoneGroup::minorGC(JS::gcreason::Reason 
 {
     MOZ_ASSERT(!JS::CurrentThreadIsHeapBusy());
 
     if (TlsContext.get()->suppressGC)
         return;
 
     gcstats::AutoPhase ap(runtime->gc.stats(), phase);
 
-    runtime->gc.minorGCTriggerReason = JS::gcreason::NO_REASON;
+    nursery().clearMinorGCRequest();
     TraceLoggerThread* logger = TraceLoggerForCurrentThread();
     AutoTraceLog logMinorGC(logger, TraceLogger_MinorGC);
     nursery().collect(reason);
     MOZ_ASSERT(nursery().isEmpty());
 
     blocksToFreeAfterMinorGC.ref().freeAll();
 
 #ifdef JS_GC_ZEAL
@@ -6624,41 +6686,47 @@ ZoneGroup::minorGC(JS::gcreason::Reason 
             runtime->gc.maybeAllocTriggerZoneGC(zone, lock);
     }
 }
 
 JS::AutoDisableGenerationalGC::AutoDisableGenerationalGC(JSContext* cx)
   : cx(cx)
 {
     if (!cx->generationalDisabled) {
-        cx->runtime()->zoneGroupFromMainThread()->evictNursery(JS::gcreason::API);
-        cx->runtime()->zoneGroupFromMainThread()->nursery().disable();
+        for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next()) {
+            group->evictNursery(JS::gcreason::API);
+            group->nursery().disable();
+        }
     }
     ++cx->generationalDisabled;
 }
 
 JS::AutoDisableGenerationalGC::~AutoDisableGenerationalGC()
 {
-    if (--cx->generationalDisabled == 0)
-        cx->runtime()->zoneGroupFromMainThread()->nursery().enable();
+    if (--cx->generationalDisabled == 0) {
+        for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next())
+            group->nursery().enable();
+    }
 }
 
 JS_PUBLIC_API(bool)
 JS::IsGenerationalGCEnabled(JSRuntime* rt)
 {
     return !TlsContext.get()->generationalDisabled;
 }
 
 bool
 GCRuntime::gcIfRequested()
 {
     // This method returns whether a major GC was performed.
 
-    if (minorGCRequested())
-        rt->zoneGroupFromMainThread()->minorGC(minorGCTriggerReason);
+    for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
+        if (group->nursery().minorGCRequested())
+            group->minorGC(group->nursery().minorGCTriggerReason());
+    }
 
     if (majorGCRequested()) {
         if (!isIncrementalGCInProgress())
             startGC(GC_NORMAL, majorGCTriggerReason);
         else
             gcSlice(majorGCTriggerReason);
         return true;
     }
@@ -6669,17 +6737,18 @@ GCRuntime::gcIfRequested()
 void
 js::gc::FinishGC(JSContext* cx)
 {
     if (JS::IsIncrementalGCInProgress(cx)) {
         JS::PrepareForIncrementalGC(cx);
         JS::FinishIncrementalGC(cx, JS::gcreason::API);
     }
 
-    cx->runtime()->zoneGroupFromMainThread()->nursery().waitBackgroundFreeEnd();
+    for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next())
+        group->nursery().waitBackgroundFreeEnd();
 }
 
 AutoPrepareForTracing::AutoPrepareForTracing(JSContext* cx, ZoneSelector selector)
 {
     js::gc::FinishGC(cx);
     session_.emplace(cx->runtime());
 }
 
@@ -6735,17 +6804,17 @@ gc::MergeCompartments(JSCompartment* sou
     // The source compartment must be specifically flagged as mergable.  This
     // also implies that the compartment is not visible to the debugger.
     MOZ_ASSERT(source->creationOptions_.mergeable());
     MOZ_ASSERT(source->creationOptions_.invisibleToDebugger());
 
     MOZ_ASSERT(source->creationOptions().addonIdOrNull() ==
                target->creationOptions().addonIdOrNull());
 
-    JSContext* cx = source->contextFromMainThread();
+    JSContext* cx = source->runtimeFromMainThread()->activeContextFromOwnThread();
 
     AutoPrepareForTracing prepare(cx, SkipAtoms);
 
     // Cleanup tables and other state in the source compartment that will be
     // meaningless after merging into the target compartment.
 
     source->clearTables();
     source->zone()->clearTables();
@@ -7632,25 +7701,25 @@ AutoAssertHeapBusy::checkCondition(JSRun
     MOZ_ASSERT(JS::CurrentThreadIsHeapBusy());
 }
 
 void
 AutoAssertEmptyNursery::checkCondition(JSContext* cx) {
     if (!noAlloc)
         noAlloc.emplace();
     this->cx = cx;
-    MOZ_ASSERT(cx->runtime()->zoneGroupFromMainThread()->nursery().isEmpty());
+    MOZ_ASSERT(AllNurseriesAreEmpty(cx->runtime()));
 }
 
 AutoEmptyNursery::AutoEmptyNursery(JSContext* cx)
   : AutoAssertEmptyNursery()
 {
     MOZ_ASSERT(!cx->suppressGC);
     cx->runtime()->gc.stats().suspendPhases();
-    cx->runtime()->zoneGroupFromMainThread()->evictNursery();
+    EvictAllNurseries(cx->runtime(), JS::gcreason::EVICT_NURSERY);
     cx->runtime()->gc.stats().resumePhases();
     checkCondition(cx);
 }
 
 } /* namespace gc */
 } /* namespace js */
 
 #ifdef DEBUG
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -97,23 +97,31 @@ class ArenaIter
         if (!arena) {
             arena = unsweptArena;
             unsweptArena = sweptArena;
             sweptArena = nullptr;
         }
     }
 };
 
+enum CellIterNeedsBarrier : uint8_t
+{
+    CellIterDoesntNeedBarrier = 0,
+    CellIterMayNeedBarrier = 1
+};
+
 class ArenaCellIterImpl
 {
     size_t firstThingOffset;
     size_t thingSize;
     Arena* arenaAddr;
     FreeSpan span;
     uint_fast16_t thing;
+    JS::TraceKind traceKind;
+    bool needsBarrier;
     mozilla::DebugOnly<bool> initialized;
 
     // Upon entry, |thing| points to any thing (free or used) and finds the
     // first used thing, which may be |thing|.
     void moveForwardIfFree() {
         MOZ_ASSERT(!done());
         MOZ_ASSERT(thing);
         // Note: if |span| is empty, this test will fail, which is what we want
@@ -123,27 +131,42 @@ class ArenaCellIterImpl
         if (thing == span.first) {
             thing = span.last + thingSize;
             span = *span.nextSpan(arenaAddr);
         }
     }
 
   public:
     ArenaCellIterImpl()
-      : firstThingOffset(0), thingSize(0), arenaAddr(nullptr), thing(0), initialized(false) {}
+      : firstThingOffset(0),
+        thingSize(0),
+        arenaAddr(nullptr),
+        thing(0),
+        traceKind(JS::TraceKind::Null),
+        needsBarrier(false),
+        initialized(false)
+    {}
 
-    explicit ArenaCellIterImpl(Arena* arena) : initialized(false) { init(arena); }
+    explicit ArenaCellIterImpl(Arena* arena, CellIterNeedsBarrier mayNeedBarrier)
+      : initialized(false)
+    {
+        init(arena, mayNeedBarrier);
+    }
 
-    void init(Arena* arena) {
+    void init(Arena* arena, CellIterNeedsBarrier mayNeedBarrier) {
         MOZ_ASSERT(!initialized);
         MOZ_ASSERT(arena);
+        MOZ_ASSERT_IF(!mayNeedBarrier,
+                      CurrentThreadIsPerformingGC() || CurrentThreadIsGCSweeping());
         initialized = true;
         AllocKind kind = arena->getAllocKind();
         firstThingOffset = Arena::firstThingOffset(kind);
         thingSize = Arena::thingSize(kind);
+        traceKind = MapAllocToTraceKind(kind);
+        needsBarrier = mayNeedBarrier && !JS::CurrentThreadIsHeapCollecting();
         reset(arena);
     }
 
     // Use this to move from an Arena of a particular kind to another Arena of
     // the same kind.
     void reset(Arena* arena) {
         MOZ_ASSERT(initialized);
         MOZ_ASSERT(arena);
@@ -156,21 +179,30 @@ class ArenaCellIterImpl
     bool done() const {
         MOZ_ASSERT(initialized);
         MOZ_ASSERT(thing <= ArenaSize);
         return thing == ArenaSize;
     }
 
     TenuredCell* getCell() const {
         MOZ_ASSERT(!done());
-        return reinterpret_cast<TenuredCell*>(uintptr_t(arenaAddr) + thing);
+        TenuredCell* cell = reinterpret_cast<TenuredCell*>(uintptr_t(arenaAddr) + thing);
+
+        // This can result in a a new reference being created to an object that
+        // an ongoing incremental GC may find to be unreachable, so we may need
+        // a barrier here.
+        if (needsBarrier)
+            ExposeGCThingToActiveJS(JS::GCCellPtr(cell, traceKind));
+
+        return cell;
     }
 
     template<typename T> T* get() const {
         MOZ_ASSERT(!done());
+        MOZ_ASSERT(JS::MapTypeToTraceKind<T>::kind == traceKind);
         return static_cast<T*>(getCell());
     }
 
     void next() {
         MOZ_ASSERT(!done());
         thing += thingSize;
         if (thing < ArenaSize)
             moveForwardIfFree();
@@ -180,37 +212,37 @@ class ArenaCellIterImpl
 template<>
 JSObject*
 ArenaCellIterImpl::get<JSObject>() const;
 
 class ArenaCellIter : public ArenaCellIterImpl
 {
   public:
     explicit ArenaCellIter(Arena* arena)
-      : ArenaCellIterImpl(arena)
+      : ArenaCellIterImpl(arena, CellIterMayNeedBarrier)
     {
         MOZ_ASSERT(JS::CurrentThreadIsHeapTracing());
     }
 };
 
 class ArenaCellIterUnderGC : public ArenaCellIterImpl
 {
   public:
     explicit ArenaCellIterUnderGC(Arena* arena)
-      : ArenaCellIterImpl(arena)
+      : ArenaCellIterImpl(arena, CellIterDoesntNeedBarrier)
     {
         MOZ_ASSERT(CurrentThreadIsPerformingGC());
     }
 };
 
 class ArenaCellIterUnderFinalize : public ArenaCellIterImpl
 {
   public:
     explicit ArenaCellIterUnderFinalize(Arena* arena)
-      : ArenaCellIterImpl(arena)
+      : ArenaCellIterImpl(arena, CellIterDoesntNeedBarrier)
     {
         MOZ_ASSERT(CurrentThreadIsGCSweeping());
     }
 };
 
 template <typename T>
 class ZoneCellIter;
 
@@ -243,17 +275,17 @@ class ZoneCellIter<TenuredCell> {
         // We have a single-threaded runtime, so there's no need to protect
         // against other threads iterating or allocating. However, we do have
         // background finalization; we may have to wait for this to finish if
         // it's currently active.
         if (IsBackgroundFinalized(kind) && zone->arenas.needBackgroundFinalizeWait(kind))
             rt->gc.waitBackgroundSweepEnd();
         arenaIter.init(zone, kind);
         if (!arenaIter.done())
-            cellIter.init(arenaIter.get());
+            cellIter.init(arenaIter.get(), CellIterMayNeedBarrier);
     }
 
   public:
     ZoneCellIter(JS::Zone* zone, AllocKind kind) {
         // If we are iterating a nursery-allocated kind then we need to
         // evict first so that we can see all things.
         if (IsNurseryAllocable(kind))
             zone->group()->evictNursery();
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -118,16 +118,18 @@ typedef void
 (* JSTraceDataOp)(JSTracer* trc, void* data);
 
 namespace js {
 namespace gc {
 class AutoTraceSession;
 class StoreBuffer;
 } // namespace gc
 
+class CooperatingContext;
+
 inline JSCompartment* GetContextCompartment(const JSContext* cx);
 inline JS::Zone* GetContextZone(const JSContext* cx);
 
 // Whether the current thread is permitted access to any part of the specified
 // runtime or zone.
 JS_FRIEND_API(bool)
 CurrentThreadCanAccessRuntime(const JSRuntime* rt);
 
@@ -221,18 +223,18 @@ class JS_PUBLIC_API(AutoGCRooter)
 
     ~AutoGCRooter() {
         MOZ_ASSERT(this == *stackTop);
         *stackTop = down;
     }
 
     /* Implemented in gc/RootMarking.cpp. */
     inline void trace(JSTracer* trc);
-    static void traceAll(JSTracer* trc);
-    static void traceAllWrappers(JSTracer* trc);
+    static void traceAll(const js::CooperatingContext& target, JSTracer* trc);
+    static void traceAllWrappers(const js::CooperatingContext& target, JSTracer* trc);
 
   protected:
     AutoGCRooter * const down;
 
     /*
      * Discriminates actual subclass of this being used.  If non-negative, the
      * subclass roots an array of values of the length stored in this field.
      * If negative, meaning is indicated by the corresponding value in the enum
--- a/js/src/jsweakmap.cpp
+++ b/js/src/jsweakmap.cpp
@@ -19,17 +19,17 @@
 
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
 WeakMapBase::WeakMapBase(JSObject* memOf, Zone* zone)
   : memberOf(memOf),
-    zone(zone),
+    zone_(zone),
     marked(false)
 {
     MOZ_ASSERT_IF(memberOf, memberOf->compartment()->zone() == zone);
 }
 
 WeakMapBase::~WeakMapBase()
 {
     MOZ_ASSERT(CurrentThreadIsGCSweeping());
@@ -117,17 +117,17 @@ WeakMapBase::saveZoneMarkedWeakMaps(JS::
     return true;
 }
 
 void
 WeakMapBase::restoreMarkedWeakMaps(WeakMapSet& markedWeakMaps)
 {
     for (WeakMapSet::Range r = markedWeakMaps.all(); !r.empty(); r.popFront()) {
         WeakMapBase* map = r.front();
-        MOZ_ASSERT(map->zone->isGCMarking());
+        MOZ_ASSERT(map->zone()->isGCMarking());
         MOZ_ASSERT(!map->marked);
         map->marked = true;
     }
 }
 
 bool
 ObjectValueMap::findZoneEdges()
 {
@@ -140,17 +140,17 @@ ObjectValueMap::findZoneEdges()
     for (Range r = all(); !r.empty(); r.popFront()) {
         JSObject* key = r.front().key();
         if (key->asTenured().isMarked(BLACK) && !key->asTenured().isMarked(GRAY))
             continue;
         JSObject* delegate = getDelegate(key);
         if (!delegate)
             continue;
         Zone* delegateZone = delegate->zone();
-        if (delegateZone == zone || !delegateZone->isGCMarking())
+        if (delegateZone == zone() || !delegateZone->isGCMarking())
             continue;
         if (!delegateZone->gcZoneGroupEdges().put(key->zone()))
             return false;
     }
     return true;
 }
 
 ObjectWeakMap::ObjectWeakMap(JSContext* cx)
--- a/js/src/jsweakmap.h
+++ b/js/src/jsweakmap.h
@@ -44,16 +44,18 @@ typedef HashSet<WeakMapBase*, DefaultHas
 class WeakMapBase : public mozilla::LinkedListElement<WeakMapBase>
 {
     friend class js::GCMarker;
 
   public:
     WeakMapBase(JSObject* memOf, JS::Zone* zone);
     virtual ~WeakMapBase();
 
+    Zone* zone() const { return zone_; }
+
     // Garbage collector entry points.
 
     // Unmark all weak maps in a zone.
     static void unmarkZone(JS::Zone* zone);
 
     // Mark all the weakmaps in a zone.
     static void traceZone(JS::Zone* zone, JSTracer* tracer);
 
@@ -94,17 +96,17 @@ class WeakMapBase : public mozilla::Link
 
     virtual bool markIteratively(GCMarker* marker) = 0;
 
   protected:
     // Object that this weak map is part of, if any.
     GCPtrObject memberOf;
 
     // Zone containing this weak map.
-    JS::Zone* zone;
+    JS::Zone* zone_;
 
     // Whether this object has been traced during garbage collection.
     bool marked;
 };
 
 template <typename T>
 static T extractUnbarriered(WriteBarrieredBase<T> v)
 {
@@ -131,17 +133,17 @@ class WeakMap : public HashMap<Key, Valu
     typedef typename Base::AddPtr AddPtr;
 
     explicit WeakMap(JSContext* cx, JSObject* memOf = nullptr)
         : Base(cx->runtime()), WeakMapBase(memOf, cx->compartment()->zone()) { }
 
     bool init(uint32_t len = 16) {
         if (!Base::init(len))
             return false;
-        zone->gcWeakMapList().insertFront(this);
+        zone()->gcWeakMapList().insertFront(this);
         marked = JS::IsIncrementalGCInProgress(TlsContext.get());
         return true;
     }
 
     // Overwritten to add a read barrier to prevent an incorrectly gray value
     // from escaping the weak map. See the UnmarkGrayTracer::onChild comment in
     // gc/Marking.cpp.
     Ptr lookup(const Lookup& l) const {
@@ -279,17 +281,17 @@ class WeakMap : public HashMap<Key, Valu
         JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp();
         if (!op)
             return nullptr;
 
         JSObject* obj = op(key);
         if (!obj)
             return nullptr;
 
-        MOZ_ASSERT(obj->runtimeFromMainThread() == zone->runtimeFromMainThread());
+        MOZ_ASSERT(obj->runtimeFromMainThread() == zone()->runtimeFromMainThread());
         return obj;
     }
 
     JSObject* getDelegate(JSScript* script) const {
         return nullptr;
     }
 
   private:
@@ -297,17 +299,17 @@ class WeakMap : public HashMap<Key, Valu
     void exposeGCThingToActiveJS(JSObject* obj) const { JS::ExposeObjectToActiveJS(obj); }
 
     bool keyNeedsMark(JSObject* key) const {
         JSObject* delegate = getDelegate(key);
         /*
          * Check if the delegate is marked with any color to properly handle
          * gray marking when the key's delegate is black and the map is gray.
          */
-        return delegate && gc::IsMarkedUnbarriered(zone->runtimeFromMainThread(), &delegate);
+        return delegate && gc::IsMarkedUnbarriered(zone()->runtimeFromMainThread(), &delegate);
     }
 
     bool keyNeedsMark(JSScript* script) const {
         return false;
     }
 
     bool findZoneEdges() override {
         // This is overridden by ObjectValueMap.
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -8,16 +8,18 @@
 #include "jswrapper.h"
 
 #include "proxy/DeadObjectProxy.h"
 #include "vm/WrapperObject.h"
 
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 
+#include "gc/Nursery-inl.h"
+
 using namespace js;
 
 #define PIERCE(cx, wrapper, pre, op, post)                      \
     JS_BEGIN_MACRO                                              \
         bool ok;                                                \
         {                                                       \
             AutoCompartment call(cx, wrappedObject(wrapper));   \
             ok = (pre) && (op);                                 \
@@ -511,17 +513,17 @@ JS_FRIEND_API(bool)
 js::NukeCrossCompartmentWrappers(JSContext* cx,
                                  const CompartmentFilter& sourceFilter,
                                  const CompartmentFilter& targetFilter,
                                  js::NukeReferencesToWindow nukeReferencesToWindow)
 {
     CHECK_REQUEST(cx);
     JSRuntime* rt = cx->runtime();
 
-    rt->zoneGroupFromMainThread()->evictNursery(JS::gcreason::EVICT_NURSERY);
+    EvictAllNurseries(rt);
 
     // Iterate through scopes looking for system cross compartment wrappers
     // that point to an object that shares a global with obj.
 
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         if (!sourceFilter.match(c))
             continue;
 
@@ -651,17 +653,17 @@ js::RemapAllWrappersForObject(JSContext*
     return true;
 }
 
 JS_FRIEND_API(bool)
 js::RecomputeWrappers(JSContext* cx, const CompartmentFilter& sourceFilter,
                       const CompartmentFilter& targetFilter)
 {
     // Drop any nursery-allocated wrappers.
-    cx->runtime()->zoneGroupFromMainThread()->evictNursery(JS::gcreason::EVICT_NURSERY);
+    EvictAllNurseries(cx->runtime());
 
     AutoWrapperVector toRecompute(cx);
     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
         // Filter by source compartment.
         if (!sourceFilter.match(c))
             continue;
 
         // Iterate over the wrappers, filtering appropriately.
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -7656,17 +7656,17 @@ SetContextOptions(JSContext* cx, const O
 
     int32_t poolMaxOffset = op.getIntOption("asm-pool-max-offset");
     if (poolMaxOffset >= 5 && poolMaxOffset <= 1024)
         jit::Assembler::AsmPoolMaxOffset = poolMaxOffset;
 #endif
 
 #if defined(JS_SIMULATOR_ARM)
     if (op.getBoolOption("arm-sim-icache-checks"))
-        jit::Simulator::ICacheCheckingEnabled = true;
+        jit::SimulatorProcess::ICacheCheckingDisableCount = 0;
 
     int32_t stopAt = op.getIntOption("arm-sim-stop-at");
     if (stopAt >= 0)
         jit::Simulator::StopSimAt = stopAt;
 #elif defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
     if (op.getBoolOption("mips-sim-icache-checks"))
         jit::Simulator::ICacheCheckingEnabled = true;
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3039,20 +3039,22 @@ Debugger::traceCrossCompartmentEdges(JST
  */
 /* static */ void
 Debugger::traceIncomingCrossCompartmentEdges(JSTracer* trc)
 {
     JSRuntime* rt = trc->runtime();
     gc::State state = rt->gc.state();
     MOZ_ASSERT(state == gc::State::MarkRoots || state == gc::State::Compact);
 
-    for (Debugger* dbg : rt->zoneGroupFromMainThread()->debuggerList()) {
-        Zone* zone = MaybeForwarded(dbg->object.get())->zone();
-        if (!zone->isCollecting() || state == gc::State::Compact)
-            dbg->traceCrossCompartmentEdges(trc);
+    for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
+        for (Debugger* dbg : group->debuggerList()) {
+            Zone* zone = MaybeForwarded(dbg->object.get())->zone();
+            if (!zone->isCollecting() || state == gc::State::Compact)
+                dbg->traceCrossCompartmentEdges(trc);
+        }
     }
 }
 
 /*
  * This method has two tasks:
  *   1. Mark Debugger objects that are unreachable except for debugger hooks that
  *      may yet be called.
  *   2. Mark breakpoint handlers.
@@ -3139,51 +3141,58 @@ Debugger::markIteratively(GCMarker* mark
                     }
                 }
             }
         }
     }
     return markedAny;
 }
 
+/* static */ void
+Debugger::traceAllForMovingGC(JSTracer* trc)
+{
+    JSRuntime* rt = trc->runtime();
+    for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
+        for (Debugger* dbg : group->debuggerList())
+            dbg->traceForMovingGC(trc);
+    }
+}
+
 /*
  * Trace all debugger-owned GC things unconditionally. This is used during
  * compacting GC and in minor GC: the minor GC cannot apply the weak constraints
  * of the full GC because it visits only part of the heap.
  */
-/* static */ void
-Debugger::traceAll(JSTracer* trc)
-{
-    JSRuntime* rt = trc->runtime();
-    for (Debugger* dbg : rt->zoneGroupFromMainThread()->debuggerList()) {
-        for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
-            TraceManuallyBarrieredEdge(trc, e.mutableFront().unsafeGet(), "Global Object");
-
-        GCPtrNativeObject& dbgobj = dbg->toJSObjectRef();
-        TraceEdge(trc, &dbgobj, "Debugger Object");
-
-        dbg->scripts.trace(trc);
-        dbg->sources.trace(trc);
-        dbg->objects.trace(trc);
-        dbg->environments.trace(trc);
-        dbg->wasmInstanceScripts.trace(trc);
-        dbg->wasmInstanceSources.trace(trc);
-
-        for (Breakpoint* bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
-            switch (bp->site->type()) {
-              case BreakpointSite::Type::JS:
-                TraceManuallyBarrieredEdge(trc, &bp->site->asJS()->script,
-                                           "breakpoint script");
-                break;
-              case BreakpointSite::Type::Wasm:
-                TraceManuallyBarrieredEdge(trc, &bp->asWasm()->wasmInstance, "breakpoint wasm instance");
-                break;
-            }
-            TraceEdge(trc, &bp->getHandlerRef(), "breakpoint handler");
-        }
+void
+Debugger::traceForMovingGC(JSTracer* trc)
+{
+    for (WeakGlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront())
+        TraceManuallyBarrieredEdge(trc, e.mutableFront().unsafeGet(), "Global Object");
+
+    GCPtrNativeObject& dbgobj = toJSObjectRef();
+    TraceEdge(trc, &dbgobj, "Debugger Object");
+
+    scripts.trace(trc);
+    sources.trace(trc);
+    objects.trace(trc);
+    environments.trace(trc);
+    wasmInstanceScripts.trace(trc);
+    wasmInstanceSources.trace(trc);
+
+    for (Breakpoint* bp = firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
+        switch (bp->site->type()) {
+          case BreakpointSite::Type::JS:
+            TraceManuallyBarrieredEdge(trc, &bp->site->asJS()->script,
+                                       "breakpoint script");
+            break;
+          case BreakpointSite::Type::Wasm:
+            TraceManuallyBarrieredEdge(trc, &bp->asWasm()->wasmInstance, "breakpoint wasm instance");
+            break;
+        }
+        TraceEdge(trc, &bp->getHandlerRef(), "breakpoint handler");
     }
 }
 
 /* static */ void
 Debugger::traceObject(JSTracer* trc, JSObject* obj)
 {
     if (Debugger* dbg = Debugger::fromJSObject(obj))
         dbg->trace(trc);
@@ -3229,25 +3238,27 @@ Debugger::trace(JSTracer* trc)
     wasmInstanceSources.trace(trc);
 }
 
 /* static */ void
 Debugger::sweepAll(FreeOp* fop)
 {
     JSRuntime* rt = fop->runtime();
 
-    for (Debugger* dbg : rt->zoneGroupFromMainThread()->debuggerList()) {
-        if (IsAboutToBeFinalized(&dbg->object)) {
-            /*
-             * dbg is being GC'd. Detach it from its debuggees. The debuggee
-             * might be GC'd too. Since detaching requires access to both
-             * objects, this must be done before finalize time.
-             */
-            for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
-                dbg->removeDebuggeeGlobal(fop, e.front().unbarrieredGet(), &e);
+    for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
+        for (Debugger* dbg : group->debuggerList()) {
+            if (IsAboutToBeFinalized(&dbg->object)) {
+                /*
+                 * dbg is being GC'd. Detach it from its debuggees. The debuggee
+                 * might be GC'd too. Since detaching requires access to both
+                 * objects, this must be done before finalize time.
+                 */
+                for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
+                    dbg->removeDebuggeeGlobal(fop, e.front().unbarrieredGet(), &e);
+            }
         }
     }
 }
 
 /* static */ void
 Debugger::detachAllDebuggersFromGlobal(FreeOp* fop, GlobalObject* global)
 {
     const GlobalObject::DebuggerVector* debuggers = global->getDebuggers();
@@ -3258,19 +3269,22 @@ Debugger::detachAllDebuggersFromGlobal(F
 
 /* static */ void
 Debugger::findZoneEdges(Zone* zone, js::gc::ZoneComponentFinder& finder)
 {
     /*
      * For debugger cross compartment wrappers, add edges in the opposite
      * direction to those already added by JSCompartment::findOutgoingEdges.
      * This ensure that debuggers and their debuggees are finalized in the same
-     * group.
+     * group. We only need to look at the zone's ZoneGroup, as debuggers and
+     * debuggees are always in the same ZoneGroup.
      */
-    for (Debugger* dbg : zone->runtimeFromMainThread()->zoneGroupFromMainThread()->debuggerList()) {
+    if (zone->isAtomsZone())
+        return;
+    for (Debugger* dbg : zone->group()->debuggerList()) {
         Zone* w = dbg->object->zone();
         if (w == zone || !w->isGCMarking())
             continue;
         if (dbg->debuggeeZones.has(zone) ||
             dbg->scripts.hasKeyInZone(zone) ||
             dbg->sources.hasKeyInZone(zone) ||
             dbg->objects.hasKeyInZone(zone) ||
             dbg->environments.hasKeyInZone(zone) ||
@@ -11768,24 +11782,26 @@ FireOnGarbageCollectionHook(JSContext* c
     AutoObjectVector triggered(cx);
 
     {
         // We had better not GC (and potentially get a dangling Debugger
         // pointer) while finding all Debuggers observing a debuggee that
         // participated in this GC.
         AutoCheckCannotGC noGC;
 
-        for (Debugger* dbg : cx->runtime()->zoneGroupFromMainThread()->debuggerList()) {
-            if (dbg->enabled &&
-                dbg->observedGC(data->majorGCNumber()) &&
-                dbg->getHook(Debugger::OnGarbageCollection))
-            {
-                if (!triggered.append(dbg->object)) {
-                    JS_ReportOutOfMemory(cx);
-                    return false;
+        for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next()) {
+            for (Debugger* dbg : group->debuggerList()) {
+                if (dbg->enabled &&
+                    dbg->observedGC(data->majorGCNumber()) &&
+                    dbg->getHook(Debugger::OnGarbageCollection))
+                {
+                    if (!triggered.append(dbg->object)) {
+                        JS_ReportOutOfMemory(cx);
+                        return false;
+                    }
                 }
             }
         }
     }
 
     for ( ; !triggered.empty(); triggered.popBack()) {
         Debugger* dbg = Debugger::fromJSObject(triggered.back());
         dbg->fireOnGarbageCollectionHook(cx, data);
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -583,16 +583,17 @@ class Debugger : private mozilla::Linked
     bool processResumptionValue(mozilla::Maybe<AutoCompartment>& ac, AbstractFramePtr frame,
                                 const mozilla::Maybe<HandleValue>& maybeThis, HandleValue rval,
                                 JSTrapStatus& statusp, MutableHandleValue vp);
 
     GlobalObject* unwrapDebuggeeArgument(JSContext* cx, const Value& v);
 
     static void traceObject(JSTracer* trc, JSObject* obj);
     void trace(JSTracer* trc);
+    void traceForMovingGC(JSTracer* trc);
     static void finalize(FreeOp* fop, JSObject* obj);
     void traceCrossCompartmentEdges(JSTracer* tracer);
 
     static const ClassOps classOps_;
 
   public:
     static const Class class_;
 
@@ -800,16 +801,18 @@ class Debugger : private mozilla::Linked
     ~Debugger();
 
     MOZ_MUST_USE bool init(JSContext* cx);
     inline const js::GCPtrNativeObject& toJSObject() const;
     inline js::GCPtrNativeObject& toJSObjectRef();
     static inline Debugger* fromJSObject(const JSObject* obj);
     static Debugger* fromChildJSObject(JSObject* obj);
 
+    Zone* zone() const { return toJSObject()->zone(); }
+
     bool hasMemory() const;
     DebuggerMemory& memory() const;
 
     WeakGlobalObjectSet::Range allDebuggees() const { return debuggees.all(); }
 
     /*********************************** Methods for interaction with the GC. */
 
     /*
@@ -824,17 +827,17 @@ class Debugger : private mozilla::Linked
      *       - it has a watchpoint set on a live object.
      *
      * Debugger::markIteratively handles the last case. If it finds any Debugger
      * objects that are definitely live but not yet marked, it marks them and
      * returns true. If not, it returns false.
      */
     static void traceIncomingCrossCompartmentEdges(JSTracer* tracer);
     static MOZ_MUST_USE bool markIteratively(GCMarker* marker);
-    static void traceAll(JSTracer* trc);
+    static void traceAllForMovingGC(JSTracer* trc);
     static void sweepAll(FreeOp* fop);
     static void detachAllDebuggersFromGlobal(FreeOp* fop, GlobalObject* global);
     static void findZoneEdges(JS::Zone* v, gc::ZoneComponentFinder& finder);
 
     // Checks it the current compartment is allowed to execute code.
     static inline MOZ_MUST_USE bool checkNoExecute(JSContext* cx, HandleScript script);
 
     /*
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -2310,18 +2310,19 @@ DebugEnvironmentProxy::isOptimizedOut() 
                !maybeSnapshot();
     }
 
     return false;
 }
 
 /*****************************************************************************/
 
-DebugEnvironments::DebugEnvironments(JSContext* cx)
- : proxiedEnvs(cx),
+DebugEnvironments::DebugEnvironments(JSContext* cx, Zone* zone)
+ : zone_(zone),
+   proxiedEnvs(cx),
    missingEnvs(cx->runtime()),
    liveEnvs(cx->runtime())
 {}
 
 DebugEnvironments::~DebugEnvironments()
 {
     MOZ_ASSERT_IF(missingEnvs.initialized(), missingEnvs.empty());
 }
@@ -2425,17 +2426,17 @@ CanUseDebugEnvironmentMaps(JSContext* cx
 
 DebugEnvironments*
 DebugEnvironments::ensureCompartmentData(JSContext* cx)
 {
     JSCompartment* c = cx->compartment();
     if (c->debugEnvs)
         return c->debugEnvs;
 
-    auto debugEnvs = cx->make_unique<DebugEnvironments>(cx);
+    auto debugEnvs = cx->make_unique<DebugEnvironments>(cx, cx->zone());
     if (!debugEnvs || !debugEnvs->init()) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     c->debugEnvs = debugEnvs.release();
     return c->debugEnvs;
 }
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -906,16 +906,18 @@ class DebugEnvironmentProxy : public Pro
     // live (and thus does not have a synthesized EnvironmentObject or a
     // snapshot)?
     bool isOptimizedOut() const;
 };
 
 /* Maintains per-compartment debug environment bookkeeping information. */
 class DebugEnvironments
 {
+    Zone* zone_;
+
     /* The map from (non-debug) environments to debug environments. */
     ObjectWeakMap proxiedEnvs;
 
     /*
      * The map from live frames which have optimized-away environments to the
      * corresponding debug environments.
      */
     typedef HashMap<MissingEnvironmentKey,
@@ -934,19 +936,21 @@ class DebugEnvironments
      */
     typedef GCHashMap<ReadBarriered<JSObject*>,
                       LiveEnvironmentVal,
                       MovableCellHasher<ReadBarriered<JSObject*>>,
                       RuntimeAllocPolicy> LiveEnvironmentMap;
     LiveEnvironmentMap liveEnvs;
 
   public:
-    explicit DebugEnvironments(JSContext* cx);
+    DebugEnvironments(JSContext* cx, Zone* zone);
     ~DebugEnvironments();
 
+    Zone* zone() const { return zone_; }
+
   private:
     bool init();
 
     static DebugEnvironments* ensureCompartmentData(JSContext* cx);
 
     template <typename Environment, typename Scope>
     static void onPopGeneric(JSContext* cx, const EnvironmentIter& ei);
 
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -465,17 +465,17 @@ js::CancelOffThreadParses(JSRuntime* rt)
     GlobalHelperThreadState::ParseTaskVector& finished = HelperThreadState().parseFinishedList(lock);
     while (true) {
         bool found = false;
         for (size_t i = 0; i < finished.length(); i++) {
             ParseTask* task = finished[i];
             if (task->runtimeMatches(rt)) {
                 found = true;
                 AutoUnlockHelperThreadState unlock(lock);
-                HelperThreadState().cancelParseTask(rt->contextFromMainThread(), task->kind, task);
+                HelperThreadState().cancelParseTask(rt, task->kind, task);
             }
         }
         if (!found)
             break;
     }
 }
 
 bool
@@ -1347,20 +1347,20 @@ GlobalHelperThreadState::finishModulePar
     module->fixEnvironmentsAfterCompartmentMerge();
     if (!ModuleObject::Freeze(cx, module))
         return nullptr;
 
     return module;
 }
 
 void
-GlobalHelperThreadState::cancelParseTask(JSContext* cx, ParseTaskKind kind, void* token)
+GlobalHelperThreadState::cancelParseTask(JSRuntime* rt, ParseTaskKind kind, void* token)
 {
     ScopedJSDeletePtr<ParseTask> parseTask(removeFinishedParseTask(kind, token));
-    LeaveParseTaskZone(cx->runtime(), parseTask);
+    LeaveParseTaskZone(rt, parseTask);
 }
 
 JSObject*
 GlobalObject::getStarGeneratorFunctionPrototype()
 {
     const Value& v = getReservedSlot(STAR_GENERATOR_FUNCTION_PROTO);
     return v.isObject() ? &v.toObject() : nullptr;
 }
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -236,17 +236,17 @@ class GlobalHelperThreadState
         if (!firstWasmError)
             firstWasmError = Move(error);
     }
     bool wasmFailed(const AutoLockHelperThreadState&) {
         return bool(numWasmFailedJobs);
     }
 
     JSScript* finishParseTask(JSContext* cx, ParseTaskKind kind, void* token);
-    void cancelParseTask(JSContext* cx, ParseTaskKind kind, void* token);
+    void cancelParseTask(JSRuntime* rt, ParseTaskKind kind, void* token);
 
     void mergeParseTaskCompartment(JSContext* cx, ParseTask* parseTask,
                                    Handle<GlobalObject*> global,
                                    JSCompartment* dest);
 
     void trace(JSTracer* trc);
 
   private:
--- a/js/src/vm/Initialization.cpp
+++ b/js/src/vm/Initialization.cpp
@@ -14,16 +14,17 @@
 
 #include "jstypes.h"
 
 #include "builtin/AtomicsObject.h"
 #include "ds/MemoryProtectionExceptionHandler.h"
 #include "gc/Statistics.h"
 #include "jit/ExecutableAllocator.h"
 #include "jit/Ion.h"
+#include "jit/JitCommon.h"
 #include "js/Utility.h"
 #if ENABLE_INTL_API
 #include "unicode/uclean.h"
 #include "unicode/utypes.h"
 #endif // ENABLE_INTL_API
 #include "vm/DateTime.h"
 #include "vm/HelperThreads.h"
 #include "vm/Runtime.h"
@@ -118,16 +119,20 @@ JS::detail::InitWithFailureDiagnostic(bo
     if (U_FAILURE(err))
         return "u_init() failed";
 #endif // EXPOSE_INTL_API
 
     RETURN_IF_FAIL(js::CreateHelperThreadsState());
     RETURN_IF_FAIL(FutexThread::initialize());
     RETURN_IF_FAIL(js::gcstats::Statistics::initialize());
 
+#ifdef JS_SIMULATOR
+    RETURN_IF_FAIL(js::jit::SimulatorProcess::initialize());
+#endif
+
     libraryInitState = InitState::Running;
     return nullptr;
 }
 
 #undef RETURN_IF_FAIL
 
 JS_PUBLIC_API(void)
 JS_ShutDown(void)
@@ -143,16 +148,20 @@ JS_ShutDown(void)
                 "TIME.  FIX THIS!\n");
     }
 #endif
 
     FutexThread::destroy();
 
     js::DestroyHelperThreadsState();
 
+#ifdef JS_SIMULATOR
+    js::jit::SimulatorProcess::destroy();
+#endif
+
 #ifdef JS_TRACE_LOGGING
     js::DestroyTraceLoggerThreadState();
     js::DestroyTraceLoggerGraphState();
 #endif
 
     js::MemoryProtectionExceptionHandler::uninstall();
 
     js::wasm::ShutDownInstanceStaticData();
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -736,17 +736,20 @@ FindNotableScriptSources(JS::RuntimeSize
 static bool
 CollectRuntimeStatsHelper(JSContext* cx, RuntimeStats* rtStats, ObjectPrivateVisitor* opv,
                           bool anonymize, IterateCellCallback statsCellCallback)
 {
     JSRuntime* rt = cx->runtime();
     if (!rtStats->compartmentStatsVector.reserve(rt->numCompartments))
         return false;
 
-    if (!rtStats->zoneStatsVector.reserve(rt->zoneGroupFromMainThread()->zones().length()))
+    size_t totalZones = 1; // For the atoms zone.
+    for (ZoneGroupsIter group(rt); !group.done(); group.next())
+        totalZones += group->zones().length();
+    if (!rtStats->zoneStatsVector.reserve(totalZones))
         return false;
 
     rtStats->gcHeapChunkTotal =
         size_t(JS_GetGCParameter(cx, JSGC_TOTAL_CHUNKS)) * gc::ChunkSize;
 
     rtStats->gcHeapUnusedChunks =
         size_t(JS_GetGCParameter(cx, JSGC_UNUSED_CHUNKS)) * gc::ChunkSize;
 
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -346,16 +346,17 @@ JSRuntime::destroyRuntime()
     js_delete(zoneGroupFromMainThread());
 }
 
 void
 JSRuntime::setActiveContext(JSContext* cx)
 {
     MOZ_ASSERT_IF(cx, isCooperatingContext(cx));
     MOZ_RELEASE_ASSERT(!activeContextChangeProhibited());
+    MOZ_RELEASE_ASSERT(gc.canChangeActiveContext(cx));
 
     activeContext_ = cx;
 }
 
 void
 JSRuntime::addTelemetry(int id, uint32_t sample, const char* key)
 {
     if (telemetryCallback)
@@ -369,64 +370,64 @@ JSRuntime::setTelemetryCallback(JSRuntim
 }
 
 void
 JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes* rtSizes)
 {
     // Several tables in the runtime enumerated below can be used off thread.
     AutoLockForExclusiveAccess lock(this);
 
-    // For now, measure the size of the derived class (JSContext).
-    // TODO (bug 1281529): make memory reporting reflect the new
-    // JSContext/JSRuntime world better.
-    JSContext* cx = unsafeContextFromAnyThread();
-    rtSizes->object += mallocSizeOf(cx);
-
+    rtSizes->object += mallocSizeOf(this);
     rtSizes->atomsTable += atoms(lock).sizeOfIncludingThis(mallocSizeOf);
 
     if (!parentRuntime) {
         rtSizes->atomsTable += mallocSizeOf(staticStrings);
         rtSizes->atomsTable += mallocSizeOf(commonNames);
         rtSizes->atomsTable += permanentAtoms->sizeOfIncludingThis(mallocSizeOf);
     }
 
-    rtSizes->contexts += cx->sizeOfExcludingThis(mallocSizeOf);
+    for (const CooperatingContext& target : cooperatingContexts()) {
+        JSContext* cx = target.context();
+        rtSizes->contexts += mallocSizeOf(cx);
+        rtSizes->contexts += cx->sizeOfExcludingThis(mallocSizeOf);
+        rtSizes->temporary += cx->tempLifoAlloc().sizeOfExcludingThis(mallocSizeOf);
+        rtSizes->interpreterStack += cx->interpreterStack().sizeOfExcludingThis(mallocSizeOf);
+    }
 
-    rtSizes->temporary += cx->tempLifoAlloc().sizeOfExcludingThis(mallocSizeOf);
+    for (ZoneGroupsIter group(this); !group.done(); group.next()) {
+        ZoneGroupCaches& caches = group->caches();
 
-    rtSizes->interpreterStack += cx->interpreterStack().sizeOfExcludingThis(mallocSizeOf);
+        if (MathCache* cache = caches.maybeGetMathCache())
+            rtSizes->mathCache += cache->sizeOfIncludingThis(mallocSizeOf);
 
-    ZoneGroupCaches& caches = zoneGroupFromAnyThread()->caches();
+        rtSizes->uncompressedSourceCache +=
+            caches.uncompressedSourceCache.sizeOfExcludingThis(mallocSizeOf);
 
-    if (MathCache* cache = caches.maybeGetMathCache())
-        rtSizes->mathCache += cache->sizeOfIncludingThis(mallocSizeOf);
+        rtSizes->gc.nurseryCommitted += group->nursery().sizeOfHeapCommitted();
+        rtSizes->gc.nurseryMallocedBuffers += group->nursery().sizeOfMallocedBuffers(mallocSizeOf);
+        group->storeBuffer().addSizeOfExcludingThis(mallocSizeOf, &rtSizes->gc);
+    }
 
     if (sharedImmutableStrings_) {
         rtSizes->sharedImmutableStringsCache +=
             sharedImmutableStrings_->sizeOfExcludingThis(mallocSizeOf);
     }
 
     rtSizes->sharedIntlData += sharedIntlData.ref().sizeOfExcludingThis(mallocSizeOf);
 
-    rtSizes->uncompressedSourceCache +=
-        caches.uncompressedSourceCache.sizeOfExcludingThis(mallocSizeOf);
-
     rtSizes->scriptData += scriptDataTable(lock).sizeOfExcludingThis(mallocSizeOf);
     for (ScriptDataTable::Range r = scriptDataTable(lock).all(); !r.empty(); r.popFront())
         rtSizes->scriptData += mallocSizeOf(r.front());
 
     if (jitRuntime_) {
         jitRuntime_->execAlloc().addSizeOfCode(&rtSizes->code);
         jitRuntime_->backedgeExecAlloc().addSizeOfCode(&rtSizes->code);
     }
 
     rtSizes->gc.marker += gc.marker.sizeOfExcludingThis(mallocSizeOf);
-    rtSizes->gc.nurseryCommitted += zoneGroupFromAnyThread()->nursery().sizeOfHeapCommitted();
-    rtSizes->gc.nurseryMallocedBuffers += zoneGroupFromAnyThread()->nursery().sizeOfMallocedBuffers(mallocSizeOf);
-    zoneGroupFromAnyThread()->storeBuffer().addSizeOfExcludingThis(mallocSizeOf, &rtSizes->gc);
 }
 
 static bool
 InvokeInterruptCallback(JSContext* cx)
 {
     MOZ_ASSERT(cx->requestDepth >= 1);
 
     cx->runtime()->gc.gcIfRequested();
@@ -527,17 +528,17 @@ JSContext::handleInterrupt()
 }
 
 bool
 JSRuntime::setDefaultLocale(const char* locale)
 {
     if (!locale)
         return false;
     resetDefaultLocale();
-    defaultLocale = JS_strdup(contextFromMainThread(), locale);
+    defaultLocale = JS_strdup(activeContextFromOwnThread(), locale);
     return defaultLocale != nullptr;
 }
 
 void
 JSRuntime::resetDefaultLocale()
 {
     js_free(defaultLocale);
     defaultLocale = nullptr;
@@ -554,17 +555,17 @@ JSRuntime::getDefaultLocale()
     locale = setlocale(LC_ALL, nullptr);
 #else
     locale = getenv("LANG");
 #endif
     // convert to a well-formed BCP 47 language tag
     if (!locale || !strcmp(locale, "C"))
         locale = "und";
 
-    char* lang = JS_strdup(contextFromMainThread(), locale);
+    char* lang = JS_strdup(activeContextFromOwnThread(), locale);
     if (!lang)
         return nullptr;
 
     char* p;
     if ((p = strchr(lang, '.')))
         *p = '\0';
     while ((p = strchr(lang, '_')))
         *p = '-';
@@ -782,17 +783,17 @@ JSRuntime::clearUsedByExclusiveThread(Zo
     numExclusiveThreads--;
     if (gc.fullGCForAtomsRequested() && !TlsContext.get())
         gc.triggerFullGCForAtoms();
 }
 
 bool
 js::CurrentThreadCanAccessRuntime(const JSRuntime* rt)
 {
-    return rt->unsafeContextFromAnyThread() == TlsContext.get();
+    return rt->activeContext() == TlsContext.get();
 }
 
 bool
 js::CurrentThreadCanAccessZone(Zone* zone)
 {
     if (CurrentThreadCanAccessRuntime(zone->runtime_))
         return true;
 
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -308,21 +308,23 @@ struct JSRuntime : public js::MallocProv
     // All contexts participating in cooperative scheduling. All threads other
     // than |activeContext_| are suspended.
     js::ActiveThreadData<js::Vector<js::CooperatingContext, 4, js::SystemAllocPolicy>> cooperatingContexts_;
 
     // Count of AutoProhibitActiveContextChange instances on the active context.
     js::ActiveThreadData<size_t> activeContextChangeProhibited_;
 
   public:
-    JSContext* activeContext() { return activeContext_; }
+    JSContext* activeContext() const { return activeContext_; }
     const void* addressOfActiveContext() { return &activeContext_; }
 
     void setActiveContext(JSContext* cx);
 
+    inline JSContext* activeContextFromOwnThread();
+
     js::Vector<js::CooperatingContext, 4, js::SystemAllocPolicy>& cooperatingContexts() {
         return cooperatingContexts_.ref();
     }
 
 #ifdef DEBUG
     bool isCooperatingContext(JSContext* cx) {
         for (const js::CooperatingContext& target : cooperatingContexts()) {
             if (target.context() == cx)
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -366,17 +366,17 @@ SavedFrame::protoAccessors[] = {
 
 /* static */ void
 SavedFrame::finalize(FreeOp* fop, JSObject* obj)
 {
     MOZ_ASSERT(fop->onMainThread());
     JSPrincipals* p = obj->as<SavedFrame>().getPrincipals();
     if (p) {
         JSRuntime* rt = obj->runtimeFromMainThread();
-        JS_DropPrincipals(rt->contextFromMainThread(), p);
+        JS_DropPrincipals(rt->activeContextFromOwnThread(), p);
     }
 }
 
 JSAtom*
 SavedFrame::getSource()
 {
     const Value& v = getReservedSlot(JSSLOT_SOURCE);
     JSString* s = v.toString();
--- a/js/src/vm/Scope.cpp
+++ b/js/src/vm/Scope.cpp
@@ -597,16 +597,22 @@ FunctionScope::copyData(JSContext* cx, H
         uint32_t shapeFlags = FunctionScopeEnvShapeFlags(hasParameterExprs);
         return CopyScopeData<FunctionScope>(cx, bi, data,
                                             &CallObject::class_,
                                             shapeFlags, envShape);
     }
     return NewEmptyScopeData<FunctionScope>(cx);
 }
 
+Zone*
+FunctionScope::Data::zone() const
+{
+    return canonicalFunction ? canonicalFunction->zone() : nullptr;
+}
+
 /* static */ FunctionScope*
 FunctionScope::create(JSContext* cx, Handle<Data*> data,
                       bool hasParameterExprs, bool needsEnvironment,
                       HandleFunction fun, HandleScope enclosing)
 {
     MOZ_ASSERT(fun->isTenured());
 
     // FunctionScope::Data has GCManagedDeletePolicy because it contains a
@@ -1107,16 +1113,22 @@ ModuleScope::copyData(JSContext* cx, Han
         BindingIter bi(*data);
         return CopyScopeData<ModuleScope>(cx, bi, data,
                                           &ModuleEnvironmentObject::class_,
                                           ModuleScopeEnvShapeFlags, envShape);
     }
     return NewEmptyScopeData<ModuleScope>(cx);
 }
 
+Zone*
+ModuleScope::Data::zone() const
+{
+    return module ? module->zone() : nullptr;
+}
+
 /* static */ ModuleScope*
 ModuleScope::create(JSContext* cx, Handle<Data*> data,
                     HandleModuleObject module, HandleScope enclosing)
 {
     MOZ_ASSERT(enclosing->is<GlobalScope>());
 
     // ModuleScope::Data has GCManagedDeletePolicy because it contains a
     // GCPtr. Destruction of |copy| below may trigger calls into the GC.
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -480,16 +480,17 @@ class FunctionScope : public Scope
         // scope.
         uint32_t nextFrameSlot;
 
         // The array of tagged JSAtom* names, allocated beyond the end of the
         // struct.
         BindingName names[1];
 
         void trace(JSTracer* trc);
+        Zone* zone() const;
     };
 
     static size_t sizeOfData(uint32_t length) {
         return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
     }
 
     static void getDataNamesAndLength(Data* data, BindingName** names, uint32_t* length) {
         *names = data->names;
@@ -881,16 +882,17 @@ class ModuleScope : public Scope
         // scope.
         uint32_t nextFrameSlot;
 
         // The array of tagged JSAtom* names, allocated beyond the end of the
         // struct.
         BindingName names[1];
 
         void trace(JSTracer* trc);
+        Zone* zone() const;
     };
 
     static size_t sizeOfData(uint32_t length) {
         return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
     }
 
     static void getDataNamesAndLength(Data* data, BindingName** names, uint32_t* length) {
         *names = data->names;
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1722,17 +1722,18 @@ ActivationIterator::ActivationIterator(J
 ActivationIterator::ActivationIterator(JSContext* cx, const CooperatingContext& target)
 {
     MOZ_ASSERT(cx == TlsContext.get());
 
     // If target was specified --- even if it is the same as cx itself --- then
     // we must be in a scope where changes of the active context are prohibited.
     // Otherwise our state would be corrupted if the target thread resumed
     // execution while we are iterating over its state.
-    MOZ_ASSERT(cx->runtime()->activeContextChangeProhibited());
+    MOZ_ASSERT(cx->runtime()->activeContextChangeProhibited() ||
+               !cx->runtime()->gc.canChangeActiveContext(cx));
 
     // Tolerate a null target context, in case we are iterating over the
     // activations for a zone group that is not in use by any thread.
     jitTop_ = target.context() ? target.context()->jitTop.ref() : nullptr;
     activation_ = target.context() ? target.context()->activation_.ref() : nullptr;
 
     settle();
 }
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -2014,17 +2014,17 @@ js::TryConvertToUnboxedLayout(JSContext*
 
         // The entire object must be allocatable inline.
         if (UnboxedPlainObject::offsetOfData() + layoutSize > JSObject::MAX_BYTE_SIZE)
             return true;
     }
 
     UniquePtr<UnboxedLayout>& layout = enter.unboxedLayoutToCleanUp;
     MOZ_ASSERT(!layout);
-    layout = group->zone()->make_unique<UnboxedLayout>();
+    layout = group->zone()->make_unique<UnboxedLayout>(group->zone());
     if (!layout)
         return false;
 
     if (isArray) {
         layout->initArray(elementType);
     } else {
         if (!layout->initProperties(properties, layoutSize))
             return false;
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -55,16 +55,18 @@ class UnboxedLayout : public mozilla::Li
         Property()
           : name(nullptr), offset(UINT32_MAX), type(JSVAL_TYPE_MAGIC)
         {}
     };
 
     typedef Vector<Property, 0, SystemAllocPolicy> PropertyVector;
 
   private:
+    Zone* zone_;
+
     // If objects in this group have ever been converted to native objects,
     // these store the corresponding native group and initial shape for such
     // objects. Type information for this object is reflected in nativeGroup.
     GCPtrObjectGroup nativeGroup_;
     GCPtrShape nativeShape_;
 
     // Any script/pc which the associated group is created for.
     GCPtrScript allocationScript_;
@@ -98,23 +100,25 @@ class UnboxedLayout : public mozilla::Li
     GCPtrJitCode constructorCode_;
 
     // The following members are only used for unboxed arrays.
 
     // The type of array elements.
     JSValueType elementType_;
 
   public:
-    UnboxedLayout()
-      : nativeGroup_(nullptr), nativeShape_(nullptr),
+    explicit UnboxedLayout(Zone* zone)
+      : zone_(zone), nativeGroup_(nullptr), nativeShape_(nullptr),
         allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr),
         size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr),
         elementType_(JSVAL_TYPE_MAGIC)
     {}
 
+    Zone* zone() const { return zone_; }
+
     bool initProperties(const PropertyVector& properties, size_t size) {
         size_ = size;
         return properties_.appendAll(properties);
     }
 
     void initArray(JSValueType elementType) {
         elementType_ = elementType;
     }
--- a/js/src/wasm/WasmSignalHandlers.cpp
+++ b/js/src/wasm/WasmSignalHandlers.cpp
@@ -1269,25 +1269,24 @@ RedirectJitCodeToInterruptCheck(JSContex
 static const int sInterruptSignal = SIGVTALRM;
 
 static void
 JitInterruptHandler(int signum, siginfo_t* info, void* context)
 {
     if (JSContext* cx = TlsContext.get()) {
 
 #if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
-        bool prevICacheCheckingState = Simulator::ICacheCheckingEnabled;
-        Simulator::ICacheCheckingEnabled = false;
+        SimulatorProcess::ICacheCheckingDisableCount++;
 #endif
 
         RedirectJitCodeToInterruptCheck(cx, (CONTEXT*)context);
 
 #if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
-        Simulator::ICacheCheckingEnabled = prevICacheCheckingState;
-        cx->simulator()->cacheInvalidatedBySignalHandler_ = true;
+        SimulatorProcess::cacheInvalidatedBySignalHandler_ = true;
+        SimulatorProcess::ICacheCheckingDisableCount--;
 #endif
 
         cx->finishHandlingJitInterrupt();
     }
 }
 #endif
 
 static bool sTriedInstallSignalHandlers = false;
--- a/js/xpconnect/tests/unit/head_watchdog.js
+++ b/js/xpconnect/tests/unit/head_watchdog.js
@@ -90,23 +90,19 @@ function checkWatchdog(expectInterrupt, 
     busyWait(3000);
     do_check_true(!expectInterrupt);
     setInterruptCallback(undefined);
     setScriptTimeout(oldTimeout);
     continuation();
   });
 }
 
-var gGenerator;
-function continueTest() {
-  gGenerator.next();
-}
-
 function run_test() {
 
   // Run async.
   do_test_pending();
 
-  // Instantiate the generator and kick it off.
-  gGenerator = testBody();
-  gGenerator.next();
+  // Run the async function.
+  testBody().then(() => {
+    do_test_finished();
+  });
 }
 
--- a/js/xpconnect/tests/unit/test_watchdog_default.js
+++ b/js/xpconnect/tests/unit/test_watchdog_default.js
@@ -1,12 +1,9 @@
 /* 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/. */
 
-function testBody() {
+async function testBody() {
   // Check that we properly implement whatever behavior is specified by the
   // default profile for this configuration.
-  checkWatchdog(isWatchdogEnabled(), continueTest);
-  yield;
-  do_test_finished();
-  yield;
+  await checkWatchdog(isWatchdogEnabled());
 }
--- a/js/xpconnect/tests/unit/test_watchdog_disable.js
+++ b/js/xpconnect/tests/unit/test_watchdog_disable.js
@@ -1,11 +1,8 @@
 /* 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/. */
 
-function testBody() {
+async function testBody() {
   setWatchdogEnabled(false);
-  checkWatchdog(false, continueTest);
-  yield;
-  do_test_finished();
-  yield;
+  await checkWatchdog(false);
 }
--- a/js/xpconnect/tests/unit/test_watchdog_enable.js
+++ b/js/xpconnect/tests/unit/test_watchdog_enable.js
@@ -1,11 +1,8 @@
 /* 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/. */
 
-function testBody() {
+async function testBody() {
   setWatchdogEnabled(true);
-  checkWatchdog(true, continueTest);
-  yield;
-  do_test_finished();
-  yield;
+  await checkWatchdog(true);
 }
--- a/js/xpconnect/tests/unit/test_watchdog_hibernate.js
+++ b/js/xpconnect/tests/unit/test_watchdog_hibernate.js
@@ -1,13 +1,13 @@
 /* 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/. */
 
-function testBody() {
+async function testBody() {
 
   setWatchdogEnabled(true);
 
   // It's unlikely that we've ever hibernated at this point, but the timestamps
   // default to 0, so this should always be true.
   var now = Date.now() * 1000;
   var startHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStart");
   var stopHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStop");
@@ -18,20 +18,21 @@ function testBody() {
   do_check_true(startHibernation < now);
   do_check_true(stopHibernation < now);
 
   // When the watchdog runs, it hibernates if there's been no activity for the
   // last 2 seconds, otherwise it sleeps for 1 second. As such, given perfect
   // scheduling, we should never have more than 3 seconds of inactivity without
   // hibernating. To add some padding for automation, we mandate that hibernation
   // must begin between 2 and 5 seconds from now.
-  var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-  timer.initWithCallback(continueTest, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
-  simulateActivityCallback(false);
-  yield;
+  await new Promise(resolve => {
+    var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    timer.initWithCallback(resolve, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
+    simulateActivityCallback(false);
+  });
 
   simulateActivityCallback(true);
   busyWait(1000); // Give the watchdog time to wake up on the condvar.
   var stateChange = Cu.getWatchdogTimestamp("ContextStateChange");
   startHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStart");
   stopHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStop");
   do_log_info("Post-hibernation statistics:");
   do_log_info("stateChange: " + stateChange / 1000000);
@@ -42,12 +43,9 @@ function testBody() {
   // that occasionally get logged in windows automation. We're really just
   // making sure this behavior is roughly as expected on the macro scale,
   // so we add a 1 second fuzz factor here.
   const FUZZ_FACTOR = 1 * 1000 * 1000;
   do_check_true(stateChange > now + 10*1000*1000 - FUZZ_FACTOR);
   do_check_true(startHibernation > now + 2*1000*1000 - FUZZ_FACTOR);
   do_check_true(startHibernation < now + 5*1000*1000 + FUZZ_FACTOR);
   do_check_true(stopHibernation > now + 10*1000*1000 - FUZZ_FACTOR);
-
-  do_test_finished();
-  yield;
 }
--- a/js/xpconnect/tests/unit/test_watchdog_toggle.js
+++ b/js/xpconnect/tests/unit/test_watchdog_toggle.js
@@ -1,13 +1,10 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-function testBody() {
+async function testBody() {
   var defaultBehavior = isWatchdogEnabled();
   setWatchdogEnabled(!defaultBehavior);
   setWatchdogEnabled(defaultBehavior);
-  checkWatchdog(defaultBehavior, continueTest);
-  yield;
-  do_test_finished();
-  yield;
+  await checkWatchdog(defaultBehavior);
 }
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -587,27 +587,31 @@ bool
 HttpChannelParent::ConnectChannel(const uint32_t& registrarId, const bool& shouldIntercept)
 {
   nsresult rv;
 
   LOG(("HttpChannelParent::ConnectChannel: Looking for a registered channel "
        "[this=%p, id=%lu]\n", this, registrarId));
   nsCOMPtr<nsIChannel> channel;
   rv = NS_LinkRedirectChannels(registrarId, this, getter_AddRefs(channel));
+  if (NS_FAILED(rv)) {
+    NS_ERROR("Could not find the http channel to connect its IPC parent");
+    // This makes the channel delete itself safely.  It's the only thing
+    // we can do now, since this parent channel cannot be used and there is
+    // no other way to tell the child side there were something wrong.
+    Delete();
+    return true;
+  }
+
   // It's safe to cast here since the found parent-side real channel is ensured
   // to be http (nsHttpChannel).  ConnectChannel called from HttpChannelParent::Init
   // can only be called for http channels.  It's bound by ipdl.
   mChannel = static_cast<nsHttpChannel*>(channel.get());
   LOG(("  found channel %p, rv=%08x", mChannel.get(), rv));
 
-  if (NS_FAILED(rv)) {
-    NS_ERROR("Could not find the http channel to connect its IPC parent");
-    return false;
-  }
-
   nsCOMPtr<nsINetworkInterceptController> controller;
   NS_QueryNotificationCallbacks(channel, controller);
   RefPtr<HttpChannelParentListener> parentListener = do_QueryObject(controller);
   MOZ_ASSERT(parentListener);
   parentListener->SetupInterceptionAfterRedirect(shouldIntercept);
 
   if (mPBOverride != kPBOverride_Unset) {
     // redirected-to channel may not support PB
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -2181,16 +2181,21 @@ nsHttpChannel::ContinueProcessResponse2(
             }
 
             rv = StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
             if (NS_SUCCEEDED(rv)) {
                 return NS_OK;
             }
         }
 
+        // Don't cache uninformative 304
+        if (mCustomConditionalRequest) {
+            CloseCacheEntry(false);
+        }
+
         if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
             rv = ProcessNormal();
         }
         break;
     case 401:
     case 407:
         if (MOZ_UNLIKELY(mCustomAuthHeader) && httpStatus == 401) {
             // When a custom auth header fails, we don't want to try
@@ -3540,16 +3545,25 @@ nsHttpChannel::OpenCacheEntry(bool isHtt
     else if (BYPASS_LOCAL_CACHE(mLoadFlags) && !mApplicationCache) {
         cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
     }
     else {
         cacheEntryOpenFlags = nsICacheStorage::OPEN_NORMALLY
                             | nsICacheStorage::CHECK_MULTITHREADED;
     }
 
+    // Remember the request is a custom conditional request so that we can
+    // process any 304 response correctly.
+    mCustomConditionalRequest =
+        mRequestHead.HasHeader(nsHttp::If_Modified_Since) ||
+        mRequestHead.HasHeader(nsHttp::If_None_Match) ||
+        mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) ||
+        mRequestHead.HasHeader(nsHttp::If_Match) ||
+        mRequestHead.HasHeader(nsHttp::If_Range);
+
     if (!mPostID && mApplicationCache) {
         rv = cacheStorageService->AppCacheStorage(info,
             mApplicationCache,
             getter_AddRefs(cacheStorage));
     } else if (PossiblyIntercepted()) {
         // The synthesized cache has less restrictions on file size and so on.
         rv = cacheStorageService->SynthesizedCacheStorage(info,
             getter_AddRefs(cacheStorage));
@@ -3707,25 +3721,16 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
     CacheControlParser cacheControlRequest(cacheControlRequestHeader);
 
     if (cacheControlRequest.NoStore()) {
         LOG(("Not using cached response based on no-store request cache directive\n"));
         *aResult = ENTRY_NOT_WANTED;
         return NS_OK;
     }
 
-    // Remember the request is a custom conditional request so that we can
-    // process any 304 response correctly.
-    mCustomConditionalRequest =
-        mRequestHead.HasHeader(nsHttp::If_Modified_Since) ||
-        mRequestHead.HasHeader(nsHttp::If_None_Match) ||
-        mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) ||
-        mRequestHead.HasHeader(nsHttp::If_Match) ||
-        mRequestHead.HasHeader(nsHttp::If_Range);
-
     // Be pessimistic: assume the cache entry has no useful data.
     *aResult = ENTRY_WANTED;
     mCachedContentIsValid = false;
 
     nsXPIDLCString buf;
 
     // Get the method that was used to generate the cached response
     rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
@@ -3933,16 +3938,23 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
             LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
             doValidation = false;
         }
     }
     // check if validation is strictly required...
     else if (mCachedResponseHead->MustValidate()) {
         LOG(("Validating based on MustValidate() returning TRUE\n"));
         doValidation = true;
+    // possibly serve from cache for a custom If-Match/If-Unmodified-Since
+    // conditional request
+    } else if (mCustomConditionalRequest &&
+               !mRequestHead.HasHeader(nsHttp::If_Match) &&
+               !mRequestHead.HasHeader(nsHttp::If_Unmodified_Since)) {
+        LOG(("Validating based on a custom conditional request\n"));
+        doValidation = true;
     } else {
         // previously we also checked for a query-url w/out expiration
         // and didn't do heuristic on it. but defacto that is allowed now.
         //
         // Check if the cache entry has expired...
 
         uint32_t now = NowInSeconds();
 
@@ -4018,16 +4030,19 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
              !requestedETag.Equals(cachedETag))) {
             // User has defined If-Match header, if the cached entry is not
             // matching the provided header value or the cached ETag is weak,
             // force validation.
             doValidation = true;
         }
     }
 
+    // Previous error should not be propagated.
+    rv = NS_OK;
+
     if (!doValidation) {
         //
         // Check the authorization headers used to generate the cache entry.
         // We must validate the cache entry if:
         //
         // 1) the cache entry was generated prior to this session w/
         //    credentials (see bug 103402).
         // 2) the cache entry was generated w/o credentials, but would now
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_bug482934.js
@@ -0,0 +1,169 @@
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var response_code;
+var response_body;
+
+var request_time;
+var response_time;
+
+var cache_storage;
+
+var httpserver = new HttpServer();
+httpserver.start(-1);
+
+var base_url = "http://localhost:" + httpserver.identity.primaryPort;
+var resource = "/resource";
+var resource_url = base_url + resource;
+
+// Test flags
+var hit_server = false;
+
+function make_channel(aUrl)
+{
+  // Reset test global status
+  hit_server = false;
+
+  var req = NetUtil.newChannel({uri: aUrl, loadUsingSystemPrincipal: true});
+  req.QueryInterface(Ci.nsIHttpChannel);
+  req.setRequestHeader("If-Modified-Since", request_time, false);
+  return req;
+}
+
+function make_uri(aUrl)
+{
+  var ios = Cc["@mozilla.org/network/io-service;1"].
+            getService(Ci.nsIIOService);
+  return ios.newURI(aUrl, null, null);
+}
+
+function resource_handler(aMetadata, aResponse)
+{
+  hit_server = true;
+  do_check_true(aMetadata.hasHeader("If-Modified-Since"));
+  do_check_eq(aMetadata.getHeader("If-Modified-Since"), request_time);
+
+  if (response_code == "200") {
+    aResponse.setStatusLine(aMetadata.httpVersion, 200, "OK");
+    aResponse.setHeader("Content-Type", "text/plain", false);
+    aResponse.setHeader("Last-Modified", response_time, false);
+
+    aResponse.bodyOutputStream.write(response_body, response_body.length);
+  } else if (response_code == "304") {
+    aResponse.setStatusLine(aMetadata.httpVersion, 304, "Not Modified");
+    aResponse.setHeader("Returned-From-Handler", "1");
+  }
+}
+
+function check_cached_data(aCachedData, aCallback)
+{
+  asyncOpenCacheEntry(resource_url, "disk", Ci.nsICacheStorage.OPEN_READONLY, null,
+    function(aStatus, aEntry) {
+      do_check_eq(aStatus, Cr.NS_OK);
+      pumpReadStream(aEntry.openInputStream(0), function(aData) {
+        do_check_eq(aData, aCachedData);
+        aCallback();
+      });
+    }
+  );
+}
+
+function run_test()
+{
+  do_get_profile();
+  evict_cache_entries();
+
+  if (!newCacheBackEndUsed()) {
+    do_check_true(true, "This test doesn't run when the old cache back end is used since it depends on the new APIs.");
+    return;
+  }
+
+  do_test_pending();
+
+  cache_storage = getCacheStorage("disk");
+  httpserver.registerPathHandler(resource, resource_handler);
+
+  wait_for_cache_index(run_next_test);
+}
+
+// 1. send custom conditional request when we don't have an entry
+//    server returns 304 -> client receives 304
+add_test(() => {
+  response_code = "304";
+  response_body = "";
+  request_time = "Thu, 1 Jan 2009 00:00:00 GMT";
+  response_time = "Thu, 1 Jan 2009 00:00:00 GMT";
+
+  var ch = make_channel(resource_url);
+  ch.asyncOpen2(new ChannelListener(function(aRequest, aData) {
+    do_check_true(hit_server);
+    do_check_eq(aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus, 304);
+    do_check_false(cache_storage.exists(make_uri(resource_url), ""));
+    do_check_eq(aRequest.getResponseHeader("Returned-From-Handler"), "1");
+
+    run_next_test();
+  }, null));
+});
+
+// 2. send custom conditional request when we don't have an entry
+//    server returns 200 -> result is cached
+add_test(() => {
+  response_code = "200";
+  response_body = "content_body";
+  request_time = "Thu, 1 Jan 2009 00:00:00 GMT";
+  response_time = "Fri, 2 Jan 2009 00:00:00 GMT";
+
+  var ch = make_channel(resource_url);
+  ch.asyncOpen2(new ChannelListener(function(aRequest, aData) {
+    do_check_true(hit_server);
+    do_check_eq(aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+    do_check_true(cache_storage.exists(make_uri(resource_url), ""));
+
+    check_cached_data(response_body, run_next_test);
+  }, null));
+});
+
+// 3. send custom conditional request when we have an entry
+//    server returns 304 -> client receives 304 and cached entry is unchanged
+add_test(() => {
+  response_code = "304";
+  var cached_body = response_body;
+  response_body = "";
+  request_time = "Fri, 2 Jan 2009 00:00:00 GMT";
+  response_time = "Fri, 2 Jan 2009 00:00:00 GMT";
+
+  var ch = make_channel(resource_url);
+  ch.asyncOpen2(new ChannelListener(function(aRequest, aData) {
+    do_check_true(hit_server);
+    do_check_eq(aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus, 304);
+    do_check_true(cache_storage.exists(make_uri(resource_url), ""));
+    do_check_eq(aRequest.getResponseHeader("Returned-From-Handler"), "1");
+    do_check_eq(aData, "");
+
+    // Check the cache data is not changed
+    check_cached_data(cached_body, run_next_test);
+  }, null));
+});
+
+// 4. send custom conditional request when we have an entry
+//    server returns 200 -> result is cached
+add_test(() => {
+  response_code = "200";
+  response_body = "updated_content_body";
+  request_time = "Fri, 2 Jan 2009 00:00:00 GMT";
+  response_time = "Sat, 3 Jan 2009 00:00:00 GMT";
+  var ch = make_channel(resource_url);
+  ch.asyncOpen2(new ChannelListener(function(aRequest, aData) {
+    do_check_true(hit_server);
+    do_check_eq(aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
+    do_check_true(cache_storage.exists(make_uri(resource_url), ""));
+
+    // Check the cache data is updated
+    check_cached_data(response_body, () => {
+      run_next_test();
+      httpserver.stop(do_test_finished);
+    });
+  }, null));
+});
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -130,16 +130,17 @@ requesttimeoutfactor = 2
 [test_bug455598.js]
 [test_bug468426.js]
 [test_bug468594.js]
 [test_bug470716.js]
 [test_bug477578.js]
 [test_bug479413.js]
 [test_bug479485.js]
 [test_bug482601.js]
+[test_bug482934.js]
 [test_bug484684.js]
 [test_bug490095.js]
 # Bug 675039: intermittent fail on Android-armv6
 skip-if = os == "android"
 [test_bug504014.js]
 [test_bug510359.js]
 [test_bug515583.js]
 [test_bug528292.js]
--- a/taskcluster/taskgraph/target_tasks.py
+++ b/taskcluster/taskgraph/target_tasks.py
@@ -82,19 +82,30 @@ def target_tasks_default(full_task_graph
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('ash_tasks')
 def target_tasks_ash(full_task_graph, parameters):
     """Target tasks that only run on the ash branch."""
     def filter(task):
         platform = task.attributes.get('build_platform')
-        # only select platforms
-        if platform not in ('linux32', 'linux32-pgo', 'linux64', 'linux64-asan', 'linux64-pgo'):
+        # Early return if platform is None
+        if not platform:
+            return False
+        # Only on Linux platforms
+        if 'linux' not in platform:
             return False
+        # No random non-build jobs either. This is being purposely done as a
+        # blacklist so newly-added jobs aren't missed by default.
+        for p in ('nightly', 'haz', 'artifact', 'cov', 'add-on'):
+            if p in platform:
+                return False
+        for k in ('toolchain', 'l10n', 'static-analysis'):
+            if k in task.attributes['kind']:
+                return False
         # and none of this linux64-asan/debug stuff
         if platform == 'linux64-asan' and task.attributes['build_type'] == 'debug':
             return False
         # no non-e10s tests
         if task.attributes.get('unittest_suite'):
             if not task.attributes.get('e10s'):
                 return False
             # don't run talos on ash
--- a/testing/talos/talos/talos_process.py
+++ b/testing/talos/talos/talos_process.py
@@ -29,17 +29,25 @@ class ProcessContext(object):
 
     def kill_process(self):
         """
         Kill the process, returning the exit code or None if the process
         is already finished.
         """
         if self.process and self.process.is_running():
             LOG.debug("Terminating %s" % self.process)
-            self.process.terminate()
+            try:
+                self.process.terminate()
+            except psutil.NoSuchProcess:
+                procs = self.process.children()
+                for p in procs:
+                    c = ProcessContext()
+                    c.process = p
+                    c.kill_process()
+                return self.process.returncode
             try:
                 return self.process.wait(3)
             except psutil.TimeoutExpired:
                 self.process.kill()
                 # will raise TimeoutExpired if unable to kill
                 return self.process.wait(3)
 
 
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -80969,16 +80969,24 @@
    "XMLHttpRequest/xmlhttprequest-timeout-overridesexpires.html": [
     [
      "/XMLHttpRequest/xmlhttprequest-timeout-overridesexpires.html",
      {
       "timeout": "long"
      }
     ]
    ],
+   "XMLHttpRequest/xmlhttprequest-timeout-reused.html": [
+    [
+     "/XMLHttpRequest/xmlhttprequest-timeout-reused.html",
+     {
+      "timeout": "long"
+     }
+    ]
+   ],
    "XMLHttpRequest/xmlhttprequest-timeout-simple.html": [
     [
      "/XMLHttpRequest/xmlhttprequest-timeout-simple.html",
      {
       "timeout": "long"
      }
     ]
    ],
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/open-url-bogus.htm.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[open-url-bogus.htm]
-  type: testharness
-  [XMLHttpRequest: open() - bogus URLs (http:)]
-    expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/xmlhttprequest-timeout-reused.html
@@ -0,0 +1,48 @@
+ <!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8" />
+    <title>XHR2 Timeout Property Tests</title>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#timeout-error" />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#the-timeout-attribute" data-tested-assertations="following::ol[1]/li[2]" />
+    <link rel="help" href="https://xhr.spec.whatwg.org/#handler-xhr-ontimeout" data-tested-assertations="../.."/>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#timeout-error" data-tested-assertations=".."/>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#request-error" data-tested-assertations="following::ol[1]/li[9]"/>
+    <link rel="help" href="https://xhr.spec.whatwg.org/#infrastructure-for-the-send()-method" data-tested-assertations="following-sibling::dl//code[contains(@title,'dom-XMLHttpRequest-timeout')]/.. following-sibling::dl//code[contains(@title,'dom-XMLHttpRequest-timeout')]/../following-sibling::dd following::dt[1] following::dd[1]" />
+    <meta name=timeout content=long>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+    <div id="log"></div>
+    <script type="text/javascript">
+
+function startRequest() {
+    xhr.open("GET", "./resources/content.py?content=Hi", true);
+    xhr.timeout = 2000;
+    setTimeout(function () {
+      xhr.send();
+    }, 1000);
+}
+
+var test = async_test();
+test.step(function()
+{
+    var count = 0;
+    xhr = new XMLHttpRequest();
+    xhr.onload = function () {
+        assert_equals(xhr.response, "Hi");
+        if (++count == 2) {
+            test.done();
+        }
+    }
+    xhr.ontimeout = function () {
+      assert_unreached("HTTP error should not timeout");
+    }
+    startRequest();
+    setTimeout(startRequest, 3500);
+});
+
+    </script>
+</body>
+</html>
--- a/toolkit/components/osfile/tests/xpcshell/test_available_free_space.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_available_free_space.js
@@ -15,17 +15,17 @@ function run_test() {
   Services.prefs.setBoolPref("toolkit.osfile.log", true);
 
   run_next_test();
 }
 
 /**
  * Test OS.File.getAvailableFreeSpace
  */
-add_task(function() {
+add_task(function*() {
   // Set up profile. We will use profile path to query for available free
   // space.
   do_get_profile();
 
   let dir = OS.Constants.Path.profileDir;
 
   // Sanity checking for the test
   do_check_true((yield OS.File.exists(dir)));
--- a/toolkit/components/osfile/tests/xpcshell/test_compression.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_compression.js
@@ -5,17 +5,17 @@
 
 Components.utils.import("resource://gre/modules/osfile.jsm");
 
 function run_test() {
   do_test_pending();
   run_next_test();
 }
 
-add_task(function test_compress_lz4() {
+add_task(function* test_compress_lz4() {
   let path = OS.Path.join(OS.Constants.Path.tmpDir, "compression.lz");
   let length = 1024;
   let array = new Uint8Array(length);
   for (let i = 0; i < array.byteLength; ++i) {
     array[i] = i;
   }
   let arrayAsString = Array.prototype.join.call(array);
 
@@ -24,17 +24,17 @@ add_task(function test_compress_lz4() {
   do_print("Compressed " + length + " bytes into " + bytes);
 
   do_print("Reading back with lz4 decompression");
   let decompressed = yield OS.File.read(path, { compression: "lz4" });
   do_print("Decompressed into " + decompressed.byteLength + " bytes");
   do_check_eq(arrayAsString, Array.prototype.join.call(decompressed));
 });
 
-add_task(function test_uncompressed() {
+add_task(function* test_uncompressed() {
   do_print("Writing data without compression");
   let path = OS.Path.join(OS.Constants.Path.tmpDir, "no_compression.tmp");
   let array = new Uint8Array(1024);
   for (let i = 0; i < array.byteLength; ++i) {
     array[i] = i;
   }
   let bytes = yield OS.File.writeAtomic(path, array); // No compression
 
@@ -45,17 +45,17 @@ add_task(function test_uncompressed() {
   } catch (ex) {
     exn = ex;
   }
   do_check_true(!!exn);
   // Check the exception message (and that it contains the file name)
   do_check_true(exn.message.indexOf(`Invalid header (no magic number) - Data: ${ path }`) != -1);
 });
 
-add_task(function test_no_header() {
+add_task(function* test_no_header() {
   let path = OS.Path.join(OS.Constants.Path.tmpDir, "no_header.tmp");
   let array = new Uint8Array(8).fill(0,0);  // Small array with no header
 
   do_print("Writing data with no header");
 
   let bytes = yield OS.File.writeAtomic(path, array); // No compression
   let exn;
   // Force decompression, reading should fail
@@ -64,17 +64,17 @@ add_task(function test_no_header() {
   } catch (ex) {
     exn = ex;
   }
   do_check_true(!!exn);
   // Check the exception message (and that it contains the file name)
   do_check_true(exn.message.indexOf(`Buffer is too short (no header) - Data: ${ path }`) != -1);
 });
 
-add_task(function test_invalid_content() {
+add_task(function* test_invalid_content() {
   let path = OS.Path.join(OS.Constants.Path.tmpDir, "invalid_content.tmp");
   let arr1 = new Uint8Array([109, 111, 122, 76, 122, 52, 48, 0]);
   let arr2 = new Uint8Array(248).fill(1,0);
 
   let array = new Uint8Array(arr1.length + arr2.length);
   array.set(arr1);
   array.set(arr2, arr1.length);
 
@@ -90,9 +90,9 @@ add_task(function test_invalid_content()
   }
   do_check_true(!!exn);
   // Check the exception message (and that it contains the file name)
   do_check_true(exn.message.indexOf(`Invalid content: Decompression stopped at 0 - Data: ${ path }`) != -1);
 });
 
 add_task(function() {
   do_test_finished();
-});
\ No newline at end of file
+});
--- a/toolkit/components/osfile/tests/xpcshell/test_creationDate.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_creationDate.js
@@ -4,17 +4,17 @@ function run_test() {
   do_test_pending();
   run_next_test();
 }
 
 /**
  * Test to ensure that deprecation warning is issued on use
  * of creationDate.
  */
-add_task(function test_deprecatedCreationDate () {
+add_task(function* test_deprecatedCreationDate () {
   let deferred = Promise.defer();
   let consoleListener = {
     observe: function (aMessage) {
       if(aMessage.message.indexOf("Field 'creationDate' is deprecated.") > -1) {
         do_print("Deprecation message printed");
         do_check_true(true);
         Services.console.unregisterListener(consoleListener);
         deferred.resolve();
--- a/toolkit/components/osfile/tests/xpcshell/test_exception.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_exception.js
@@ -7,17 +7,17 @@
 
 "use strict";
 
 var EXISTING_FILE = do_get_file("xpcshell.ini").path;
 
 
 // Tests on |open|
 
-add_test_pair(function test_typeerror() {
+add_test_pair(function* test_typeerror() {
   let exn;
   try {
     let fd = yield OS.File.open("/tmp", {no_such_key: 1});
     do_print("Fd: " + fd);
   } catch (ex) {
     exn = ex;
   }
   do_print("Exception: " + exn);
--- a/toolkit/components/osfile/tests/xpcshell/test_makeDir.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_makeDir.js
@@ -73,17 +73,17 @@ add_task(function* test_root() {
   } else {
     yield OS.File.makeDir("/");
   }
 });
 
 /**
  * Creating subdirectories
  */
-add_task(function test_option_from() {
+add_task(function* test_option_from() {
   let dir = Path.join(profileDir, "a", "b", "c");
 
   // Sanity checking for the test
   do_check_false((yield OS.File.exists(dir)));
 
   // Make a directory
   yield OS.File.makeDir(dir, {from: profileDir});
 
--- a/toolkit/components/osfile/tests/xpcshell/test_open.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_open.js
@@ -10,17 +10,17 @@ function run_test() {
 }
 
 /**
  * Test OS.File.open for reading:
  * - with an existing file (should succeed);
  * - with a non-existing file (should fail);
  * - with inconsistent arguments (should fail).
  */
-add_task(function() {
+add_task(function*() {
   // Attempt to open a file that does not exist, ensure that it yields the
   // appropriate error.
   try {
     let fd = yield OS.File.open(OS.Path.join(".", "This file does not exist"));
     do_check_true(false, "File opening 1 succeeded (it should fail)");
   } catch (err if err instanceof OS.File.Error && err.becauseNoSuchFile) {
     do_print("File opening 1 failed " + err);
   }
@@ -50,17 +50,17 @@ add_task(function() {
   do_print("Attempting to close a file again");
   yield openedFile.close();
 });
 
 /**
  * Test the error thrown by OS.File.open when attempting to open a directory
  * that does not exist.
  */
-add_task(function test_error_attributes () {
+add_task(function* test_error_attributes () {
 
   let dir = OS.Path.join(do_get_profile().path, "test_osfileErrorAttrs");
   let fpath = OS.Path.join(dir, "test_error_attributes.txt");
 
   try {
     yield OS.File.open(fpath, {truncate: true}, {});
     do_check_true(false, "Opening path suceeded (it should fail) " + fpath);
   } catch (err) {
--- a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_append.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_append.js
@@ -18,17 +18,17 @@ function setup_mode(mode) {
   };
   for (let k in mode) {
     realMode[k] = mode[k];
   }
   return realMode;
 }
 
 // Test append mode.
-function test_append(mode) {
+function* test_append(mode) {
   let path = OS.Path.join(OS.Constants.Path.tmpDir,
                           "test_osfile_async_append.tmp");
 
   // Clear any left-over files from previous runs.
   try {
     yield OS.File.remove(path);
   } catch (ex if ex.becauseNoSuchFile) {
     // ignore
@@ -59,17 +59,17 @@ function test_append(mode) {
       yield OS.File.remove(path);
     } catch (ex if ex.becauseNoSuchFile) {
       // ignore.
     }
   }
 }
 
 // Test no-append mode.
-function test_no_append(mode) {
+function* test_no_append(mode) {
   let path = OS.Path.join(OS.Constants.Path.tmpDir,
                           "test_osfile_async_noappend.tmp");
 
   // Clear any left-over files from previous runs.
   try {
     yield OS.File.remove(path);
   } catch (ex if ex.becauseNoSuchFile) {
     // ignore
--- a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_copy.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_copy.js
@@ -59,28 +59,28 @@ var reference_fetch_file = function refe
  *
  * Used for comparing xpcom-based results to os.file-based results.
  *
  * @param {string} a The _absolute_ path to the first file.
  * @param {string} b The _absolute_ path to the second file.
  *
  * @resolves {null}
  */
-var reference_compare_files = function reference_compare_files(a, b) {
-  let a_contents = yield reference_fetch_file(a);
-  let b_contents = yield reference_fetch_file(b);
+var reference_compare_files = async function reference_compare_files(a, b) {
+  let a_contents = await reference_fetch_file(a);
+  let b_contents = await reference_fetch_file(b);
   // Not using do_check_eq to avoid dumping the whole file to the log.
   // It is OK to === compare here, as both variables contain a string.
   do_check_true(a_contents === b_contents);
 };
 
 /**
  * Test to ensure that OS.File.copy works.
  */
-function test_copymove(options = {}) {
+function* test_copymove(options = {}) {
   let source = OS.Path.join((yield OS.File.getCurrentDirectory()),
                             EXISTING_FILE);
   let dest = OS.Path.join(OS.Constants.Path.tmpDir,
                           "test_osfile_async_copy_dest.tmp");
   let dest2 = OS.Path.join(OS.Constants.Path.tmpDir,
                            "test_osfile_async_copy_dest2.tmp");
   try {
     // 1. Test copy.
--- a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_flush.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_flush.js
@@ -7,17 +7,17 @@ function run_test() {
   do_test_pending();
   run_next_test();
 }
 
 /**
  * Test to ensure that |File.prototype.flush| is available in the async API.
  */
 
-add_task(function test_flush() {
+add_task(function* test_flush() {
   let path = OS.Path.join(OS.Constants.Path.tmpDir,
                           "test_osfile_async_flush.tmp");
   let file = yield OS.File.open(path, {trunc: true, write: true});
   try {
     try {
       yield file.flush();
     } finally {
       yield file.close();
--- a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_largefiles.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_largefiles.js
@@ -8,17 +8,17 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/Task.jsm");
 
 /**
  * A test to check that .getPosition/.setPosition work with large files.
  * (see bug 952997)
  */
 
 // Test setPosition/getPosition.
-function test_setPosition(forward, current, backward) {
+function* test_setPosition(forward, current, backward) {
   let path = OS.Path.join(OS.Constants.Path.tmpDir,
                           "test_osfile_async_largefiles.tmp");
 
   // Clear any left-over files from previous runs.
   try {
     yield OS.File.remove(path);
   } catch (ex if ex.becauseNoSuchFile) {
     // ignore
@@ -57,17 +57,17 @@ function test_setPosition(forward, curre
     } catch (ex if ex.becauseNoSuchFile) {
       // ignore.
     }
     do_throw(ex);
   }
 }
 
 // Test setPosition/getPosition expected failures.
-function test_setPosition_failures() {
+function* test_setPosition_failures() {
   let path = OS.Path.join(OS.Constants.Path.tmpDir,
                           "test_osfile_async_largefiles.tmp");
 
   // Clear any left-over files from previous runs.
   try {
     yield OS.File.remove(path);
   } catch (ex if ex.becauseNoSuchFile) {
     // ignore
--- a/toolkit/components/osfile/tests/xpcshell/test_osfile_closed.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_closed.js
@@ -3,17 +3,17 @@
 Components.utils.import("resource://gre/modules/osfile.jsm");
 Components.utils.import("resource://gre/modules/Task.jsm");
 
 function run_test() {
   do_test_pending();
   run_next_test();
 }
 
-add_task(function test_closed() {
+add_task(function* test_closed() {
   OS.Shared.DEBUG = true;
   let currentDir = yield OS.File.getCurrentDirectory();
   do_print("Open a file, ensure that we can call stat()");
   let path = OS.Path.join(currentDir, "test_osfile_closed.js");
   let file = yield OS.File.open(path);
   yield file.stat();
   do_check_true(true);
 
--- a/toolkit/components/osfile/tests/xpcshell/test_osfile_writeAtomic_backupTo_option.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_writeAtomic_backupTo_option.js
@@ -14,29 +14,27 @@ Components.utils.import("resource://gre/
  * test_backupTo_option_without_tmpPath.tmp.backup
  * test_non_backupTo_option.tmp
  * test_non_backupTo_option.tmp.backup
  * test_backupTo_option_without_destination_file.tmp
  * test_backupTo_option_without_destination_file.tmp.backup
  * test_backupTo_option_with_backup_file.tmp
  * test_backupTo_option_with_backup_file.tmp.backup
  */
-function clearFiles() {
-  return Task.spawn(function () {
-    let files = ["test_backupTo_option_with_tmpPath.tmp",
-                  "test_backupTo_option_without_tmpPath.tmp",
-                  "test_non_backupTo_option.tmp",
-                  "test_backupTo_option_without_destination_file.tmp",
-                  "test_backupTo_option_with_backup_file.tmp"];
-    for (let file of files) {
-      let path = Path.join(Constants.Path.tmpDir, file);
-      yield File.remove(path);
-      yield File.remove(path + ".backup");
-    }
-  });
+async function clearFiles() {
+  let files = ["test_backupTo_option_with_tmpPath.tmp",
+               "test_backupTo_option_without_tmpPath.tmp",
+               "test_non_backupTo_option.tmp",
+               "test_backupTo_option_without_destination_file.tmp",
+               "test_backupTo_option_with_backup_file.tmp"];
+  for (let file of files) {
+    let path = Path.join(Constants.Path.tmpDir, file);
+    await File.remove(path);
+    await File.remove(path + ".backup");
+  }
 }
 
 function run_test() {
   run_next_test();
 }
 
 add_task(function* init() {
   yield clearFiles();
--- a/toolkit/components/osfile/tests/xpcshell/test_removeDir.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_removeDir.js
@@ -13,17 +13,17 @@ do_register_cleanup(function() {
 });
 
 function run_test() {
   Services.prefs.setBoolPref("toolkit.osfile.log", true);
 
   run_next_test();
 }
 
-add_task(function() {
+add_task(function*() {
   // Set up profile. We create the directory in the profile, because the profile
   // is removed after every test run.
   do_get_profile();
 
   let file = OS.Path.join(OS.Constants.Path.profileDir, "file");
   let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory");
   let file1 = OS.Path.join(dir, "file1");
   let file2 = OS.Path.join(dir, "file2");
--- a/toolkit/components/osfile/tests/xpcshell/test_removeEmptyDir.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_removeEmptyDir.js
@@ -15,17 +15,17 @@ function run_test() {
   Services.prefs.setBoolPref("toolkit.osfile.log", true);
 
   run_next_test();
 }
 
 /**
  * Test OS.File.removeEmptyDir
  */
-add_task(function() {
+add_task(function*() {
   // Set up profile. We create the directory in the profile, because the profile
   // is removed after every test run.
   do_get_profile();
 
   let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory");
 
   // Sanity checking for the test
   do_check_false((yield OS.File.exists(dir)));
--- a/toolkit/components/osfile/tests/xpcshell/test_shutdown.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_shutdown.js
@@ -5,24 +5,24 @@ Components.utils.import("resource://gre/
 
 add_task(function init() {
   do_get_profile();
 });
 
 /**
  * Test logging of file descriptors leaks.
  */
-add_task(function system_shutdown() {
+add_task(function* system_shutdown() {
 
   // Test that unclosed files cause warnings
   // Test that unclosed directories cause warnings
   // Test that closed files do not cause warnings
   // Test that closed directories do not cause warnings
   function testLeaksOf(resource, topic) {
-    return Task.spawn(function() {
+    return Task.spawn(function*() {
       let deferred = Promise.defer();
 
       // Register observer
       Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
       Services.prefs.setBoolPref("toolkit.osfile.log", true);
       Services.prefs.setBoolPref("toolkit.osfile.log.redirect", true);
       Services.prefs.setCharPref("toolkit.osfile.test.shutdown.observer", topic);
 
@@ -64,17 +64,17 @@ add_task(function system_shutdown() {
         resolved = false;
       }
       Services.console.unregisterListener(observer);
       Services.prefs.clearUserPref("toolkit.osfile.log");
       Services.prefs.clearUserPref("toolkit.osfile.log.redirect");
       Services.prefs.clearUserPref("toolkit.osfile.test.shutdown.observer");
       Services.prefs.clearUserPref("toolkit.async_shutdown.testing");
 
-      throw new Task.Result(resolved);
+      return resolved;
     });
   }
 
   let TEST_DIR = OS.Path.join((yield OS.File.getCurrentDirectory()), "..");
   do_print("Testing for leaks of directory iterator " + TEST_DIR);
   let iterator = new OS.File.DirectoryIterator(TEST_DIR);
   do_print("At this stage, we leak the directory");
   do_check_true((yield testLeaksOf(TEST_DIR, "test.shutdown.dir.leak")));
--- a/toolkit/components/osfile/tests/xpcshell/test_unique.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_unique.js
@@ -4,17 +4,17 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/Task.jsm");
 
 function run_test() {
   do_get_profile();
   run_next_test();
 }
 
 function testFiles(filename) {
-  return Task.spawn(function() {
+  return Task.spawn(function*() {
     const MAX_TRIES = 10;
     let profileDir = OS.Constants.Path.profileDir;
     let path = OS.Path.join(profileDir, filename);
 
     // Ensure that openUnique() uses the file name if there is no file with that name already.
     let openedFile = yield OS.File.openUnique(path);
     do_print("\nCreate new file: " + openedFile.path);
     yield openedFile.file.close();
@@ -74,15 +74,15 @@ function testFiles(filename) {
 
     do_print("Ensure that this raises the correct error");
     do_check_true(!!exn);
     do_check_true(exn instanceof OS.File.Error);
     do_check_true(exn.becauseExists);
   });
 }
 
-add_task(function test_unique() {
+add_task(function* test_unique() {
   OS.Shared.DEBUG = true;
   // Tests files with extension
   yield testFiles("dummy_unique_file.txt");
   // Tests files with no extension
   yield testFiles("dummy_unique_file_no_ext");
 });
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8247,17 +8247,17 @@
     "expires_in_version": "55",
     "kind": "enumerated",
     "n_values": 10,
     "releaseChannelCollection": "opt-out",
     "description": "How often are secure cookies set from non-secure origins, and vice-versa? 0=nonsecure/http, 1=nonsecure/https, 2=secure/http, 3=secure/https"
   },
   "COOKIE_LEAVE_SECURE_ALONE": {
     "alert_emails": ["seceng-telemetry@mozilla.com"],
-    "bug_numbers": [976073],
+    "bug_numbers": [976073, 1325909],
     "expires_in_version": "57",
     "kind": "enumerated",
     "n_values": 10,
     "releaseChannelCollection": "opt-out",
     "description": "Strict Secure Cookies: 0=blocked secure cookie from http; 1=blocked shadowing secure cookie from http; 2=shadowing secure cookie from https; 3=evicted newer insecure cookie; 4=evicted oldest insecure cookie; 5=evicted secure cookie; 6=evicting secure cookie blocked; 7=blocked downgrading secure cookie from http; 8=downgraded secure cookie from https"
   },
   "NTLM_MODULE_USED_2": {
     "expires_in_version": "never",
--- a/toolkit/components/telemetry/docs/collection/histograms.rst
+++ b/toolkit/components/telemetry/docs/collection/histograms.rst
@@ -1,5 +1,267 @@
 ==========
 Histograms
 ==========
 
-Recording into histograms is currently documented in `a MDN article <https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Adding_a_new_Telemetry_probe>`_.
+If a user has opted into submitting performance data to Mozilla, the Telemetry system will collect various measures of Firefox performance, hardware, usage and customizations and submit it to Mozilla. The Telemetry data collected by a single client can be examined from the integrated ``about:telemetry`` browser page, while the aggregated reports across entire user populations are publicly available at `https://telemetry.mozilla.org <https://telemetry.mozilla.org>`_.
+
+.. important::
+
+    Every new data collection in Firefox needs a `data collection review <https://wiki.mozilla.org/Firefox/Data_Collection#Requesting_Approval>`_ from a data collection peer. Just set the feedback? flag for :bsmedberg or one of the other data peers. We try to reply within a business day.
+
+The following sections explain how to add a new measurement to Telemetry.
+
+Overview
+========
+
+Telemetry histograms are an efficient way to collect numeric measurements like multiple counts or timings.
+They are collected through a common API and automatically submitted with the :doc:`main ping <../data/main-ping>`.
+
+.. hint::
+
+    Before adding a new histogram,  you should consider using other collection mechanisms. For example, if the need is to track a single scalar value (e.g. number, boolean or string), you should use :doc:`scalars`.
+
+The histogram below is taken from Firefox's ``about:telemetry`` page. It shows a histogram used for tracking plugin shutdown times and the data collected over a single Firefox session. The timing data is grouped into buckets where the height of the blue bars represents the number of items in each bucket. The tallest bar, for example, indicates that there were 63 plugin shutdowns lasting between 129ms and 204ms.
+
+.. image:: sampleHistogram.png
+
+The histograms on the ``about:telemetry`` page only show the non-empty buckets in a histogram except for the bucket to the left of the first non-empty bucket and the bucket to the right of the last non-empty bucket.
+
+Choosing a Histogram Type
+=========================
+
+The first step to adding a new histogram is to choose the histogram type that best represents the data being measured. The sample histogram used above is an "exponential" histogram.
+
+.. note::
+
+    Ony ``flag`` and ``count`` histograms have default values. All other histograms start out empty and are only submitted if a value is recorded.
+
+``boolean``
+-----------
+These histograms only record boolean values. Multiple boolean entries can be recorded in the same histogram during a single browsing session, e.g. if a histogram is measuring user choices in a dialog box with options "Yes" or "No", a new boolean value is added every time the dialog is displayed.
+
+``linear``
+----------
+Linear histograms are similar to enumerated histograms, except each bucket is associated with a range of values instead of a single enum value. The range of values covered by each bucket increases linearly from the previous bucket, e.g. one bucket might count the number of occurrences of values between 0 to 9, the next bucket would cover values 10-19, the next 20-29, etc. This bucket type is useful if there aren't orders of magnitude differences between the minimum and maximum values stored in the histogram, e.g. if the values you are storing are percentages 0-100%.
+
+.. note::
+
+    If you need a linear histogram with buckets < 0, 1, 2 ... N >, then you should declare an enumerated histogram. This restriction was added to prevent developers from making a common off-by-one mistake when specifying the number of buckets in a linear histogram.
+
+``exponential``
+---------------
+Exponential histograms are similar to linear histograms but the range of values covered by each bucket increases exponentially. As an example of its use, consider the timings of an I/O operation whose duration might normally fall in the range of 0ms-50ms but extreme cases might have durations in seconds or minutes. For such measurements, you would want finer-grained bucketing in the normal range but coarser-grained bucketing for the extremely large values. An exponential histogram fits this requirement since it has "narrow" buckets near the minimum value and significantly "wider" buckets near the maximum value.
+
+``categorical``
+---------------
+Categorical histograms are similar to enumerated histograms. However, instead of specifying ``n_buckets``, you specify an array of strings in the ``labels`` field. From JavaScript, the label values or their indices can be passed as strings to ``histogram.add()``. From C++ you can use ``AccumulateCategorical`` with passing a value from the corresponding ``Telemetry::LABEL_*`` enum, or, in exceptional cases the string values.
+
+.. note::
+
+    Categorical histograms by default support up to 50 labels, but you can set it higher using the `n_values` property. If you need to add more labels later, you need to use a new histogram name. The current Telemetry server does not support changing histogram declarations after the histogram has already been released. See `Changing a histogram`_ if you need to add more labels later.
+
+``enumerated``
+--------------
+This histogram type is intended for storing "enum" values, when you can't specify labels and thus cannot use ``categorical`` histograms. An enumerated histogram consists of a fixed number of *buckets*, each of which is associated with a consecutive integer value (the bucket's *label*). Each bucket corresponds to an enum value and counts the number of times its particular enum value was recorded.
+
+You might use this type of histogram if, for example, you wanted to track the relative popularity of SSL handshake types. Whenever the browser started an SSL handshake, it would record one of a limited number of enum values which uniquely identifies the handshake type.
+
+.. note::
+
+    Set ``n_buckets`` to a slightly larger value than needed to allow for new enum values in the future. See `Changing a histogram`_ if you need to add more enums later.
+
+``flag``
+--------
+*Deprecated* (please use boolean :doc:`scalars`).
+
+This histogram type allows you to record a single value (`0` or `1`, default `0`). This type is useful if you need to track whether a feature was ever used during a Firefox session. You only need to add a single line of code which sets the flag when the feature is used because the histogram is initialized with a default value of `0`/`false` (flag not set). Thus, recording a value of `0` is not allowed and asserts.
+
+Flag histograms will ignore any changes after the flag is set, so once the flag is set, it cannot be unset.
+
+``count``
+---------
+*Deprecated* (please use uint :doc:`scalars`).
+
+This histogram type is used when you want to record a count of something. It only stores a single value and defaults to `0`.
+
+Keyed Histograms
+----------------
+
+Keyed histograms are collections of one of the histogram types above, indexed by a string key. This is for example useful when you want to break down certain counts by a name, like how often searches happen with which search engine.
+Note that when you need to record for a small set of known keys, using separate plain histograms is more efficient.
+
+.. warning::
+
+    Keyed histograms are currently not supported in the `histogram change detector <https://alerts.telemetry.mozilla.org/index.html>`_.
+
+Declaring a Histogram
+=====================
+
+Histograms should be declared in the `Histograms.json <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Histograms.json>`_ file. These declarations are checked for correctness at `compile time <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/gen-histogram-data.py>`_ and used to generate C++ code.
+
+The following is a sample histogram declaration from ``Histograms.json`` for a histogram named ``MEMORY_RESIDENT`` which tracks the amount of resident memory used by a process:
+
+
+.. code-block:: json
+
+    "MEMORY_RESIDENT": {
+      "alert_emails": ["team@mozilla.xyz"],
+      "expires_in_version": "never",
+      "kind": "exponential",
+      "low": 32768,
+      "high": 1048576,
+      "n_buckets": 50,
+      "bug_numbers": [12345],
+      "description": "Resident memory size (KB)"
+    },
+
+Histograms which track timings in milliseconds or microseconds should suffix their names with ``"_MS"`` and ``"_US"`` respectively. Flag-type histograms should have the suffix ``"_FLAG"`` in their name.
+
+The possible fields in a histogram declaration are listed below.
+
+``alert_emails``
+----------------
+Required. This field is a list of e-mail addresses that should be notified when the distribution of the histogram changes significantly from one build-id to the other. This can be useful to detect regressions. Note that all alerts will be sent automatically to mozilla.dev.telemetry-alerts.
+
+``expires_in_version``
+----------------------
+Required. The version number in which the histogram expires; e.g. a value of `"30"` will mean that the histogram stops recording from Firefox 30 on. A version number of type ``"N"`` and ``"N.0"`` is automatically converted to ``"N.0a1"`` in order to expire the histogram also in the development channels. For histograms that never expire the value ``"never"`` can be used as in the example above. Accumulating data into an expired histogram is effectively a non-op and will not record anything.
+
+``kind``
+--------
+Required. One of the histogram types described in the previous section. Different histogram types require different fields to be present in the declaration.
+
+``keyed``
+---------
+Optional, boolean, defaults to ``false``. Determines whether this is a *keyed histogram*.
+
+``low``
+-------
+Optional, the default value is ``1``. This field represents the minimum value expected in the histogram. Note that all histograms automatically get a bucket with label ``0`` for counting values below the ``low`` value. If a histogram does not specify a ``low`` value, it will always have a ``"0"`` bucket (for negative or zero values) and a ``"1"`` bucket (for values between ``1`` and the next bucket).
+
+
+``high``
+--------
+Required for linear and exponential histograms. The maximum value to be stored in a linear or exponential histogram. Any recorded values greater than this maximum will be counted in the last bucket.
+
+``n_buckets``
+-------------
+Required for linear and exponential histograms. The number of buckets in a linear or exponential histogram.
+
+``n_values``
+------------
+Required for enumerated histograms. Similar to n_buckets, it represent the number of elements in the enum.
+
+``labels``
+----------
+Required for categorical histograms. This is an array of strings which are the labels for different values in this histograms. The labels are restricted to a C++-friendly subset of characters (``^[a-z][a-z0-9_]+[a-z0-9]$``).
+
+``bug_numbers``
+---------------
+Required for all new histograms. This is an array of integers and should at least contain the bug number that added the probe and additionally other bug numbers that affected its behavior.
+
+``description``
+---------------
+Required. A description of the data tracked by the histogram, e.g. _"Resident memory size"_
+
+``cpp_guard``
+-------------
+Optional. This field inserts an #ifdef directive around the histogram's C++ declaration. This is typically used for platform-specific histograms, e.g. ``"cpp_guard": "ANDROID"``
+
+``releaseChannelCollection``
+----------------------------
+Optional. This is one of:
+
+* ``"opt-in"``: (default value) This histogram is submitted by default on pre-release channels; on the release channel only if the user opted into additional data collection
+* ``"opt-out"``: this histogram is submitted by default on release and pre-release channels, unless the user opted out.
+
+.. warning::
+
+    Because they are collected by default, opt-out probes need to meet a higher "user benefit" threshold than opt-in probes.
+
+
+    **Every** new data collection in Firefox needs a `data collection review <https://wiki.mozilla.org/Firefox/Data_Collection#Requesting_Approval>`_ from a data collection peer. Just set the feedback? flag for :bsmedberg or one of the other data peers.
+
+Changing a histogram
+====================
+Changing histogram declarations after the histogram has been released is tricky. The current recommended procedure is to change the name of the histogram.
+
+* When changing existing histograms, the recommended pattern is to use a versioned name (``PROBE``, ``PROBE_2``, ``PROBE_3``, ...).
+* For enum histograms, it's recommended to set "n_buckets" to a slightly larger value than needed since new elements may be added to the enum in the future.
+
+Adding a JavaScript Probe
+=========================
+
+A Telemetry probe is the code that measures and stores values in a histogram. Probes in privileged JavaScript code can make use of the `nsITelemetry <https://mxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/nsITelemetry.idl>`_ interface to get references to histogram objects. A new value is recorded in the histogram by calling ``add`` on the histogram object:
+
+.. code-block:: js
+
+  let histogram = Services.telemetry.getHistogramById("PLACES_AUTOCOMPLETE_1ST_RESULT_TIME_MS");
+  histogram.add(measuredDuration);
+
+  let keyed = Services.telemetry.getKeyedHistogramById("TAG_SEEN_COUNTS");
+  keyed.add("blink");
+
+Note that ``nsITelemetry.getHistogramById()`` will throw an ``NS_ERROR_ILLEGAL_VALUE`` JavaScript exception if it is called with an invalid histogram ID. The ``add()`` function will not throw if it fails, instead it prints an error in the browser console.
+
+For histograms measuring time, `TelemetryStopwatch <https://mxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/TelemetryStopwatch.jsm>`_ can be used to avoid working with Dates manually:
+
+.. code-block:: js
+
+  TelemetryStopwatch.start("SEARCH_SERVICE_INIT_MS");
+  TelemetryStopwatch.finish("SEARCH_SERVICE_INIT_MS");
+
+  TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_MS");
+  TelemetryStopwatch.cancel("FX_TAB_SWITCH_TOTAL_MS");
+
+Adding a C++ Probe
+==================
+
+Probes in native code can also use the `nsITelemetry <https://mxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/nsITelemetry.idl>`_ interface, but the helper functions declared in `Telemetry.h <https://mxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Telemetry.h>`_ are more convenient:
+
+.. code-block:: cpp
+
+  #include "mozilla/Telemetry.h"
+
+  /**
+   * Adds sample to a histogram defined in Histograms.json
+   *
+   * @param id - histogram id
+   * @param sample - value to record.
+   */
+  void Accumulate(ID id, uint32_t sample);
+
+  /**
+   * Adds sample to a keyed histogram defined in Histograms.h
+   *
+   * @param id - keyed histogram id
+   * @param key - the string key
+   * @param sample - (optional) value to record, defaults to 1.
+   */
+  void Accumulate(ID id, const nsCString& key, uint32_t sample = 1);
+
+  /**
+   * Adds time delta in milliseconds to a histogram defined in Histograms.json
+   *
+   * @param id - histogram id
+   * @param start - start time
+   * @param end - end time
+   */
+  void AccumulateTimeDelta(ID id, TimeStamp start, TimeStamp end = TimeStamp::Now());
+
+The histogram names declared in ``Histograms.json`` are translated into constants in the ``mozilla::Telemetry`` namespace:
+
+.. code-block:: cpp
+
+  mozilla::Telemetry::Accumulate(mozilla::Telemetry::STARTUP_CRASH_DETECTED, true);
+
+The ``Telemetry.h`` header also declares the helper classes ``AutoTimer`` and ``AutoCounter``. Objects of these types automatically record a histogram value when they go out of scope:
+
+.. code-block:: cpp
+
+  nsresult
+  nsPluginHost::StopPluginInstance(nsNPAPIPluginInstance* aInstance)
+  {
+    Telemetry::AutoTimer<Telemetry::PLUGIN_SHUTDOWN_MS> timer;
+    ...
+    return NS_OK;
+  }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e0c8497acc5709c474de550ac05aa6d4b1261329
GIT binary patch
literal 6862
zc%1E*X*3k<-^Xp;vdfY?yC^bugpegmvL*XgVJ0I<cB7G)8A2FkD?3R<vW_ex%Ve35
zWkeWe#=eBHPG)2bf8DR1SI;^BbDndaH(lra&UJp@>vO*6cU`Z(SAva|DG#S8CleDB
zkGa`(J0>P(v*UVvlI2)w;->W;Z_E%o(`!ssgW@a44V#ytr6Cg&Dw*rR{lsz45omTB
z!o(yH^1GO)?n^ypViNT*zi#O87`UGQ?NQzpDO1u-QL{7t9T(9tn-|rK6Y|#Yf`$!{
zy%f0GXYRBVgU=`U0%6oVOYtT7l^dCu&uV+?2)MZcOR4M@RO}lFBPSgtcnWP^iI>l@
zjyoImEE3$zFBf=?1qtgge`F?L<2xS7W@To8vc6KrYtDjP7ft!h!J$CqkRO#XH$b_u
z3S5z4#_G)$Ys+ycH24^>$jiOK%jY@7$uVQ0JZ3X7Y(n&ym#ZnqY85j_UP>gmAka=C
zjqM@!aCiH31ng+L)F|}mu)8{IzCdc4eY#m<9P?gF*UA?Zi14J3iW9}tS}L^8`>8zC
z=Y)%A!yJdoP<rxL*PmpC>>0|hZv<AfpE7zv+w3}p^Ef@!(^kA`8>zx&c{R!6?lU}Y
zckIVF!!H$Ii7Cv-=t-{75a9<+E`HnBq=cKzdB@k1H-zd;$jfA2cAa>?W^ulYdOGT+
zy1ZtBE~>MU^wUctKdfGNLIy7Wr0)JOJ~bJXS$`FD7V2`FuvlwAwjs^3$$=3^+p=Zu
z9^lL(ngAWOesVw}e$>c9n|M&KS;e)6E2KQQ{1K9D5qc#^ZJttiwp*?SgKx4o{Om7H
zEm&Akxr0ia)j>+UUgu=2u%Ed-3RQh#8CXBWP)QQUT=E9)09v8u7#$VaZdbhX=58>G
zkaM{iY+d>GIX(z5ZxM#h^z(Y`j4JR@;MJ&_Fbu;uFW*`UB<K;(ImR>OFZB9F>~#p#
zmG=cLY%8DAX}RHg0oq)gQ|<rA3(HGA9MQzRZk!pm7~P_Ro>T+m&7^PXV1>u=IM#yD
zqgu`Vr^ScKIBJLfPr{Ej9p)lY6yai6@JAWgtjGCU&7-824&_PO888j_d}@5ax%=P_
z%z)47Hjq413y-)!8^KTcoZUd;n!>0=Nryy+u3OjKl#V2yx-L%l!e|01m!Ye7)baPE
zasCx-jaR(efuYq#x7$iP&M~{y(Ow4XqWtpeLYL4@$A;n*Vfw*?k_K=xpGMocF@HAI
z@cCrW#KjPNrnzV|1beYo%yavBO8y)Bid_pHFc(U#dC?}Jf1~vV*&lG=vAT?JKUh@u
z?Bo<@a4YK>@z8I}Lqi;>iaNjU7Y%ot?#=&NicjA2I$B9Lg!Er|5yB-Ymtg^G=*vu-
z<Bld>CsP$0$BWDBzhyLRbxv+Q)NZ*<1`_;*69a6T>cx7_P|r3e$vU3jcMW|(0+yM`
z$Cvc2xERjan}x!}HvXQBG6|1&ATNhUd<_^+01jWjZ*|Y}f!D1WX9-pr=wo|DxJX|Q
z0i|s`@8XrLbA_RU8(t6kk{7sITSj9o!EW$A;XMyoOs&R$yV_K7k5R+v^=>H=-TpIz
zpTx|yY5B?;vSQ(%3Zt<|8$WPPpW~jd5~g!|JmMBWb4Sf1^S@`KB=%3-w-*)3TF^4m
zt-?c0R!{=yznQUZ_!u3c55EoBr~hM7%YP$e$w0Rx<7aaVi*ODInNPD~A22m))srCy
zRoe}j-w41FO`?23HZBQ3X}~*6AV#XiW42D^;zIZCPtiwO)Ej>?{J;M?Af<dqO2Ctf
zmT=8H(%xn$)858?jby9DsT@fdzg*VzFpd2MQJ?Qg_@_x3$q}2UDSCslN4JZHIoB|t
z{%L#U4b(i&<|hIGL*Qht`(1zv?+4v{mwZn|{{u-Wx(H=W7ejn0-D(>1t}YPVdZ(+1
zP0}+wGZ=vXHP=GR&DdhTnw<<+_ol^d?&{FtiGRbm>%zYngu5PG&+HIPP3vys25JeN
zCFbh^^CLb3#DfDL@67f<52D#qz4Qi0dE~<m?<zHYot`rs0Wa&7INe%H4siZwA>>?_
za)N%4(?<a(TeJP#B&F=thO#q2kz}{gl}V9tB+1RLE_pW|>N?=+*>?29Fhul+B!#iu
z-97KjdpT^j^NG|f=^?M=-YKcmSFt{OU6_qpl}~n0KJ0$~xsBfzuZoZ_%6`$P)Y&jO
zK%y<*4L~UK={CLRl3IObUX?#{5H!y_c)PWo9bDQqSP`RwD(LgQcdp`X%1_O8_|j#K
z0Dv0S!*RJ3=&B423_nNDiX5i!ahCpS^Jw3jf4MLd5L<f&D28?S-j>ue^F`w3r|HBu
zvG51)ULt}71q&Fa8nJ?&@76#d4!U(*vfE}-WmD{(%<|ihk=7b4Mc{7hRWnWB&TJPB
z8F^@bP2DT6jY*$z;)nT%*ft}s)|`FFP}xRI?oDqrj^||}VYRk)HVw)4Trw(U<5?e0
zVuh|Z)!VZc>@vbC_0MzUg5yWv)VXIAagEd?IB~m|Q{9!r13X?4<0!P`L`Dx?Prnde
z;Fs3@VamD%ZABhlG#&LcABlcom22;VZsoQTR3Fij&L}T^=lvjR0jIHIHwfKvH;ypQ
z!o=n<DRM^)jou>X*bnuqr-2|8Xijf(&lkz7f>b3pj`o=D>po5${qEgyd|1f?{}O(N
zYjtwqi~OV&71no~%VGLsmMxP-fa)KmmsIx9;LMUY`VT`y4qi2*K^H@73DPl|Xrv#H
z{al=2{Qw_u3X(5rtoACsd8oTg$+W1J-A`T-LEP;Zzq#!JOoZqU58gS6UGHQKf`%;G
zj#F+uf`7%JtHAn_3^9jhXq1Ug-IA-Wf-*+0gJ6(cPTZsJ9UvuKznQ{Lx@!nB$hi5o
z(Hi{;;WJgs4Lx$g3seun@}@X!>JC|2e={}Rn$Pw=C9`AT<AH*cQf$b;Vrf-*Z>NM2
zkQ&1<i01xPLmKO=LY)KSJktn!v5o#CI+r2V;imjL7dTgVJ<0jzq%pug%HgP0tO6}G
z?)y2%`KP|H#cO8mt;#hw8p9ot#@O+Iz0%gYQus*uB$vtKDNgrvwP^o(eeg+OhQ;e<
zq*^{1;gD7K$ddAI(;As?oa4e_DE6&JQ#kxm1-$p?kx>5N4MN7*_`M8^MU(rpdG|q1
zw^6<4$GzH1J6wo=WuknZaqtCNI%l*}cT<$ax`ROP@H7B%d}kl14lFl?jMMsL@^vET
zrK<7DDskIOTX-i7+fZNVTnZQjA7hh_;y8{i0;&H+Y!QF<c@q~va#6>?4mZLX6vp;n
zgS3LUGks$cj^s*?++_X-#gfSIBkIUb?riF|StmP>EjikXSpNw|X*8K2xSw8!dMZ%s
zxwV!a0AlOGw2^Xrx9jTo>MoAv(h3K<^ZA5^rn}jHuV%4ARjZD$OD@A?4-XHCp3|2H
z1JIRtCo-!tg$v36R<LFw5N&axSXSxua4MH{9T;>s&H7ST%?)<ymoKJ`J=T|gs2kak
z)qh5;!`b$wz};foGxWonzREo~*Kmg1)(N%>Jcx;O{#=bZGj=Mc=9j=83k#BQV#v>q
z#YNEO_idD;l%?PN3t6yDO&w1Bz?7>vlgkJ8ERt#dyO^WxqgDoGxO3_a1lh{$Ae$**
zLw>g~JbppLcvw6#63oh<dlFk>mcz<#<ID6GY|lSXcv9i`3~8Bqnf=(9={i8;#5Zwd
zB-mcA5-(&9VPZk@7}Wg%|A2qMKj0tm5BLZC1OESkuBj9{h2TQpU8m5HdGi<tRGF58
zFLGfnuV6D^Q@`0c4gnN@JR_p0p&s@d&pF1gDvUJFG3p|`=b}KR`0oqHVJJAr>K+wG
z1B5f2Q6kDk3$g!NLkN-jZA$ReN<1LL_NU!(P*NIb@@r$wMYMtL_+f#cp8uPqg1xOO
zYRB;bM~B@<G%9X*MQe3Uh+gic<fPS>P9232T44Z<SXb1Av_8nbl*e>coic2*Ki8|z
zIaL#P3HsOUam*941N`riZ0C)z&Zp_R35inJox+kGZY`wcz+4b^YL>b^DWy1_xuS7o
zJ96~!w;T^nPLb=PuAhcvd;sGL)uH4dEWEAtNi&~{smu0>fZdSnLAMftT#Jg314C@m
zvyZsAERni7)%@n0=QJ|$+1SHoXrC)P1P&N?D;WTKwY#OP36#4;@0#Wpf&oxQ#Afh<
zAB3{bfb|N>3gza!F+#$s*<o5}L{6AtP?j9JE!{_Hq<O3Wv%DFwAsB24erXZZ&KxI1
z*BIPt8$0dPsNmcXjP=|{3m4+KeAz(34k$x5g0Y(w`R>aiJfX5Y=N`7Iux=}>FF`_g
zzKr3$KpgnK7=O|A_K31Q+1n&B8NH)g4^{?h^l%A_wP*3!lA8iSiTG4RYBU(<7Os|r
zsPxn>ng{T$hc2*BA>;IgnvTNSb@-gYuDd&#g5gEhmPD|T6}^5g9J^eB6VVp_V*MBd
z^;Gftv4v8;K&7gZKSol$PXJGWL}|5MytG7dAu$+!JrGpGFkiK>j1favq6m$|&qkyx
z$DaT>Dr1iJF7wA)0%NC#Q~o5j?HPWZQ4vs3P|e6S0a}n*r_{n9R^i9_=_<2{h}fY~
zf6bBtYx=LSiUIWTi;j*?>D7l6I4S;>r@P_b+x$cE&cqf!)`|RKu|7GNUT3F16D?JY
zN4nL=&}{t+oId;&u@x=kf-2A0()+qTa$_`-51#VdM_|gd33jJUbgP}uj_ebEwdKrZ
ziXV}#&}a={4{i8BN5Xhe<)=IGhQ|^J;B{ooi>$;u4sVtcsR11%uYVkGY3i7vgqt4*
zt;=?1-zn?(G-3FgNL;)5bzLn(u_9OE-!ahC)Y+Sr4(=qfG*+(#@AX8a5o})qpkC<G
z>*J9>9z6RR94pH|Sa(yRkFYSI2K0FKwlBB>uV&3Z|8Z@_BY0PyJueflDpTe>z)$dL
zYFhR!Cypk-NQApUJMwxEIW#T!AA7bA(Z!bU{O`+F)w-$@D1UR9%?O`VpaS}z!cv%S
z8lc?Yd@=u@%3W4e=)U9qK7^rzddGrMznXr#?pVLsa#_#|sAOJI>igZwSFcyyMjw!r
zw<XpVDASyL9@bKx+Nz6--j$+#<kI#L@Fu0a|1G;-04-d!;a9AsY`%QA<A$!5spcMz
z;<mSpK{z+4?nwVcEf6qQB~Bi>+W6PKY=1mg_{g0WOFKOD+mFy!nNN;fP+3}I8U`b<
z)vAOq7y{YvwD><CQ#HoaL2&gerWpO{=x#q#%WIYQBxCf_@_3)Dde0Qu&JG>&?tdG5
zc>>~K;VofshrHL1?mIT915ZmSkl+JT+>YyerrMZbR|aoD3uSFdK{Z_NVG!UYX7+B)
zAw407z83cOeerD9j44NfG8JF<lu1)4e6b^s`nK73;tSf4GC{(J$Xl2`4}nJqrEBx`
z+ak7a>C!5_JzdE+g9ESS?uxw_et!xTZMnmZDlJLB+8rQAB|;X14G*U@%*7l>itW{w
zYT#GMz8Qppkk?s7d#W1pvy)C=!On8duAZ7b!M3ts_T^frrY0<#gu4m-V*15SrJq<A
zAPo)Xdjn5Z)(aL~{Ht~1efoe>Z4t9vr42-=`Y=S{Iv@<SmR8=PWXwaIn#i9`Gq%+i
zdDPkegf)>wj($oR|2~lDsl@J5NIgA%{+y2gB780n0#-`$EvC$%O|-2CZCgw_x%@w*
z7vj@<)%(D@is-qE`!2H7uIC~VW#ad}^HE^U`~(dDnP;thE|VK)b$m~V_n0lW2MKHQ
z;cSJ*)$WkKUV6hNWz7ri9TC(R?VXv1xWimtS_+puDrU8Z<tipr;O#4aZ3?7(R$>zh
z7wOkH8wKM~ptiO6S+X?q+mPKZ5#rgX=icDyI}Pd_<ZA|~&~{&00eog8c(ac%CRf9C
z{DnX9@rBu45DT(ZIl$%R|D};OD&UA>!6I-)FzSdk>LBmJwvq5zrsHL9Y<0cr+TExB
E0kjxyKmY&$
--- a/toolkit/components/telemetry/docs/data/main-ping.rst
+++ b/toolkit/components/telemetry/docs/data/main-ping.rst
@@ -92,17 +92,17 @@ Note that this currently does not behave
 * On OS X this uses ``mach_absolute_time()``, which does not increase over sleep periods
 * On POSIX/Linux this uses ``clock_gettime(CLOCK_MONOTONIC, &ts)``, which should not increase over sleep time
 
 See `bug 1204823 <https://bugzilla.mozilla.org/show_bug.cgi?id=1204823>`_ for details.
 
 subsessionLength
 ~~~~~~~~~~~~~~~~
 The length of this subsession in seconds.
-This uses a monotonic clock, so this may mismatch with other measurements that are not monotonic (e.g. based on ``Date.now()`).
+This uses a monotonic clock, so this may mismatch with other measurements that are not monotonic (e.g. based on ``Date.now()``).
 
 If ``sessionLength`` is ``-1``, the monotonic clock is not working.
 
 Also see the remarks for ``sessionLength`` on platform consistency.
 
 processes
 ---------
 This section contains per-process data.
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -197,25 +197,37 @@ Notification.prototype = {
  *        populated with <popupnotification> children and displayed it as
  *        needed.
  * @param iconBox
  *        Reference to a container element that should be hidden or
  *        unhidden when notifications are hidden or shown. It should be the
  *        parent of anchor elements whose IDs are passed to show().
  *        It is used as a fallback popup anchor if notifications specify
  *        invalid or non-existent anchor IDs.
+ * @param options
+ *        An optional object with the following optional properties:
+ *        {
+ *          shouldSuppress:
+ *            If this function returns true, then all notifications are
+ *            suppressed for this window. This state is checked on construction
+ *            and when the "anchorVisibilityChange" method is called.
+ *        }
  */
-this.PopupNotifications = function PopupNotifications(tabbrowser, panel, iconBox) {
+this.PopupNotifications = function PopupNotifications(tabbrowser, panel,
+                                                      iconBox, options = {}) {
   if (!(tabbrowser instanceof Ci.nsIDOMXULElement))
     throw "Invalid tabbrowser";
   if (iconBox && !(iconBox instanceof Ci.nsIDOMXULElement))
     throw "Invalid iconBox";
   if (!(panel instanceof Ci.nsIDOMXULElement))
     throw "Invalid panel";
 
+  this._shouldSuppress = options.shouldSuppress || (() => false);
+  this._suppress = this._shouldSuppress();
+
   this.window = tabbrowser.ownerGlobal;
   this.panel = panel;
   this.tabbrowser = tabbrowser;
   this.iconBox = iconBox;
   this.buttonDelay = Services.prefs.getIntPref(PREF_SECURITY_DELAY);
 
   this.panel.addEventListener("popuphidden", this, true);
   this.panel.classList.add("popup-notification-panel");
@@ -519,35 +531,45 @@ PopupNotifications.prototype = {
 
       this._fireCallback(notification, NOTIFICATION_EVENT_REMOVED);
       return false;
     }, this);
 
     this._setNotificationsForBrowser(aBrowser, notifications);
 
     if (this._isActiveBrowser(aBrowser)) {
-      // get the anchor element if the browser has defined one so it will
-      // _update will handle both the tabs iconBox and non-tab permission
-      // anchors.
-      this._update(notifications, this._getAnchorsForNotifications(notifications,
-        getAnchorFromBrowser(aBrowser)));
+      this.anchorVisibilityChange();
     }
   },
 
   /**
    * Called by the consumer to indicate that the visibility of the notification
-   * anchors may have changed, but the location has not changed. This may result
-   * in the "showing" and "shown" events for visible notifications to be
-   * invoked even if the anchor has not changed.
+   * anchors may have changed, but the location has not changed. This also
+   * checks whether all notifications are suppressed for this window.
+   *
+   * Calling this method may result in the "showing" and "shown" events for
+   * visible notifications to be invoked even if the anchor has not changed.
    */
   anchorVisibilityChange() {
-    let notifications =
-      this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser);
-    this._update(notifications, this._getAnchorsForNotifications(notifications,
-      getAnchorFromBrowser(this.tabbrowser.selectedBrowser)));
+    let suppress = this._shouldSuppress();
+    if (!suppress) {
+      // If notifications are not suppressed, always update the visibility.
+      this._suppress = false;
+      let notifications =
+        this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser);
+      this._update(notifications, this._getAnchorsForNotifications(notifications,
+        getAnchorFromBrowser(this.tabbrowser.selectedBrowser)));
+      return;
+    }
+
+    // Notifications are suppressed, ensure that the panel is hidden.
+    if (!this._suppress) {
+      this._suppress = true;
+      this._hidePanel().catch(Cu.reportError);
+    }
   },
 
   /**
    * Removes a Notification.
    * @param notification
    *        The Notification object to remove.
    */
   remove: function PopupNotifications_remove(notification) {
@@ -1009,20 +1031,22 @@ PopupNotifications.prototype = {
         if (anchor.parentNode == this.iconBox)
           continue;
         useIconBox = false;
         break;
       }
     }
 
     // Filter out notifications that have been dismissed, unless they are
-    // persistent.
-    let notificationsToShow = notifications.filter(function(n) {
-      return (!n.dismissed || n.options.persistent) && !n.options.neverShow;
-    });
+    // persistent. Also check if we should not show any notification.
+    let notificationsToShow = [];
+    if (!this._suppress) {
+      notificationsToShow = notifications.filter(
+        n => (!n.dismissed || n.options.persistent) && !n.options.neverShow);
+    }
 
     if (useIconBox) {
       // Hide icons of the previous tab.
       this._hideIcons();
     }
 
     if (haveNotifications) {
       // Also filter out notifications that are for a different anchor.
@@ -1278,28 +1302,33 @@ PopupNotifications.prototype = {
         return n.options.eventCallback.call(n, event, ...args);
     } catch (error) {
       Cu.reportError(error);
     }
     return undefined;
   },
 
   _onPopupHidden: function PopupNotifications_onPopupHidden(event) {
-    if (event.target != this.panel || this._ignoreDismissal) {
-      if (this._ignoreDismissal) {
-        this._ignoreDismissal.resolve();
-        this._ignoreDismissal = null;
-      }
+    if (event.target != this.panel) {
       return;
     }
 
-    // Ensure that when the panel comes up without user interaction,
-    // we don't autofocus it.
+    // We may have removed the "noautofocus" attribute before showing the panel
+    // if it was opened with user interaction. When the panel is closed, we have
+    // to restore the attribute to its default value, so we don't autofocus it
+    // if it is subsequently opened from a different code path.
     this.panel.setAttribute("noautofocus", "true");
 
+    // Handle the case where the panel was closed programmatically.
+    if (this._ignoreDismissal) {
+      this._ignoreDismissal.resolve();
+      this._ignoreDismissal = null;
+      return;
+    }
+
     this._dismissOrRemoveCurrentNotifications();
 
     this._clearPanel();
 
     this._update();
   },
 
   _dismissOrRemoveCurrentNotifications() {
--- a/widget/windows/GfxInfo.cpp
+++ b/widget/windows/GfxInfo.cpp
@@ -1172,16 +1172,27 @@ GfxInfo::GetGfxDriverInfo()
       DRIVER_BETWEEN_INCLUSIVE, V(8,17,12,5730), V(8,17,12,6901), "FEATURE_FAILURE_BUG_1137716", "Nvidia driver > 8.17.12.6901");
 
     /* Bug 1153381: WebGL issues with D3D11 ANGLE on Intel. These may be fixed by an ANGLE update. */
     APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows,
       (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
       nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
       DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1153381");
 
+    /* Bug 1336710: Crash in rx::Blit9::initialize. */
+    APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::WindowsXP,
+      (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
+      nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+      DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1336710");
+
+    APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::WindowsXP,
+      (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelHDGraphicsToSandyBridge),
+      nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+      DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1336710");
+
     /* Bug 1304360: Graphical artifacts with D3D9 on Windows 7. */
     APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7,
       (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX3000),
       nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
       DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 1749, "FEATURE_FAILURE_INTEL_W7_D3D9_LAYERS");
 
     ////////////////////////////////////
     // WebGL