Merge inbound to mozilla-central. a=merge
authorGurzau Raul <rgurzau@mozilla.com>
Fri, 13 Apr 2018 02:08:51 +0300
changeset 413059 da809ecceaf3a8ada0aa2d7115822d39d0439654
parent 413044 46615d425bcb02f4b9b6ce530e0c34f3b582ac32 (current diff)
parent 413058 91cfe8e76dd18e360ef9e79166abac11c94e3f9e (diff)
child 413091 409d13ff69327f4a397f26c0524caf2265aaa43b
child 413132 8bc58e08f3a71e4355af804a086ab3c812e57eb1
push id33832
push userrgurzau@mozilla.com
push dateThu, 12 Apr 2018 23:09:18 +0000
treeherdermozilla-central@da809ecceaf3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
da809ecceaf3 / 61.0a1 / 20180412232808 / files
nightly linux64
da809ecceaf3 / 61.0a1 / 20180412232808 / files
nightly mac
da809ecceaf3 / 61.0a1 / 20180412232808 / files
nightly win32
da809ecceaf3 / 61.0a1 / 20180412232808 / files
nightly win64
da809ecceaf3 / 61.0a1 / 20180412232808 / 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 mozilla-central. a=merge
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1284,27 +1284,16 @@ var BookmarkingUI = {
   _getFormattedTooltip(strId) {
     let args = [];
     let shortcut = document.getElementById(this.BOOKMARK_BUTTON_SHORTCUT);
     if (shortcut)
       args.push(ShortcutUtils.prettifyShortcut(shortcut));
     return gNavigatorBundle.getFormattedString(strId, args);
   },
 
-  /**
-   * The popup contents must be updated when the user customizes the UI, or
-   * changes the personal toolbar collapsed status.  In such a case, any needed
-   * change should be handled in the popupshowing helper, for performance
-   * reasons.
-   */
-  _popupNeedsUpdate: true,
-  onToolbarVisibilityChange: function BUI_onToolbarVisibilityChange() {
-    this._popupNeedsUpdate = true;
-  },
-
   onPopupShowing: function BUI_onPopupShowing(event) {
     // Don't handle events for submenus.
     if (event.target != event.currentTarget)
       return;
 
     // On non-photon, this code should never be reached. However, if you click
     // the outer button's border, some cpp code for the menu button's XBL
     // binding decides to open the popup even though the dropmarker is invisible.
@@ -1326,33 +1315,31 @@ var BookmarkingUI = {
       event.preventDefault();
       widget.node.removeAttribute("closemenu");
       PlacesCommandHook.showPlacesOrganizer("BookmarksMenu");
       return;
     }
 
     this._initMobileBookmarks(document.getElementById("BMB_mobileBookmarks"));
 
-    if (!this._popupNeedsUpdate)
-      return;
-    this._popupNeedsUpdate = false;
+    this.selectLabel("BMB_viewBookmarksSidebar",
+                     SidebarUI.currentID == "viewBookmarksSidebar");
+    this.selectLabel("BMB_viewBookmarksToolbar",
+                     !document.getElementById("PersonalToolbar").collapsed);
+  },
 
-    let popup = event.target;
-    let getPlacesAnonymousElement =
-      aAnonId => document.getAnonymousElementByAttribute(popup.parentNode,
-                                                         "placesanonid",
-                                                         aAnonId);
+  selectLabel(elementId, visible) {
+    let element = document.getElementById(elementId);
+    element.setAttribute("label", element.getAttribute(visible ? "label-hide"
+                                                               : "label-show"));
+  },
 
-    let viewToolbarMenuitem = getPlacesAnonymousElement("view-toolbar");
-    if (viewToolbarMenuitem) {
-      // Update View bookmarks toolbar checkbox menuitem.
-      viewToolbarMenuitem.classList.add("subviewbutton");
-      let personalToolbar = document.getElementById("PersonalToolbar");
-      viewToolbarMenuitem.setAttribute("checked", !personalToolbar.collapsed);
-    }
+  toggleBookmarksToolbar() {
+    CustomizableUI.setToolbarVisibility("PersonalToolbar",
+      document.getElementById("PersonalToolbar").collapsed);
   },
 
   attachPlacesView(event, node) {
     // If the view is already there, bail out early.
     if (node.parentNode._placesView)
       return;
 
     new PlacesMenu(event, "place:folder=BOOKMARKS_MENU", {
@@ -1435,17 +1422,16 @@ var BookmarkingUI = {
     if (!this._isCustomizing) {
       this._uninitView();
     }
   },
 
   onCustomizeEnd: function BUI_customizeEnd(aWindow) {
     if (aWindow == window) {
       this._isCustomizing = false;
-      this.onToolbarVisibilityChange();
     }
   },
 
   init() {
     CustomizableUI.addListener(this);
 
     if (Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
       let starButtonBox = document.getElementById("star-button-box");
@@ -1646,50 +1632,24 @@ var BookmarkingUI = {
 
   onPanelMenuViewHiding: function BUI_onViewHiding(aEvent) {
     this._panelMenuView.uninit();
     delete this._panelMenuView;
     aEvent.target.removeEventListener("ViewHiding", this);
   },
 
   showBookmarkingTools(triggerNode) {
-    const panelID = "PanelUI-bookmarkingTools";
-    let viewNode = document.getElementById(panelID);
-    for (let button of [...viewNode.getElementsByTagName("toolbarbutton")]) {
-      let update = true;
-      switch (button.id) {
-        case "panelMenu_toggleBookmarksMenu":
-          let placement = CustomizableUI.getPlacementOfWidget(this.BOOKMARK_BUTTON_ID);
-          button.setAttribute("checked", !!placement && placement.area == CustomizableUI.AREA_NAVBAR);
-          break;
-        case "panelMenu_viewBookmarksSidebar":
-          button.setAttribute("checked", SidebarUI.currentID == "viewBookmarksSidebar");
-          break;
-        case "panelMenu_viewBookmarksToolbar":
-          let toolbar = document.getElementById("PersonalToolbar");
-          // This is an actual toolbarbutton[type=checkbox], and its checked
-          // attribute will get added/removed by the binding when clicked.
-          // Setting the attribute to 'false' breaks showing the toolbar,
-          // because the binding removes the attribute instead of setting it
-          // to 'true' when clicked.
-          if (toolbar.getAttribute("collapsed") != "true") {
-            button.setAttribute("checked", "true");
-          } else {
-            button.removeAttribute("checked");
-          }
-          break;
-        default:
-          update = false;
-          break;
-      }
-      if (update) {
-        updateToggleControlLabel(button);
-      }
-    }
-    PanelUI.showSubView(panelID, triggerNode);
+    let placement = CustomizableUI.getPlacementOfWidget(this.BOOKMARK_BUTTON_ID);
+    this.selectLabel("panelMenu_toggleBookmarksMenu",
+                     placement && placement.area == CustomizableUI.AREA_NAVBAR);
+    this.selectLabel("panelMenu_viewBookmarksSidebar",
+                     SidebarUI.currentID == "viewBookmarksSidebar");
+    this.selectLabel("panelMenu_viewBookmarksToolbar",
+                     !document.getElementById("PersonalToolbar").collapsed);
+    PanelUI.showSubView("PanelUI-bookmarkingTools", triggerNode);
   },
 
   toggleMenuButtonInToolbar(triggerNode) {
     let placement = CustomizableUI.getPlacementOfWidget(this.BOOKMARK_BUTTON_ID);
     const area = CustomizableUI.AREA_NAVBAR;
     if (!placement) {
       // Button is in the palette, so we can move it to the navbar.
       let pos;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5600,18 +5600,16 @@ function setToolbarVisibility(toolbar, i
     detail: {
       visible: isVisible
     },
     bubbles: true
   };
   let event = new CustomEvent("toolbarvisibilitychange", eventParams);
   toolbar.dispatchEvent(event);
 
-  BookmarkingUI.onToolbarVisibilityChange();
-
   if (toolbar.getAttribute("type") == "menubar" && CustomizationHandler.isCustomizing()) {
     gCustomizeMode._updateDragSpaceCheckbox();
   }
 }
 
 function updateToggleControlLabel(control) {
   if (!control.hasAttribute("label-checked")) {
     return;
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1105,21 +1105,19 @@
                    onmouseup="BookmarksEventHandler.onMouseUp(event);"
                    oncommand="BookmarksEventHandler.onCommand(event);"
                    onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
                    onpopupshowing="BookmarkingUI.onPopupShowing(event);
                                    BookmarkingUI.attachPlacesView(event, this);"
                    tooltip="bhTooltip" popupsinherittooltip="true">
           <menuitem id="BMB_viewBookmarksSidebar"
                     class="subviewbutton"
-                    label="&viewBookmarksSidebar2.label;"
-                    type="checkbox"
-                    oncommand="SidebarUI.toggle('viewBookmarksSidebar');">
-            <observes element="viewBookmarksSidebar" attribute="checked"/>
-          </menuitem>
+                    label-show="&viewBookmarksSidebar2.label;"
+                    label-hide="&hideBookmarksSidebar.label;"
+                    oncommand="SidebarUI.toggle('viewBookmarksSidebar');"/>
           <!-- NB: temporary solution for bug 985024, this should go away soon. -->
           <menuitem id="BMB_bookmarksShowAllTop"
                     class="menuitem-iconic subviewbutton"
                     label="&showAllBookmarks2.label;"
                     command="Browser:ShowAllBookmarks"
                     key="manBookmarkKb"/>
           <menuseparator/>
           <menu id="BMB_bookmarksToolbar"
@@ -1128,21 +1126,20 @@
                 container="true">
             <menupopup id="BMB_bookmarksToolbarPopup"
                        placespopup="true"
                        context="placesContext"
                        onpopupshowing="if (!this.parentNode._placesView)
                                          new PlacesMenu(event, 'place:folder=TOOLBAR',
                                                         PlacesUIUtils.getViewForNode(this.parentNode.parentNode).options);">
               <menuitem id="BMB_viewBookmarksToolbar"
-                        placesanonid="view-toolbar"
-                        toolbarId="PersonalToolbar"
-                        type="checkbox"
-                        oncommand="onViewToolbarCommand(event)"
-                        label="&viewBookmarksToolbar.label;"/>
+                        class="subviewbutton"
+                        label-show="&viewBookmarksToolbar.label;"
+                        label-hide="&hideBookmarksToolbar.label;"
+                        oncommand="BookmarkingUI.toggleBookmarksToolbar();"/>
               <menuseparator/>
               <!-- Bookmarks toolbar items -->
             </menupopup>
           </menu>
           <menu id="BMB_unsortedBookmarks"
                 class="menu-iconic bookmark-item subviewbutton"
                 label="&bookmarksMenuButton.other.label;"
                 container="true">
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -655,36 +655,31 @@
           <!-- Recent Highlights will go here -->
         </toolbaritem>
       </vbox>
     </panelview>
 
     <panelview id="PanelUI-bookmarkingTools" class="PanelUI-subView">
       <vbox class="panel-subview-body">
         <toolbarbutton id="panelMenu_toggleBookmarksMenu"
-                       label="&addBookmarksMenu.label;"
-                       label-checked="&removeBookmarksMenu.label;"
                        class="subviewbutton subviewbutton-iconic"
-                       oncommand="BookmarkingUI.toggleMenuButtonInToolbar(this); PanelUI.hide();"/>
+                       label-show="&addBookmarksMenu.label;"
+                       label-hide="&removeBookmarksMenu.label;"
+                       oncommand="BookmarkingUI.toggleMenuButtonInToolbar(this);"/>
         <toolbarbutton id="panelMenu_viewBookmarksSidebar"
-                       label="&viewBookmarksSidebar2.label;"
-                       label-checked="&hideBookmarksSidebar.label;"
                        class="subviewbutton subviewbutton-iconic"
+                       label-show="&viewBookmarksSidebar2.label;"
+                       label-hide="&hideBookmarksSidebar.label;"
                        key="viewBookmarksSidebarKb"
-                       oncommand="SidebarUI.toggle('viewBookmarksSidebar', this); PanelUI.hide();">
-          <observes element="viewBookmarksSidebar" attribute="checked"/>
-        </toolbarbutton>
+                       oncommand="SidebarUI.toggle('viewBookmarksSidebar', this);"/>
         <toolbarbutton id="panelMenu_viewBookmarksToolbar"
                        class="subviewbutton subviewbutton-iconic"
-                       placesanonid="view-toolbar"
-                       toolbarId="PersonalToolbar"
-                       type="checkbox"
-                       oncommand="onViewToolbarCommand(event)"
-                       label="&viewBookmarksToolbar.label;"
-                       label-checked="&hideBookmarksToolbar.label;"/>
+                       label-show="&viewBookmarksToolbar.label;"
+                       label-hide="&hideBookmarksToolbar.label;"
+                       oncommand="BookmarkingUI.toggleBookmarksToolbar();"/>
       </vbox>
     </panelview>
   </panelmultiview>
 </panel>
 
 <panel id="downloads-button-autohide-panel"
        role="group"
        type="arrow"
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -858,17 +858,17 @@ panelview .toolbarbutton-1,
 .PanelUI-subView .subviewbutton:not(.panel-subview-footer) > .menu-text,
 .PanelUI-subView .subviewbutton:not(.panel-subview-footer) > .menu-iconic-text {
   font: menu;
 }
 
 .subviewbutton[shortcut]::after {
   content: attr(shortcut);
   float: right;
-  color: GrayText;
+  opacity: 0.5;
 }
 
 .PanelUI-subView .subviewbutton-nav::after {
   -moz-context-properties: fill, fill-opacity;
   content: url(chrome://browser/skin/back-12.svg);
   fill: currentColor;
   fill-opacity: 0.6;
   float: right;
--- a/dom/media/MediaData.cpp
+++ b/dom/media/MediaData.cpp
@@ -496,20 +496,21 @@ MediaRawData::~MediaRawData()
 size_t
 MediaRawData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t size = aMallocSizeOf(this);
   size += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
   return size;
 }
 
-MediaRawDataWriter*
+UniquePtr<MediaRawDataWriter>
 MediaRawData::CreateWriter()
 {
-  return new MediaRawDataWriter(this);
+  UniquePtr<MediaRawDataWriter> p(new MediaRawDataWriter(this));
+  return p;
 }
 
 MediaRawDataWriter::MediaRawDataWriter(MediaRawData* aMediaRawData)
   : mCrypto(aMediaRawData->mCryptoInternal)
   , mTarget(aMediaRawData)
 {
 }
 
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -696,19 +696,19 @@ public:
   // Indicate to the audio decoder that mDiscardPadding frames should be
   // trimmed.
   uint32_t mDiscardPadding = 0;
 
   RefPtr<TrackInfoSharedPtr> mTrackInfo;
 
   // Return a deep copy or nullptr if out of memory.
   virtual already_AddRefed<MediaRawData> Clone() const;
-  // Create a MediaRawDataWriter for this MediaRawData. The caller must
-  // delete the writer once done. The writer is not thread-safe.
-  virtual MediaRawDataWriter* CreateWriter();
+  // Create a MediaRawDataWriter for this MediaRawData. The writer is not
+  // thread-safe.
+  virtual UniquePtr<MediaRawDataWriter> CreateWriter();
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
 
 protected:
   ~MediaRawData();
 
 private:
   friend class MediaRawDataWriter;
   AlignedByteBuffer mBuffer;
--- a/dom/media/flac/FlacDemuxer.cpp
+++ b/dom/media/flac/FlacDemuxer.cpp
@@ -3,17 +3,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FlacDemuxer.h"
 
 #include "mozilla/Maybe.h"
 #include "BitReader.h"
-#include "nsAutoPtr.h"
 #include "prenv.h"
 #include "FlacFrameParser.h"
 #include "VideoUtils.h"
 #include "TimeUnits.h"
 
 extern mozilla::LazyLogModule gMediaDemuxerLog;
 #define LOG(msg, ...)                                                          \
   DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, msg, ##__VA_ARGS__)
@@ -958,17 +957,17 @@ FlacTrackDemuxer::GetNextFrame(const fla
       aFrame.Time().ToSeconds(), aFrame.Offset(), aFrame.Size());
 
   const int64_t offset = aFrame.Offset();
   const uint32_t size = aFrame.Size();
 
   RefPtr<MediaRawData> frame = new MediaRawData();
   frame->mOffset = offset;
 
-  nsAutoPtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
+  UniquePtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
   if (!frameWriter->SetSize(size)) {
     LOG("GetNext() Exit failed to allocated media buffer");
     return nullptr;
   }
 
   const uint32_t read = Read(frameWriter->Data(), offset, size);
   if (read != size) {
     LOG("GetNextFrame() Exit read=%u frame->Size=%zu", read, frame->Size());
--- a/dom/media/mp3/MP3Demuxer.cpp
+++ b/dom/media/mp3/MP3Demuxer.cpp
@@ -6,17 +6,16 @@
 
 #include "MP3Demuxer.h"
 
 #include <algorithm>
 #include <inttypes.h>
 #include <limits>
 
 #include "mozilla/Assertions.h"
-#include "nsAutoPtr.h"
 #include "TimeUnits.h"
 #include "VideoUtils.h"
 
 extern mozilla::LazyLogModule gMediaDemuxerLog;
 #define MP3LOG(msg, ...)                                                       \
   DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, msg, ##__VA_ARGS__)
 #define MP3LOGV(msg, ...)                                                      \
   DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, msg, ##__VA_ARGS__)
@@ -639,17 +638,17 @@ MP3TrackDemuxer::GetNextFrame(const Medi
          aRange.mStart, aRange.Length());
   if (!aRange.Length()) {
     return nullptr;
   }
 
   RefPtr<MediaRawData> frame = new MediaRawData();
   frame->mOffset = aRange.mStart;
 
-  nsAutoPtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
+  UniquePtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
   if (!frameWriter->SetSize(aRange.Length())) {
     MP3LOG("GetNext() Exit failed to allocated media buffer");
     return nullptr;
   }
 
   const uint32_t read =
     Read(frameWriter->Data(), frame->mOffset, frame->Size());
 
--- a/dom/media/mp4/Index.cpp
+++ b/dom/media/mp4/Index.cpp
@@ -105,17 +105,17 @@ already_AddRefed<MediaRawData> SampleIte
 
   RefPtr<MediaRawData> sample = new MediaRawData();
   sample->mTimecode= TimeUnit::FromMicroseconds(s->mDecodeTime);
   sample->mTime = TimeUnit::FromMicroseconds(s->mCompositionRange.start);
   sample->mDuration = TimeUnit::FromMicroseconds(s->mCompositionRange.Length());
   sample->mOffset = s->mByteRange.mStart;
   sample->mKeyframe = s->mSync;
 
-  nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
+  UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter());
   // Do the blocking read
   if (!writer->SetSize(s->mByteRange.Length())) {
     return nullptr;
   }
 
   size_t bytesRead;
   if (!mIndex->mSource->ReadAt(sample->mOffset, writer->Data(), sample->Size(),
                                &bytesRead) || bytesRead != sample->Size()) {
--- a/dom/media/mp4/MP4Demuxer.cpp
+++ b/dom/media/mp4/MP4Demuxer.cpp
@@ -495,17 +495,17 @@ MP4TrackDemuxer::GetNextSample()
           // handle the error later.
           // TODO: make demuxer errors non-fatal.
           break;
       }
     }
   }
 
   if (sample->mCrypto.mValid) {
-    nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
+    UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter());
     writer->mCrypto.mMode = mInfo->mCrypto.mMode;
 
     // Only use the default key parsed from the moov if we haven't already got
     // one from the sample group description.
     if (writer->mCrypto.mKeyId.Length() == 0) {
       writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
       writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
     }
--- a/dom/media/platforms/agnostic/bytestreams/Adts.cpp
+++ b/dom/media/platforms/agnostic/bytestreams/Adts.cpp
@@ -1,17 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Adts.h"
 #include "MediaData.h"
 #include "mozilla/Array.h"
 #include "mozilla/ArrayUtils.h"
-#include "nsAutoPtr.h"
 
 namespace mozilla
 {
 
 static const int kADTSHeaderSize = 7;
 
 int8_t
 Adts::GetFrequencyIndex(uint32_t aSamplesPerSecond)
@@ -51,17 +50,17 @@ Adts::ConvertSample(uint16_t aChannelCou
   header[1] = 0xf1;
   header[2] =
     ((aProfile - 1) << 6) + (aFrequencyIndex << 2) + (aChannelCount >> 2);
   header[3] = ((aChannelCount & 0x3) << 6) + (newSize >> 11);
   header[4] = (newSize & 0x7ff) >> 3;
   header[5] = ((newSize & 7) << 5) + 0x1f;
   header[6] = 0xfc;
 
-  nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
+  UniquePtr<MediaRawDataWriter> writer(aSample->CreateWriter());
   if (!writer->Prepend(&header[0], ArrayLength(header))) {
     return false;
   }
 
   if (aSample->mCrypto.mValid) {
     if (aSample->mCrypto.mPlainSizes.Length() == 0) {
       writer->mCrypto.mPlainSizes.AppendElement(kADTSHeaderSize);
       writer->mCrypto.mEncryptedSizes.AppendElement(aSample->Size() - kADTSHeaderSize);
@@ -83,17 +82,17 @@ Adts::RevertSample(MediaRawData* aSample
   {
     const uint8_t* header = aSample->Data();
     if (header[0] != 0xff || header[1] != 0xf1 || header[6] != 0xfc) {
       // Not ADTS.
       return false;
     }
   }
 
-  nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
+  UniquePtr<MediaRawDataWriter> writer(aSample->CreateWriter());
   writer->PopFront(kADTSHeaderSize);
 
   if (aSample->mCrypto.mValid) {
     if (aSample->mCrypto.mPlainSizes.Length() > 0 &&
         writer->mCrypto.mPlainSizes[0] >= kADTSHeaderSize) {
       writer->mCrypto.mPlainSizes[0] -= kADTSHeaderSize;
     }
   }
--- a/dom/media/platforms/agnostic/bytestreams/AnnexB.cpp
+++ b/dom/media/platforms/agnostic/bytestreams/AnnexB.cpp
@@ -5,17 +5,16 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/Unused.h"
 #include "AnnexB.h"
 #include "BufferReader.h"
 #include "ByteWriter.h"
 #include "MediaData.h"
-#include "nsAutoPtr.h"
 
 namespace mozilla
 {
 
 static const uint8_t kAnnexBDelimiter[] = { 0, 0, 0, 1 };
 
 Result<Ok, nsresult>
 AnnexB::ConvertSampleToAnnexB(mozilla::MediaRawData* aSample, bool aAddSPS)
@@ -50,17 +49,17 @@ AnnexB::ConvertSampleToAnnexB(mozilla::M
     if (!p) {
       break;
     }
     if (!writer.Write(p, nalLen)) {
       return Err(NS_ERROR_OUT_OF_MEMORY);
     }
   }
 
-  nsAutoPtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
+  UniquePtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
 
   if (!samplewriter->Replace(tmp.Elements(), tmp.Length())) {
     return Err(NS_ERROR_OUT_OF_MEMORY);
   }
 
   // Prepend the Annex B NAL with SPS and PPS tables to keyframes.
   if (aAddSPS && aSample->mKeyframe) {
     RefPtr<MediaByteBuffer> annexB =
@@ -249,17 +248,17 @@ AnnexB::ConvertSampleToAVCC(mozilla::Med
 
   nsTArray<uint8_t> nalu;
   ByteWriter writer(nalu);
   BufferReader reader(aSample->Data(), aSample->Size());
 
   if (ParseNALUnits(writer, reader).isErr()) {
     return false;
   }
-  nsAutoPtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
+  UniquePtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
   if (!samplewriter->Replace(nalu.Elements(), nalu.Length())) {
     return false;
   }
   // Create the AVCC header.
   RefPtr<mozilla::MediaByteBuffer> extradata = new mozilla::MediaByteBuffer;
   static const uint8_t kFakeExtraData[] = {
     1 /* version */,
     0x64 /* profile (High) */,
@@ -303,17 +302,17 @@ AnnexB::ConvertSampleTo4BytesAVCC(mozill
     if (!p) {
       return Ok();
     }
     if (!writer.WriteU32(nalLen)
         || !writer.Write(p, nalLen)) {
       return Err(NS_ERROR_OUT_OF_MEMORY);
     }
   }
-  nsAutoPtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
+  UniquePtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
   if (!samplewriter->Replace(dest.Elements(), dest.Length())) {
     return Err(NS_ERROR_OUT_OF_MEMORY);
   }
   return Ok();
 }
 
 bool
 AnnexB::IsAVCC(const mozilla::MediaRawData* aSample)
--- a/dom/media/wave/WaveDemuxer.cpp
+++ b/dom/media/wave/WaveDemuxer.cpp
@@ -7,17 +7,16 @@
 #include "WaveDemuxer.h"
 
 #include <inttypes.h>
 #include <algorithm>
 
 #include "mozilla/Assertions.h"
 #include "mozilla/EndianUtils.h"
 #include "BufferReader.h"
-#include "nsAutoPtr.h"
 #include "VideoUtils.h"
 #include "TimeUnits.h"
 
 using mozilla::media::TimeUnit;
 using mozilla::media::TimeIntervals;
 
 namespace mozilla {
 
@@ -510,17 +509,17 @@ WAVTrackDemuxer::GetNextChunk(const Medi
 {
   if (!aRange.Length()) {
     return nullptr;
   }
 
   RefPtr<MediaRawData> datachunk = new MediaRawData();
   datachunk->mOffset = aRange.mStart;
 
-  nsAutoPtr<MediaRawDataWriter> chunkWriter(datachunk->CreateWriter());
+  UniquePtr<MediaRawDataWriter> chunkWriter(datachunk->CreateWriter());
   if (!chunkWriter->SetSize(aRange.Length())) {
     return nullptr;
   }
 
   const uint32_t read =
     Read(chunkWriter->Data(), datachunk->mOffset, datachunk->Size());
 
   if (read != aRange.Length()) {
@@ -554,17 +553,17 @@ WAVTrackDemuxer::GetFileHeader(const Med
 {
   if (!aRange.Length()) {
     return nullptr;
   }
 
   RefPtr<MediaRawData> fileHeader = new MediaRawData();
   fileHeader->mOffset = aRange.mStart;
 
-  nsAutoPtr<MediaRawDataWriter> headerWriter(fileHeader->CreateWriter());
+  UniquePtr<MediaRawDataWriter> headerWriter(fileHeader->CreateWriter());
   if (!headerWriter->SetSize(aRange.Length())) {
     return nullptr;
   }
 
   const uint32_t read =
     Read(headerWriter->Data(), fileHeader->mOffset, fileHeader->Size());
 
   if (read != aRange.Length()) {
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -14,17 +14,16 @@
 #include "VPXDecoder.h"
 #include "WebMDemuxer.h"
 #include "WebMBufferedParser.h"
 #include "gfx2DGlue.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/SharedThreadPool.h"
 #include "MediaDataDemuxer.h"
-#include "nsAutoPtr.h"
 #include "nsAutoRef.h"
 #include "NesteggPacketHolder.h"
 #include "XiphExtradata.h"
 #include "prprf.h"           // leaving it for PR_vsnprintf()
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Sprintf.h"
 
 #include <algorithm>
@@ -776,17 +775,17 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
       if (discardFrames.isValid()) {
         sample->mDiscardPadding = discardFrames.value();
       }
     }
 
     if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_UNENCRYPTED ||
         packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED ||
         packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_PARTITIONED) {
-      nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
+      UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter());
       unsigned char const* iv;
       size_t ivLength;
       nestegg_packet_iv(holder->Packet(), &iv, &ivLength);
       writer->mCrypto.mValid = true;
       writer->mCrypto.mIVSize = ivLength;
       if (ivLength == 0) {
         // Frame is not encrypted
         writer->mCrypto.mPlainSizes.AppendElement(length);
@@ -1267,17 +1266,17 @@ WebMTrackDemuxer::Reset()
   }
 }
 
 void
 WebMTrackDemuxer::UpdateSamples(nsTArray<RefPtr<MediaRawData>>& aSamples)
 {
   for (const auto& sample : aSamples) {
     if (sample->mCrypto.mValid) {
-      nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
+      UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter());
       writer->mCrypto.mMode = mInfo->mCrypto.mMode;
       writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
       writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
     }
   }
   if (mNextKeyframeTime.isNothing() ||
       aSamples.LastElement()->mTime >= mNextKeyframeTime.value()) {
     SetNextKeyFrameTime();
--- a/js/src/builtin/DataViewObject.cpp
+++ b/js/src/builtin/DataViewObject.cpp
@@ -87,17 +87,17 @@ DataViewObject::create(JSContext* cx, ui
         // need to fail here if isSharedMemory.  However, mmap() can
         // place a SharedArrayRawBuffer up against the bottom end of a
         // nursery chunk, and a zero-length buffer will erroneously be
         // perceived as being inside the nursery; sidestep that.
         if (isSharedMemory) {
             MOZ_ASSERT(arrayBuffer->byteLength() == 0 &&
                        (uintptr_t(ptr.unwrapValue()) & gc::ChunkMask) == 0);
         } else {
-            cx->zone()->group()->storeBuffer().putWholeCell(obj);
+            cx->runtime()->gc.storeBuffer().putWholeCell(obj);
         }
     }
 
     // Verify that the private slot is at the expected place
     MOZ_ASSERT(obj->numFixedSlots() == TypedArrayObject::DATA_SLOT);
 
     if (arrayBuffer->is<ArrayBufferObject>()) {
         if (!arrayBuffer->as<ArrayBufferObject>().addView(cx, obj))
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -261,33 +261,33 @@ MapIteratorObject::create(JSContext* cx,
 
 void
 MapIteratorObject::finalize(FreeOp* fop, JSObject* obj)
 {
     MOZ_ASSERT(fop->onActiveCooperatingThread());
     MOZ_ASSERT(!IsInsideNursery(obj));
 
     auto range = MapIteratorObjectRange(&obj->as<NativeObject>());
-    MOZ_ASSERT(!obj->zone()->group()->nursery().isInside(range));
+    MOZ_ASSERT(!fop->runtime()->gc.nursery().isInside(range));
 
     fop->delete_(range);
 }
 
 size_t
 MapIteratorObject::objectMoved(JSObject* obj, JSObject* old)
 {
     if (!IsInsideNursery(old))
         return 0;
 
     MapIteratorObject* iter = &obj->as<MapIteratorObject>();
     ValueMap::Range* range = MapIteratorObjectRange(iter);
     if (!range)
         return 0;
 
-    Nursery& nursery = iter->zone()->group()->nursery();
+    Nursery& nursery = iter->runtimeFromActiveCooperatingThread()->gc.nursery();
     if (!nursery.isInside(range)) {
         nursery.removeMallocedBuffer(range);
         return 0;
     }
 
     AutoEnterOOMUnsafeRegion oomUnsafe;
     auto newRange = iter->zone()->pod_malloc<ValueMap::Range>();
     if (!newRange)
@@ -562,17 +562,18 @@ WriteBarrierPostImpl(ObjectT* obj, const
         return true;
 
     NurseryKeysVector* keys = GetNurseryKeys(obj);
     if (!keys) {
         keys = AllocNurseryKeys(obj);
         if (!keys)
             return false;
 
-        key->zone()->group()->storeBuffer().putGeneric(OrderedHashTableRef<ObjectT>(obj));
+        JSRuntime* rt = key->runtimeFromActiveCooperatingThread();
+        rt->gc.storeBuffer().putGeneric(OrderedHashTableRef<ObjectT>(obj));
     }
 
     if (!keys->append(key))
         return false;
 
     return true;
 }
 
@@ -1111,33 +1112,33 @@ SetIteratorObject::create(JSContext* cx,
 
 void
 SetIteratorObject::finalize(FreeOp* fop, JSObject* obj)
 {
     MOZ_ASSERT(fop->onActiveCooperatingThread());
     MOZ_ASSERT(!IsInsideNursery(obj));
 
     auto range = SetIteratorObjectRange(&obj->as<NativeObject>());
-    MOZ_ASSERT(!obj->zone()->group()->nursery().isInside(range));
+    MOZ_ASSERT(!fop->runtime()->gc.nursery().isInside(range));
 
     fop->delete_(range);
 }
 
 size_t
 SetIteratorObject::objectMoved(JSObject* obj, JSObject* old)
 {
     if (!IsInsideNursery(old))
         return 0;
 
     SetIteratorObject* iter = &obj->as<SetIteratorObject>();
     ValueSet::Range* range = SetIteratorObjectRange(iter);
     if (!range)
         return 0;
 
-    Nursery& nursery = iter->zone()->group()->nursery();
+    Nursery& nursery = iter->runtimeFromActiveCooperatingThread()->gc.nursery();
     if (!nursery.isInside(range)) {
         nursery.removeMallocedBuffer(range);
         return 0;
     }
 
     AutoEnterOOMUnsafeRegion oomUnsafe;
     auto newRange = iter->zone()->pod_malloc<ValueSet::Range>();
     if (!newRange)
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -347,17 +347,17 @@ GC(JSContext* cx, unsigned argc, Value* 
     return ReturnStringCopy(cx, args, buf);
 }
 
 static bool
 MinorGC(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.get(0) == BooleanValue(true))
-        cx->zone()->group()->storeBuffer().setAboutToOverflow(JS::gcreason::FULL_GENERIC_BUFFER);
+        cx->runtime()->gc.storeBuffer().setAboutToOverflow(JS::gcreason::FULL_GENERIC_BUFFER);
 
     cx->minorGC(JS::gcreason::API);
     args.rval().setUndefined();
     return true;
 }
 
 #define FOR_EACH_GC_PARAM(_)                                                    \
     _("maxBytes",                   JSGC_MAX_BYTES,                      true)  \
@@ -2534,17 +2534,18 @@ testingFunc_bailAfter(JSContext* cx, uns
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != 1 || !args[0].isInt32() || args[0].toInt32() < 0) {
         JS_ReportErrorASCII(cx, "Argument must be a positive number that fits in an int32");
         return false;
     }
 
 #ifdef DEBUG
-    cx->zone()->group()->setIonBailAfter(args[0].toInt32());
+    if (auto* jitRuntime = cx->runtime()->jitRuntime())
+        jitRuntime->setIonBailAfter(args[0].toInt32());
 #endif
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 testingFunc_inJit(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -1429,17 +1429,17 @@ OutlineTypedObject::setOwnerAndData(JSOb
     // Typed objects cannot move from one owner to another, so don't worry
     // about pre barriers during this initialization.
     owner_ = owner;
     data_ = data;
 
     // Trigger a post barrier when attaching an object outside the nursery to
     // one that is inside it.
     if (owner && !IsInsideNursery(this) && IsInsideNursery(owner))
-        zone()->group()->storeBuffer().putWholeCell(this);
+        owner->storeBuffer()->putWholeCell(this);
 }
 
 /*static*/ OutlineTypedObject*
 OutlineTypedObject::createUnattachedWithClass(JSContext* cx,
                                               const Class* clasp,
                                               HandleTypeDescr descr,
                                               int32_t length,
                                               gc::InitialHeap heap)
@@ -1629,17 +1629,17 @@ OutlineTypedObject::obj_trace(JSTracer* 
     if (owner != oldOwner &&
         (owner->is<InlineTypedObject>() ||
          owner->as<ArrayBufferObject>().hasInlineData()))
     {
         newData += reinterpret_cast<uint8_t*>(owner) - reinterpret_cast<uint8_t*>(oldOwner);
         typedObj.setData(newData);
 
         if (trc->isTenuringTracer()) {
-            Nursery& nursery = typedObj.zoneFromAnyThread()->group()->nursery();
+            Nursery& nursery = trc->runtime()->gc.nursery();
             nursery.maybeSetForwardingPointer(trc, oldData, newData, /* direct = */ false);
         }
     }
 
     if (!descr.opaque() || !typedObj.isAttached())
         return;
 
     descr.traceInstances(trc, newData, 1);
@@ -2139,17 +2139,17 @@ InlineTypedObject::obj_moved(JSObject* d
     // whether this object moved and where it was moved from.
     TypeDescr& descr = dst->as<InlineTypedObject>().typeDescr();
     if (descr.kind() == type::Array) {
         // The forwarding pointer can be direct as long as there is enough
         // space for it. Other objects might point into the object's buffer,
         // but they will not set any direct forwarding pointers.
         uint8_t* oldData = reinterpret_cast<uint8_t*>(src) + offsetOfDataStart();
         uint8_t* newData = dst->as<InlineTypedObject>().inlineTypedMem();
-        auto& nursery = dst->zone()->group()->nursery();
+        auto& nursery = dst->runtimeFromActiveCooperatingThread()->gc.nursery();
         bool direct = descr.size() >= sizeof(uintptr_t);
         nursery.setForwardingPointerWhileTenuring(oldData, newData, direct);
     }
 
     return 0;
 }
 
 ArrayBufferObject*
@@ -2190,17 +2190,17 @@ InlineTransparentTypedObject::getOrCreat
     buffer->setHasTypedObjectViews();
 
     if (!table->add(cx, this, buffer))
         return nullptr;
 
     if (IsInsideNursery(this)) {
         // Make sure the buffer is traced by the next generational collection,
         // so that its data pointer is updated after this typed object moves.
-        zone()->group()->storeBuffer().putWholeCell(buffer);
+        storeBuffer()->putWholeCell(buffer);
     }
 
     return buffer;
 }
 
 ArrayBufferObject*
 OutlineTransparentTypedObject::getOrCreateBuffer(JSContext* cx)
 {
--- a/js/src/gc/GC-inl.h
+++ b/js/src/gc/GC-inl.h
@@ -202,17 +202,18 @@ class ZoneCellIter<TenuredCell> {
     mozilla::Maybe<JS::AutoAssertNoGC> nogc;
 
   protected:
     // For use when a subclass wants to insert some setup before init().
     ZoneCellIter() {}
 
     void init(JS::Zone* zone, AllocKind kind) {
         MOZ_ASSERT_IF(IsNurseryAllocable(kind),
-                      zone->isAtomsZone() || zone->group()->nursery().isEmpty());
+                      (zone->isAtomsZone() ||
+                       zone->runtimeFromActiveCooperatingThread()->gc.nursery().isEmpty()));
         initForTenuredIteration(zone, kind);
     }
 
     void initForTenuredIteration(JS::Zone* zone, AllocKind kind) {
         JSRuntime* rt = zone->runtimeFromAnyThread();
 
         // If called from outside a GC, ensure that the heap is in a state
         // that allows us to iterate.
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -1060,20 +1060,18 @@ GCRuntime::setZeal(uint8_t zeal, uint32_
             nursery().leaveZealMode();
         }
 
         if (isIncrementalGCInProgress())
             finishGC(JS::gcreason::DEBUG_GC);
     }
 
     ZealMode zealMode = ZealMode(zeal);
-    if (zealMode == ZealMode::GenerationalGC) {
-        for (ZoneGroupsIter group(rt); !group.done(); group.next())
-            group->nursery().enterZealMode();
-    }
+    if (zealMode == ZealMode::GenerationalGC)
+        nursery().enterZealMode();
 
     // Some modes are mutually exclusive. If we're setting one of those, we
     // first reset all of them.
     if (IncrementalSliceZealModes.contains(zealMode)) {
         for (auto mode : IncrementalSliceZealModes)
             clearZealMode(mode);
     }
 
@@ -1277,18 +1275,17 @@ GCRuntime::finish()
     groups().clear();
 
     FreeChunkPool(fullChunks_.ref());
     FreeChunkPool(availableChunks_.ref());
     FreeChunkPool(emptyChunks_.ref());
 
     FinishTrace();
 
-    for (ZoneGroupsIter group(rt); !group.done(); group.next())
-        group->nursery().printTotalProfileTimes();
+    nursery().printTotalProfileTimes();
     stats().printTotalProfileTimes();
 }
 
 bool
 GCRuntime::setParameter(JSGCParamKey key, uint32_t value, AutoLockGC& lock)
 {
     switch (key) {
       case JSGC_MAX_MALLOC_BYTES:
@@ -6604,17 +6601,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(nursery().isEmpty());
         zone->changeGCState(Zone::Finished, Zone::Compact);
 
         if (relocateArenas(zone, reason, relocatedArenas, sliceBudget)) {
             updateZonePointersToRelocatedCells(zone);
             relocatedZones.append(zone);
         } else {
             zone->changeGCState(Zone::Compact, Zone::Finished);
         }
@@ -6694,38 +6691,26 @@ 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)
   : runtime(rt),
     prevState(rt->mainContextFromOwnThread()->heapState),
     pseudoFrame(rt->mainContextFromOwnThread(), HeapStateToLabel(heapState),
                 ProfileEntry::Category::GC)
 {
     MOZ_ASSERT(prevState == JS::HeapState::Idle);
     MOZ_ASSERT(heapState != JS::HeapState::Idle);
-    MOZ_ASSERT_IF(heapState == JS::HeapState::MajorCollecting, AllNurseriesAreEmpty(rt));
+    MOZ_ASSERT_IF(heapState == JS::HeapState::MajorCollecting, rt->gc.nursery().isEmpty());
 
     // Session always begins with lock held, see comment in class definition.
     maybeLock.emplace(rt);
 
     rt->mainContextFromOwnThread()->heapState = heapState;
 }
 
 AutoTraceSession::~AutoTraceSession()
@@ -7686,18 +7671,17 @@ 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.
-    for (ZoneGroupsIter group(rt); !group.done(); group.next())
-        group->nursery().waitBackgroundFreeEnd();
+    nursery().waitBackgroundFreeEnd();
 
     AutoLockGC lock(rt);
     onOutOfMallocMemory(lock);
 }
 
 void
 GCRuntime::onOutOfMallocMemory(const AutoLockGC& lock)
 {
@@ -7751,20 +7735,18 @@ JS::AutoDisableGenerationalGC::AutoDisab
         cx->runtime()->gc.evictNursery(JS::gcreason::API);
         cx->nursery().disable();
     }
     ++cx->generationalDisabled;
 }
 
 JS::AutoDisableGenerationalGC::~AutoDisableGenerationalGC()
 {
-    if (--cx->generationalDisabled == 0) {
-        for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next())
-            group->nursery().enable();
-    }
+    if (--cx->generationalDisabled == 0)
+        cx->nursery().enable();
 }
 
 JS_PUBLIC_API(bool)
 JS::IsGenerationalGCEnabled(JSRuntime* rt)
 {
     return !rt->mainContextFromOwnThread()->generationalDisabled;
 }
 
@@ -7799,18 +7781,17 @@ GCRuntime::gcIfRequested()
 void
 js::gc::FinishGC(JSContext* cx)
 {
     if (JS::IsIncrementalGCInProgress(cx)) {
         JS::PrepareForIncrementalGC(cx);
         JS::FinishIncrementalGC(cx, JS::gcreason::API);
     }
 
-    for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next())
-        group->nursery().waitBackgroundFreeEnd();
+    cx->nursery().waitBackgroundFreeEnd();
 }
 
 AutoPrepareForTracing::AutoPrepareForTracing(JSContext* cx)
 {
     js::gc::FinishGC(cx);
     session_.emplace(cx->runtime());
 }
 
@@ -7853,19 +7834,16 @@ js::NewCompartment(JSContext* cx, JSPrin
 
     if (!group) {
         MOZ_ASSERT(!zone);
         group = cx->new_<ZoneGroup>(rt);
         if (!group)
             return nullptr;
 
         groupHolder.reset(group);
-
-        if (cx->generationalDisabled)
-            group->nursery().disable();
     }
 
     if (!zone) {
         zone = cx->new_<Zone>(cx->runtime(), group);
         if (!zone)
             return nullptr;
 
         zoneHolder.reset(zone);
@@ -8937,25 +8915,25 @@ StateName(State state)
     MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("invalide gc::State enum value");
 }
 
 void
 AutoAssertEmptyNursery::checkCondition(JSContext* cx) {
     if (!noAlloc)
         noAlloc.emplace();
     this->cx = cx;
-    MOZ_ASSERT(AllNurseriesAreEmpty(cx->runtime()));
+    MOZ_ASSERT(cx->nursery().isEmpty());
 }
 
 AutoEmptyNursery::AutoEmptyNursery(JSContext* cx)
   : AutoAssertEmptyNursery()
 {
     MOZ_ASSERT(!cx->suppressGC);
     cx->runtime()->gc.stats().suspendPhases();
-    EvictAllNurseries(cx->runtime(), JS::gcreason::EVICT_NURSERY);
+    cx->runtime()->gc.evictNursery(JS::gcreason::EVICT_NURSERY);
     cx->runtime()->gc.stats().resumePhases();
     checkCondition(cx);
 }
 
 } /* namespace gc */
 } /* namespace js */
 
 #ifdef DEBUG
--- a/js/src/gc/Nursery-inl.h
+++ b/js/src/gc/Nursery-inl.h
@@ -134,17 +134,11 @@ ReallocateObjectBuffer(JSContext* cx, JS
     T* buffer =  static_cast<T*>(cx->nursery().reallocateBuffer(obj, oldBuffer,
                                                                 oldCount * sizeof(T),
                                                                 newCount * sizeof(T)));
     if (!buffer)
         ReportOutOfMemory(cx);
     return buffer;
 }
 
-static inline void
-EvictAllNurseries(JSRuntime* rt, JS::gcreason::Reason reason = JS::gcreason::EVICT_NURSERY)
-{
-    rt->gc.evictNursery(reason);
-}
-
 } // namespace js
 
 #endif /* gc_Nursery_inl_h */
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -279,18 +279,18 @@ js::gc::GCRuntime::traceRuntimeForMinorG
 }
 
 void
 js::TraceRuntime(JSTracer* trc)
 {
     MOZ_ASSERT(!trc->isMarkingTracer());
 
     JSRuntime* rt = trc->runtime();
-    EvictAllNurseries(rt);
-    AutoPrepareForTracing prep(TlsContext.get());
+    rt->gc.evictNursery();
+    AutoPrepareForTracing prep(rt->mainContextFromOwnThread());
     gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
     rt->gc.traceRuntime(trc, prep.session());
 }
 
 void
 js::gc::GCRuntime::traceRuntime(JSTracer* trc, AutoTraceSession& session)
 {
     MOZ_ASSERT(!rt->isBeingDestroyed());
--- a/js/src/gc/StoreBuffer.cpp
+++ b/js/src/gc/StoreBuffer.cpp
@@ -133,30 +133,30 @@ ArenaCellSet::ArenaCellSet(Arena* arena,
     MOZ_ASSERT(arena);
     bits.clear(false);
 }
 
 ArenaCellSet*
 StoreBuffer::WholeCellBuffer::allocateCellSet(Arena* arena)
 {
     Zone* zone = arena->zone;
-    Nursery& nursery = zone->group()->nursery();
-    if (!nursery.isEnabled())
+    JSRuntime* rt = zone->runtimeFromActiveCooperatingThread();
+    if (!rt->gc.nursery().isEnabled())
         return nullptr;
 
     AutoEnterOOMUnsafeRegion oomUnsafe;
     auto cells = storage_->new_<ArenaCellSet>(arena, head_);
     if (!cells)
         oomUnsafe.crash("Failed to allocate ArenaCellSet");
 
     arena->bufferedCells() = cells;
     head_ = cells;
 
     if (isAboutToOverflow())
-        zone->group()->storeBuffer().setAboutToOverflow(JS::gcreason::FULL_WHOLE_CELL_BUFFER);
+        rt->gc.storeBuffer().setAboutToOverflow(JS::gcreason::FULL_WHOLE_CELL_BUFFER);
 
     return cells;
 }
 
 void
 StoreBuffer::WholeCellBuffer::clear()
 {
     for (ArenaCellSet* set = head_; set; set = set->next)
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -577,17 +577,19 @@ struct Zone : public JS::shadow::Zone,
         // Set a new uid on the cell.
         *uidp = js::gc::NextCellUniqueId(runtimeFromAnyThread());
         if (!uniqueIds().add(p, cell, *uidp))
             return false;
 
         // If the cell was in the nursery, hopefully unlikely, then we need to
         // tell the nursery about it so that it can sweep the uid if the thing
         // does not get tenured.
-        if (IsInsideNursery(cell) && !group()->nursery().addedUniqueIdToCell(cell)) {
+        if (IsInsideNursery(cell) &&
+            !runtimeFromActiveCooperatingThread()->gc.nursery().addedUniqueIdToCell(cell))
+        {
             uniqueIds().remove(cell);
             return false;
         }
 
         return true;
     }
 
     js::HashNumber getHashCodeInfallible(js::gc::Cell* cell) {
--- a/js/src/gc/ZoneGroup.cpp
+++ b/js/src/gc/ZoneGroup.cpp
@@ -14,34 +14,22 @@
 using namespace js;
 
 namespace js {
 
 ZoneGroup::ZoneGroup(JSRuntime* runtime)
   : runtime(runtime),
     helperThreadOwnerContext_(nullptr),
     zones_(this),
-    helperThreadUse(HelperThreadUse::None),
-#ifdef DEBUG
-    ionBailAfter_(this, 0),
-#endif
-    numFinishedBuilders(0),
-    ionLazyLinkListSize_(0)
+    helperThreadUse(HelperThreadUse::None)
 {}
 
 ZoneGroup::~ZoneGroup()
 {
-#ifdef DEBUG
     MOZ_ASSERT(helperThreadUse == HelperThreadUse::None);
-    {
-        AutoLockHelperThreadState lock;
-        MOZ_ASSERT(ionLazyLinkListSize_ == 0);
-        MOZ_ASSERT(ionLazyLinkList().isEmpty());
-    }
-#endif
 
     if (this == runtime->gc.systemZoneGroup)
         runtime->gc.systemZoneGroup = nullptr;
 }
 
 void
 ZoneGroup::setHelperThreadOwnerContext(JSContext* cx)
 {
@@ -52,48 +40,16 @@ ZoneGroup::setHelperThreadOwnerContext(J
 bool
 ZoneGroup::ownedByCurrentHelperThread()
 {
     MOZ_ASSERT(usedByHelperThread());
     MOZ_ASSERT(TlsContext.get());
     return helperThreadOwnerContext_ == TlsContext.get();
 }
 
-ZoneGroup::IonBuilderList&
-ZoneGroup::ionLazyLinkList()
-{
-    MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime),
-               "Should only be mutated by the active thread.");
-    return ionLazyLinkList_.ref();
-}
-
-void
-ZoneGroup::ionLazyLinkListRemove(jit::IonBuilder* builder)
-{
-    MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime),
-               "Should only be mutated by the active thread.");
-    MOZ_ASSERT(this == builder->script()->zone()->group());
-    MOZ_ASSERT(ionLazyLinkListSize_ > 0);
-
-    builder->removeFrom(ionLazyLinkList());
-    ionLazyLinkListSize_--;
-
-    MOZ_ASSERT(ionLazyLinkList().isEmpty() == (ionLazyLinkListSize_ == 0));
-}
-
-void
-ZoneGroup::ionLazyLinkListAdd(jit::IonBuilder* builder)
-{
-    MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime),
-               "Should only be mutated by the active thread.");
-    MOZ_ASSERT(this == builder->script()->zone()->group());
-    ionLazyLinkList().insertFront(builder);
-    ionLazyLinkListSize_++;
-}
-
 void
 ZoneGroup::deleteEmptyZone(Zone* zone)
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));
     MOZ_ASSERT(zone->group() == this);
     MOZ_ASSERT(zone->compartments().empty());
     for (auto& i : zones()) {
         if (i == zone) {
--- a/js/src/gc/ZoneGroup.h
+++ b/js/src/gc/ZoneGroup.h
@@ -77,59 +77,15 @@ class ZoneGroup
     void clearUsedByHelperThread() {
         MOZ_ASSERT(helperThreadUse != HelperThreadUse::None);
         helperThreadUse = HelperThreadUse::None;
     }
 
     explicit ZoneGroup(JSRuntime* runtime);
     ~ZoneGroup();
 
-    inline Nursery& nursery();
-    inline gc::StoreBuffer& storeBuffer();
-
-    inline bool isCollecting();
-    inline bool isGCScheduled();
-
     // Delete an empty zone after its contents have been merged.
     void deleteEmptyZone(Zone* zone);
-
-#ifdef DEBUG
-  private:
-    // The number of possible bailing places encounters before forcefully bailing
-    // in that place. Zero means inactive.
-    ZoneGroupData<uint32_t> ionBailAfter_;
-
-  public:
-    void* addressOfIonBailAfter() { return &ionBailAfter_; }
-
-    // Set after how many bailing places we should forcefully bail.
-    // Zero disables this feature.
-    void setIonBailAfter(uint32_t after) {
-        ionBailAfter_ = after;
-    }
-#endif
-
-    // Number of Ion compilations which were finished off thread and are
-    // waiting to be lazily linked. This is only set while holding the helper
-    // thread state lock, but may be read from at other times.
-    mozilla::Atomic<size_t> numFinishedBuilders;
-
-  private:
-    /* List of Ion compilation waiting to get linked. */
-    typedef mozilla::LinkedList<js::jit::IonBuilder> IonBuilderList;
-
-    js::HelperThreadLockData<IonBuilderList> ionLazyLinkList_;
-    js::HelperThreadLockData<size_t> ionLazyLinkListSize_;
-
-  public:
-    IonBuilderList& ionLazyLinkList();
-
-    size_t ionLazyLinkListSize() {
-        return ionLazyLinkListSize_;
-    }
-
-    void ionLazyLinkListRemove(js::jit::IonBuilder* builder);
-    void ionLazyLinkListAdd(js::jit::IonBuilder* builder);
 };
 
 } // namespace js
 
 #endif // gc_Zone_h
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -10483,17 +10483,17 @@ CodeGenerator::link(JSContext* cx, Compi
     if (recovers_.size())
         ionScript->copyRecovers(&recovers_);
     if (graph.numConstants()) {
         const Value* vp = graph.constantPool();
         ionScript->copyConstants(vp);
         for (size_t i = 0; i < graph.numConstants(); i++) {
             const Value& v = vp[i];
             if ((v.isObject() || v.isString()) && IsInsideNursery(v.toGCThing())) {
-                cx->zone()->group()->storeBuffer().putWholeCell(script);
+                cx->runtime()->gc.storeBuffer().putWholeCell(script);
                 break;
             }
         }
     }
 
     // Attach any generated script counts to the script.
     if (IonScriptCounts* counts = extractScriptCounts())
         script->addIonCounts(counts);
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -162,17 +162,17 @@ CompileZone::isAtomsZone()
 {
     return zone()->isAtomsZone();
 }
 
 #ifdef DEBUG
 const void*
 CompileZone::addressOfIonBailAfter()
 {
-    return zone()->group()->addressOfIonBailAfter();
+    return zone()->runtimeFromAnyThread()->jitRuntime()->addressOfIonBailAfter();
 }
 #endif
 
 const void*
 CompileZone::addressOfNeedsIncrementalBarrier()
 {
     return zone()->addressOfNeedsIncrementalBarrier();
 }
@@ -207,31 +207,32 @@ CompileZone::addressOfStringNurseryCurre
 {
     return zone()->runtimeFromAnyThread()->gc.addressOfStringNurseryCurrentEnd();
 }
 
 bool
 CompileZone::canNurseryAllocateStrings()
 {
     return nurseryExists() &&
-        zone()->group()->nursery().canAllocateStrings() &&
+        zone()->runtimeFromAnyThread()->gc.nursery().canAllocateStrings() &&
         zone()->allocNurseryStrings;
 }
 
 bool
 CompileZone::nurseryExists()
 {
-    return zone()->group()->nursery().exists();
+    return zone()->runtimeFromAnyThread()->gc.nursery().exists();
 }
 
 void
 CompileZone::setMinorGCShouldCancelIonCompilations()
 {
     MOZ_ASSERT(CurrentThreadCanAccessZone(zone()));
-    zone()->group()->storeBuffer().setShouldCancelIonCompilations();
+    JSRuntime* rt = zone()->runtimeFromActiveCooperatingThread();
+    rt->gc.storeBuffer().setShouldCancelIonCompilations();
 }
 
 JSCompartment*
 CompileCompartment::compartment()
 {
     return reinterpret_cast<JSCompartment*>(this);
 }
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -208,22 +208,31 @@ JitRuntime::JitRuntime()
     argumentsRectifierReturnOffset_(0),
     invalidatorOffset_(0),
     lazyLinkStubOffset_(0),
     interpreterStubOffset_(0),
     debugTrapHandler_(nullptr),
     baselineDebugModeOSRHandler_(nullptr),
     trampolineCode_(nullptr),
     functionWrappers_(nullptr),
-    jitcodeGlobalTable_(nullptr)
+    jitcodeGlobalTable_(nullptr),
+#ifdef DEBUG
+    ionBailAfter_(0),
+#endif
+    numFinishedBuilders_(0),
+    ionLazyLinkListSize_(0)
 {
 }
 
 JitRuntime::~JitRuntime()
 {
+    MOZ_ASSERT(numFinishedBuilders_ == 0);
+    MOZ_ASSERT(ionLazyLinkListSize_ == 0);
+    MOZ_ASSERT(ionLazyLinkList_.ref().isEmpty());
+
     js_delete(functionWrappers_.ref());
 
     // By this point, the jitcode global table should be empty.
     MOZ_ASSERT_IF(jitcodeGlobalTable_, jitcodeGlobalTable_->empty());
     js_delete(jitcodeGlobalTable_.ref());
 }
 
 uint32_t
@@ -368,16 +377,48 @@ JitRuntime::debugTrapHandler(JSContext* 
         // be allocated in the atoms compartment.
         AutoLockForExclusiveAccess lock(cx);
         AutoAtomsCompartment ac(cx, lock);
         debugTrapHandler_ = generateDebugTrapHandler(cx);
     }
     return debugTrapHandler_;
 }
 
+JitRuntime::IonBuilderList&
+JitRuntime::ionLazyLinkList(JSRuntime* rt)
+{
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt),
+               "Should only be mutated by the active thread.");
+    return ionLazyLinkList_.ref();
+}
+
+void
+JitRuntime::ionLazyLinkListRemove(JSRuntime* rt, jit::IonBuilder* builder)
+{
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt),
+               "Should only be mutated by the active thread.");
+    MOZ_ASSERT(rt == builder->script()->runtimeFromActiveCooperatingThread());
+    MOZ_ASSERT(ionLazyLinkListSize_ > 0);
+
+    builder->removeFrom(ionLazyLinkList(rt));
+    ionLazyLinkListSize_--;
+
+    MOZ_ASSERT(ionLazyLinkList(rt).isEmpty() == (ionLazyLinkListSize_ == 0));
+}
+
+void
+JitRuntime::ionLazyLinkListAdd(JSRuntime* rt, jit::IonBuilder* builder)
+{
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt),
+               "Should only be mutated by the active thread.");
+    MOZ_ASSERT(rt == builder->script()->runtimeFromActiveCooperatingThread());
+    ionLazyLinkList(rt).insertFront(builder);
+    ionLazyLinkListSize_++;
+}
+
 uint8_t*
 JSContext::allocateOsrTempData(size_t size)
 {
     osrTempData_ = (uint8_t*)js_realloc(osrTempData_, size);
     return osrTempData_;
 }
 
 void
@@ -480,17 +521,17 @@ jit::FinishOffThreadBuilder(JSRuntime* r
     if (builder->script()->baselineScript()->hasPendingIonBuilder() &&
         builder->script()->baselineScript()->pendingIonBuilder() == builder)
     {
         builder->script()->baselineScript()->removePendingIonBuilder(runtime, builder->script());
     }
 
     // If the builder is still in one of the helper thread list, then remove it.
     if (builder->isInList())
-        builder->script()->zone()->group()->ionLazyLinkListRemove(builder);
+        runtime->jitRuntime()->ionLazyLinkListRemove(runtime, builder);
 
     // Clear the recompiling flag of the old ionScript, since we continue to
     // use the old ionScript if recompiling fails.
     if (builder->script()->hasIonScript())
         builder->script()->ionScript()->clearRecompiling();
 
     // Clean up if compilation did not succeed.
     if (builder->script()->isIonCompilingOffThread()) {
@@ -541,17 +582,17 @@ jit::LinkIonScript(JSContext* cx, Handle
         AutoLockHelperThreadState lock;
 
         // Get the pending builder from the Ion frame.
         MOZ_ASSERT(calleeScript->hasBaselineScript());
         builder = calleeScript->baselineScript()->pendingIonBuilder();
         calleeScript->baselineScript()->removePendingIonBuilder(cx->runtime(), calleeScript);
 
         // Remove from pending.
-        cx->zone()->group()->ionLazyLinkListRemove(builder);
+        cx->runtime()->jitRuntime()->ionLazyLinkListRemove(cx->runtime(), builder);
     }
 
     {
         AutoEnterAnalysis enterTypes(cx);
         if (!LinkBackgroundCodeGen(cx, builder)) {
             // Silently ignore OOM during code generation. The assembly code
             // doesn't has code to handle it after linking happened. So it's
             // not OK to throw a catchable exception from there.
@@ -1891,72 +1932,69 @@ CompileBackEnd(MIRGenerator* mir)
     if (!lir)
         return nullptr;
 
     return GenerateCode(mir, lir);
 }
 
 // Find a builder which the current thread can finish.
 static IonBuilder*
-GetFinishedBuilder(ZoneGroup* group, GlobalHelperThreadState::IonBuilderVector& finished)
+GetFinishedBuilder(JSRuntime* rt, GlobalHelperThreadState::IonBuilderVector& finished,
+                   const AutoLockHelperThreadState& locked)
 {
     for (size_t i = 0; i < finished.length(); i++) {
         IonBuilder* testBuilder = finished[i];
-        if (testBuilder->script()->runtimeFromAnyThread() == group->runtime &&
-            testBuilder->script()->zone()->group() == group) {
+        if (testBuilder->script()->runtimeFromAnyThread() == rt) {
             HelperThreadState().remove(finished, &i);
-            group->numFinishedBuilders--;
+            rt->jitRuntime()->numFinishedBuildersRef(locked)--;
             return testBuilder;
         }
     }
 
     return nullptr;
 }
 
 void
-AttachFinishedCompilations(ZoneGroup* group, JSContext* maybecx)
+AttachFinishedCompilations(JSContext* cx)
 {
-    MOZ_ASSERT_IF(maybecx, maybecx->zone()->group() == group);
-
-    if (!group->numFinishedBuilders)
+    JSRuntime* rt = cx->runtime();
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
+
+    if (!rt->jitRuntime() || !rt->jitRuntime()->numFinishedBuilders())
         return;
 
     AutoLockHelperThreadState lock;
     GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
 
     // Incorporate any off thread compilations for the runtime which have
     // finished, failed or have been cancelled.
     while (true) {
         // Find a finished builder for the zone group.
-        IonBuilder* builder = GetFinishedBuilder(group, finished);
+        IonBuilder* builder = GetFinishedBuilder(rt, finished, lock);
         if (!builder)
             break;
 
         JSScript* script = builder->script();
         MOZ_ASSERT(script->hasBaselineScript());
-        script->baselineScript()->setPendingIonBuilder(group->runtime, script, builder);
-        group->ionLazyLinkListAdd(builder);
+        script->baselineScript()->setPendingIonBuilder(rt, script, builder);
+        rt->jitRuntime()->ionLazyLinkListAdd(rt, builder);
 
         // Don't keep more than 100 lazy link builders in a zone group.
-        // Link the oldest ones immediately. Only do this if we have a valid
-        // context to use (otherwise this method might have been called in the
-        // middle of a compartment change on the current thread's context).
-        if (maybecx) {
-            while (group->ionLazyLinkListSize() > 100) {
-                jit::IonBuilder* builder = group->ionLazyLinkList().getLast();
-                RootedScript script(maybecx, builder->script());
-
-                AutoUnlockHelperThreadState unlock(lock);
-                AutoCompartment ac(maybecx, script);
-                jit::LinkIonScript(maybecx, script);
-            }
+        // Link the oldest ones immediately.
+        while (rt->jitRuntime()->ionLazyLinkListSize() > 100) {
+            jit::IonBuilder* builder = rt->jitRuntime()->ionLazyLinkList(rt).getLast();
+            RootedScript script(cx, builder->script());
+
+            AutoUnlockHelperThreadState unlock(lock);
+            AutoCompartment ac(cx, script);
+            jit::LinkIonScript(cx, script);
         }
     }
 
-    MOZ_ASSERT(!group->numFinishedBuilders);
+    MOZ_ASSERT(!rt->jitRuntime()->numFinishedBuilders());
 }
 
 static void
 TrackAllProperties(JSContext* cx, JSObject* obj)
 {
     MOZ_ASSERT(obj->isSingleton());
 
     for (Shape::Range<NoGC> range(obj->as<NativeObject>().lastProperty()); !range.empty(); range.popFront())
@@ -1985,17 +2023,17 @@ TrackPropertiesForSingletonScopes(JSCont
         if (scope->is<CallObject>() && scope->isSingleton())
             TrackAllProperties(cx, scope);
     }
 }
 
 static void
 TrackIonAbort(JSContext* cx, JSScript* script, jsbytecode* pc, const char* message)
 {
-    if (!cx->runtime()->jitRuntime()->isOptimizationTrackingEnabled(cx->zone()->group()))
+    if (!cx->runtime()->jitRuntime()->isOptimizationTrackingEnabled(cx->runtime()))
         return;
 
     // Only bother tracking aborts of functions we're attempting to
     // Ion-compile after successfully running in Baseline.
     if (!script->hasBaselineScript())
         return;
 
     JitcodeGlobalTable* table = cx->runtime()->jitRuntime()->getJitcodeGlobalTable();
@@ -2081,17 +2119,17 @@ IonCompile(JSContext* cx, JSScript* scri
     IonBuilder* builder = alloc->new_<IonBuilder>((JSContext*) nullptr,
                                                   CompileCompartment::get(cx->compartment()),
                                                   options, temp, graph, constraints,
                                                   inspector, info, optimizationInfo,
                                                   baselineFrameInspector);
     if (!builder)
         return AbortReason::Alloc;
 
-    if (cx->zone()->group()->storeBuffer().cancelIonCompilations())
+    if (cx->runtime()->gc.storeBuffer().cancelIonCompilations())
         builder->setNotSafeForMinorGC();
 
     MOZ_ASSERT(recompile == builder->script()->hasIonScript());
     MOZ_ASSERT(builder->script()->canIonCompile());
 
     RootedScript builderScript(cx, builder->script());
 
     if (recompile)
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -140,17 +140,17 @@ class LIRGraph;
 class CodeGenerator;
 class LazyLinkExitFrameLayout;
 
 MOZ_MUST_USE bool OptimizeMIR(MIRGenerator* mir);
 LIRGraph* GenerateLIR(MIRGenerator* mir);
 CodeGenerator* GenerateCode(MIRGenerator* mir, LIRGraph* lir);
 CodeGenerator* CompileBackEnd(MIRGenerator* mir);
 
-void AttachFinishedCompilations(ZoneGroup* group, JSContext* maybecx);
+void AttachFinishedCompilations(JSContext* cx);
 void FinishOffThreadBuilder(JSRuntime* runtime, IonBuilder* builder,
                             const AutoLockHelperThreadState& lock);
 void FreeIonBuilder(IonBuilder* builder);
 
 void LinkIonScript(JSContext* cx, HandleScript calleescript);
 uint8_t* LazyLinkTopActivation(JSContext* cx, LazyLinkExitFrameLayout* frame);
 
 static inline bool
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -8642,17 +8642,17 @@ IonBuilder::addTypedArrayLengthAndData(M
     } else if (TemporaryTypeSet* types = obj->resultTypeSet()) {
         tarr = types->maybeSingleton();
     }
 
     if (tarr) {
         SharedMem<void*> data = tarr->as<TypedArrayObject>().viewDataEither();
         // Bug 979449 - Optimistically embed the elements and use TI to
         //              invalidate if we move them.
-        bool isTenured = !tarr->zone()->group()->nursery().isInside(data);
+        bool isTenured = !tarr->runtimeFromActiveCooperatingThread()->gc.nursery().isInside(data);
         if (isTenured && tarr->isSingleton()) {
             // The 'data' pointer of TypedArrayObject can change in rare circumstances
             // (ArrayBufferObject::changeContents).
             TypeSet::ObjectKey* tarrKey = TypeSet::ObjectKey::get(tarr);
             if (!tarrKey->unknownProperties()) {
                 if (tarr->is<TypedArrayObject>())
                     tarrKey->watchStateChangeForTypedArrayData(constraints());
 
--- a/js/src/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -130,16 +130,32 @@ class JitRuntime
     // Map VMFunction addresses to the offset of the wrapper in
     // trampolineCode_.
     using VMWrapperMap = HashMap<const VMFunction*, uint32_t, VMFunction>;
     ExclusiveAccessLockWriteOnceData<VMWrapperMap*> functionWrappers_;
 
     // Global table of jitcode native address => bytecode address mappings.
     UnprotectedData<JitcodeGlobalTable*> jitcodeGlobalTable_;
 
+#ifdef DEBUG
+    // The number of possible bailing places encounters before forcefully bailing
+    // in that place. Zero means inactive.
+    ActiveThreadData<uint32_t> ionBailAfter_;
+#endif
+
+    // Number of Ion compilations which were finished off thread and are
+    // waiting to be lazily linked. This is only set while holding the helper
+    // thread state lock, but may be read from at other times.
+    mozilla::Atomic<size_t> numFinishedBuilders_;
+
+    // List of Ion compilation waiting to get linked.
+    using IonBuilderList = mozilla::LinkedList<js::jit::IonBuilder>;
+    ActiveThreadData<IonBuilderList> ionLazyLinkList_;
+    ActiveThreadData<size_t> ionLazyLinkListSize_;
+
   private:
     void generateLazyLinkStub(MacroAssembler& masm);
     void generateInterpreterStub(MacroAssembler& masm);
     void generateProfilerExitFrameTailStub(MacroAssembler& masm, Label* profilerExitTail);
     void generateExceptionTailStub(MacroAssembler& masm, void* handler, Label* profilerExitTail);
     void generateBailoutTailStub(MacroAssembler& masm, Label* bailoutTail);
     void generateEnterJIT(JSContext* cx, MacroAssembler& masm);
     void generateArgumentsRectifier(MacroAssembler& masm);
@@ -267,19 +283,45 @@ class JitRuntime
         MOZ_ASSERT(hasJitcodeGlobalTable());
         return jitcodeGlobalTable_;
     }
 
     bool isProfilerInstrumentationEnabled(JSRuntime* rt) {
         return rt->geckoProfiler().enabled();
     }
 
-    bool isOptimizationTrackingEnabled(ZoneGroup* group) {
-        return isProfilerInstrumentationEnabled(group->runtime);
+    bool isOptimizationTrackingEnabled(JSRuntime* rt) {
+        return isProfilerInstrumentationEnabled(rt);
+    }
+
+#ifdef DEBUG
+    void* addressOfIonBailAfter() { return &ionBailAfter_; }
+
+    // Set after how many bailing places we should forcefully bail.
+    // Zero disables this feature.
+    void setIonBailAfter(uint32_t after) {
+        ionBailAfter_ = after;
     }
+#endif
+
+    size_t numFinishedBuilders() const {
+        return numFinishedBuilders_;
+    }
+    mozilla::Atomic<size_t>& numFinishedBuildersRef(const AutoLockHelperThreadState& locked) {
+        return numFinishedBuilders_;
+    }
+
+    IonBuilderList& ionLazyLinkList(JSRuntime* rt);
+
+    size_t ionLazyLinkListSize() const {
+        return ionLazyLinkListSize_;
+    }
+
+    void ionLazyLinkListRemove(JSRuntime* rt, js::jit::IonBuilder* builder);
+    void ionLazyLinkListAdd(JSRuntime* rt, js::jit::IonBuilder* builder);
 };
 
 enum class CacheKind : uint8_t;
 class CacheIRStubInfo;
 
 enum class ICStubEngine : uint8_t {
     // Baseline IC, see SharedIC.h and BaselineIC.h.
     Baseline = 0,
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -970,33 +970,33 @@ TraceBailoutFrame(JSTracer* trc, const J
         if (!snapIter.moreInstructions())
             break;
         snapIter.nextInstruction();
     }
 
 }
 
 static void
-UpdateIonJSFrameForMinorGC(const JSJitFrameIter& frame)
+UpdateIonJSFrameForMinorGC(JSRuntime* rt, const JSJitFrameIter& frame)
 {
     // Minor GCs may move slots/elements allocated in the nursery. Update
     // any slots/elements pointers stored in this frame.
 
     JitFrameLayout* layout = (JitFrameLayout*)frame.fp();
 
     IonScript* ionScript = nullptr;
     if (frame.checkInvalidation(&ionScript)) {
         // This frame has been invalidated, meaning that its IonScript is no
         // longer reachable through the callee token (JSFunction/JSScript->ion
         // is now nullptr or recompiled).
     } else {
         ionScript = frame.ionScriptFromCalleeToken();
     }
 
-    Nursery& nursery = ionScript->method()->zone()->group()->nursery();
+    Nursery& nursery = rt->gc.nursery();
 
     const SafepointIndex* si = ionScript->getSafepointIndex(frame.returnAddressToFp());
     SafepointReader safepoint(ionScript, si);
 
     LiveGeneralRegisterSet slotsRegs = safepoint.slotsOrElementsSpills();
     uintptr_t* spill = frame.spillBase();
     for (GeneralRegisterBackwardIterator iter(safepoint.allGprSpills()); iter.more(); ++iter) {
         --spill;
@@ -1310,17 +1310,17 @@ TraceJitActivations(JSContext* cx, JSTra
 void
 UpdateJitActivationsForMinorGC(JSRuntime* rt)
 {
     MOZ_ASSERT(JS::CurrentThreadIsHeapMinorCollecting());
     JSContext* cx = rt->mainContextFromOwnThread();
     for (JitActivationIterator activations(cx); !activations.done(); ++activations) {
         for (OnlyJSJitFrameIter iter(activations); !iter.done(); ++iter) {
             if (iter.frame().type() == JitFrame_IonJS)
-                UpdateIonJSFrameForMinorGC(iter.frame());
+                UpdateIonJSFrameForMinorGC(rt, iter.frame());
         }
     }
 }
 
 void
 GetPcScript(JSContext* cx, JSScript** scriptRes, jsbytecode** pcRes)
 {
     JitSpew(JitSpew_IonSnapshots, "Recover PC & Script from the last frame.");
--- a/js/src/jit/Linker.cpp
+++ b/js/src/jit/Linker.cpp
@@ -54,14 +54,14 @@ Linker::newCode(JSContext* cx, CodeKind 
     if (!code)
         return fail(cx);
     if (masm.oom())
         return fail(cx);
     awjc.emplace(result, bytesNeeded);
     code->copyFrom(masm);
     masm.link(code);
     if (masm.embedsNurseryPointers())
-        cx->zone()->group()->storeBuffer().putWholeCell(code);
+        cx->runtime()->gc.storeBuffer().putWholeCell(code);
     return code;
 }
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -957,18 +957,18 @@ jit::IonCompilationCanUseNurseryPointers
     // thread but might actually be on the active thread, check the flag set on
     // the JSContext by AutoEnterIonCompilation.
     if (CurrentThreadIsIonCompiling())
         return !CurrentThreadIsIonCompilingSafeForMinorGC();
 
     // Otherwise, we must be on the active thread during MIR construction. The
     // store buffer must have been notified that minor GCs must cancel pending
     // or in progress Ion compilations.
-    JSContext* cx = TlsContext.get();
-    return cx->zone()->group()->storeBuffer().cancelIonCompilations();
+    JSRuntime* rt = TlsContext.get()->zone()->runtimeFromActiveCooperatingThread();
+    return rt->gc.storeBuffer().cancelIonCompilations();
 }
 
 #endif // DEBUG
 
 MConstant::MConstant(TempAllocator& alloc, const js::Value& vp, CompilerConstraintList* constraints)
   : MNullaryInstruction(classOpcode)
 {
     setResultType(MIRTypeFromValue(vp));
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -574,34 +574,34 @@ NewCallObject(JSContext* cx, HandleShape
     JSObject* obj = CallObject::create(cx, shape, group);
     if (!obj)
         return nullptr;
 
     // The JIT creates call objects in the nursery, so elides barriers for
     // the initializing writes. The interpreter, however, may have allocated
     // the call object tenured, so barrier as needed before re-entering.
     if (!IsInsideNursery(obj))
-        cx->zone()->group()->storeBuffer().putWholeCell(obj);
+        cx->runtime()->gc.storeBuffer().putWholeCell(obj);
 
     return obj;
 }
 
 JSObject*
 NewSingletonCallObject(JSContext* cx, HandleShape shape)
 {
     JSObject* obj = CallObject::createSingleton(cx, shape);
     if (!obj)
         return nullptr;
 
     // The JIT creates call objects in the nursery, so elides barriers for
     // the initializing writes. The interpreter, however, may have allocated
     // the call object tenured, so barrier as needed before re-entering.
     MOZ_ASSERT(!IsInsideNursery(obj),
                "singletons are created in the tenured heap");
-    cx->zone()->group()->storeBuffer().putWholeCell(obj);
+    cx->runtime()->gc.storeBuffer().putWholeCell(obj);
 
     return obj;
 }
 
 JSObject*
 NewStringObject(JSContext* cx, HandleString str)
 {
     return StringObject::create(cx, str);
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -1057,17 +1057,18 @@ CodeGeneratorShared::verifyCompactTracke
             MOZ_ASSERT(startOffset == entry.startOffset.offset());
             MOZ_ASSERT(endOffset == entry.endOffset.offset());
             MOZ_ASSERT(index == unique.indexOf(entry.optimizations));
 
             // Assert that the type info and attempts vectors are correctly
             // decoded. This is disabled for now if the types table might
             // contain nursery pointers, in which case the types might not
             // match, see bug 1175761.
-            if (!code->zone()->group()->storeBuffer().cancelIonCompilations()) {
+            JSRuntime* rt = code->runtimeFromActiveCooperatingThread();
+            if (!rt->gc.storeBuffer().cancelIonCompilations()) {
                 IonTrackedOptimizationsTypeInfo typeInfo = typesTable->entry(index);
                 TempOptimizationTypeInfoVector tvec(alloc());
                 ReadTempTypeInfoVectorOp top(alloc(), &tvec);
                 typeInfo.forEach(top, allTypes);
                 MOZ_ASSERT_IF(!top.oom(), entry.optimizations->matchTypes(tvec));
             }
 
             IonTrackedOptimizationsAttempts attempts = attemptsTable->entry(index);
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1214,17 +1214,17 @@ DumpHeapTracer::onChild(const JS::GCCell
     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)
-        EvictAllNurseries(cx->runtime(), JS::gcreason::API);
+        cx->runtime()->gc.evictNursery(JS::gcreason::API);
 
     DumpHeapTracer dtrc(fp, cx);
 
     fprintf(dtrc.output, "# Roots.\n");
     {
         JSRuntime* rt = cx->runtime();
         js::gc::AutoPrepareForTracing prep(cx);
         gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -701,17 +701,17 @@ js::RecomputeWrappers(JSContext* cx, con
 
     AutoWrapperVector toRecompute(cx);
     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
         // Filter by source compartment.
         if (!sourceFilter.match(c))
             continue;
 
         if (!evictedNursery && c->hasNurseryAllocatedWrapperEntries(targetFilter)) {
-            EvictAllNurseries(cx->runtime());
+            cx->runtime()->gc.evictNursery();
             evictedNursery = true;
         }
 
         // Iterate over the wrappers, filtering appropriately.
         for (JSCompartment::NonStringWrapperEnum e(c, targetFilter); !e.empty(); e.popFront()) {
             // Filter out non-objects.
             CrossCompartmentKey& k = e.front().mutableKey();
             if (!k.is<JSObject*>())
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5929,17 +5929,17 @@ class SprintOptimizationAttemptsOp : pub
 
 static bool
 ReflectTrackedOptimizations(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject callee(cx, &args.callee());
     JSRuntime* rt = cx->runtime();
 
-    if (!rt->hasJitRuntime() || !rt->jitRuntime()->isOptimizationTrackingEnabled(cx->zone()->group())) {
+    if (!rt->hasJitRuntime() || !rt->jitRuntime()->isOptimizationTrackingEnabled(cx->runtime())) {
         JS_ReportErrorASCII(cx, "Optimization tracking is off.");
         return false;
     }
 
     if (args.length() != 1) {
         ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
         return false;
     }
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -863,17 +863,17 @@ ArgumentsObject::objectMoved(JSObject* d
 {
     ArgumentsObject* ndst = &dst->as<ArgumentsObject>();
     const ArgumentsObject* nsrc = &src->as<ArgumentsObject>();
     MOZ_ASSERT(ndst->data() == nsrc->data());
 
     if (!IsInsideNursery(src))
         return 0;
 
-    Nursery& nursery = dst->zone()->group()->nursery();
+    Nursery& nursery = dst->runtimeFromActiveCooperatingThread()->gc.nursery();
 
     size_t nbytesTotal = 0;
     if (!nursery.isInside(nsrc->data())) {
         nursery.removeMallocedBuffer(nsrc->data());
     } else {
         AutoEnterOOMUnsafeRegion oomUnsafe;
         uint32_t nbytes = ArgumentsData::bytesRequired(nsrc->data()->numArgs);
         uint8_t* data = nsrc->zone()->pod_malloc<uint8_t>(nbytes);
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -1648,17 +1648,17 @@ ArrayBufferViewObject::trace(JSTracer* t
                 void* srcData = obj->getPrivate();
                 void* dstData = view->as<InlineTypedObject>().inlineTypedMemForGC() + offset;
                 obj->setPrivateUnbarriered(dstData);
 
                 // We can't use a direct forwarding pointer here, as there might
                 // not be enough bytes available, and other views might have data
                 // pointers whose forwarding pointers would overlap this one.
                 if (trc->isTenuringTracer()) {
-                    Nursery& nursery = obj->zoneFromAnyThread()->group()->nursery();
+                    Nursery& nursery = trc->runtime()->gc.nursery();
                     nursery.maybeSetForwardingPointer(trc, srcData, dstData, /* direct = */ false);
                 }
             } else {
                 MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset == 0);
 
                 // The data may or may not be inline with the buffer. The buffer
                 // can only move during a compacting GC, in which case its
                 // objectMoved hook has already updated the buffer's data pointer.
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -204,17 +204,17 @@ js::StartOffThreadIonFree(jit::IonBuilde
  * compilations which are started must eventually be finished.
  */
 static void
 FinishOffThreadIonCompile(jit::IonBuilder* builder, const AutoLockHelperThreadState& lock)
 {
     AutoEnterOOMUnsafeRegion oomUnsafe;
     if (!HelperThreadState().ionFinishedList(lock).append(builder))
         oomUnsafe.crash("FinishOffThreadIonCompile");
-    builder->script()->zoneFromAnyThread()->group()->numFinishedBuilders++;
+    builder->script()->runtimeFromAnyThread()->jitRuntime()->numFinishedBuildersRef(lock)++;
 }
 
 static JSRuntime*
 GetSelectorRuntime(const CompilationSelector& selector)
 {
     struct Matcher
     {
         JSRuntime* match(JSScript* script)    { return script->runtimeFromActiveCooperatingThread(); }
@@ -304,34 +304,33 @@ CancelOffThreadIonCompileLocked(const Co
             HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
     } while (cancelled);
 
     /* Cancel code generation for any completed entries. */
     GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
     for (size_t i = 0; i < finished.length(); i++) {
         jit::IonBuilder* builder = finished[i];
         if (IonBuilderMatches(selector, builder)) {
-            builder->script()->zoneFromAnyThread()->group()->numFinishedBuilders--;
-            jit::FinishOffThreadBuilder(builder->script()->runtimeFromAnyThread(), builder, lock);
+            JSRuntime* rt = builder->script()->runtimeFromAnyThread();
+            rt->jitRuntime()->numFinishedBuildersRef(lock)--;
+            jit::FinishOffThreadBuilder(rt, builder, lock);
             HelperThreadState().remove(finished, &i);
         }
     }
 
     /* Cancel lazy linking for pending builders (attached to the ionScript). */
     if (discardLazyLinkList) {
         MOZ_ASSERT(!selector.is<AllCompilations>());
         JSRuntime* runtime = GetSelectorRuntime(selector);
-        for (ZoneGroupsIter group(runtime); !group.done(); group.next()) {
-            jit::IonBuilder* builder = group->ionLazyLinkList().getFirst();
-            while (builder) {
-                jit::IonBuilder* next = builder->getNext();
-                if (IonBuilderMatches(selector, builder))
-                    jit::FinishOffThreadBuilder(runtime, builder, lock);
-                builder = next;
-            }
+        jit::IonBuilder* builder = runtime->jitRuntime()->ionLazyLinkList(runtime).getFirst();
+        while (builder) {
+            jit::IonBuilder* next = builder->getNext();
+            if (IonBuilderMatches(selector, builder))
+                jit::FinishOffThreadBuilder(runtime, builder, lock);
+            builder = next;
         }
     }
 }
 
 void
 js::CancelOffThreadIonCompile(const CompilationSelector& selector, bool discardLazyLinkList)
 {
     if (!JitDataStructuresExist(selector))
@@ -364,17 +363,18 @@ js::HasOffThreadIonCompile(JSCompartment
 
     GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
     for (size_t i = 0; i < finished.length(); i++) {
         jit::IonBuilder* builder = finished[i];
         if (builder->script()->compartment() == comp)
             return true;
     }
 
-    jit::IonBuilder* builder = comp->zone()->group()->ionLazyLinkList().getFirst();
+    JSRuntime* rt = comp->runtimeFromActiveCooperatingThread();
+    jit::IonBuilder* builder = rt->jitRuntime()->ionLazyLinkList(rt).getFirst();
     while (builder) {
         if (builder->script()->compartment() == comp)
             return true;
         builder = builder->getNext();
     }
 
     return false;
 }
@@ -2103,18 +2103,19 @@ GlobalHelperThreadState::trace(JSTracer*
 
     if (HelperThreadState().threads) {
         for (auto& helper : *HelperThreadState().threads) {
             if (auto builder = helper.ionBuilder())
                 builder->trace(trc);
         }
     }
 
-    for (ZoneGroupsIter group(trc->runtime()); !group.done(); group.next()) {
-        jit::IonBuilder* builder = group->ionLazyLinkList().getFirst();
+    JSRuntime* rt = trc->runtime();
+    if (auto* jitRuntime = rt->jitRuntime()) {
+        jit::IonBuilder* builder = jitRuntime->ionLazyLinkList(rt).getFirst();
         while (builder) {
             builder->trace(trc);
             builder = builder->getNext();
         }
     }
 
     for (auto parseTask : parseWorklist_)
         parseTask->trace(trc);
--- a/js/src/vm/JSObject.cpp
+++ b/js/src/vm/JSObject.cpp
@@ -1553,23 +1553,23 @@ JSObject::fixDictionaryShapeAfterSwap()
 {
     // Dictionary shapes can point back to their containing objects, so after
     // swapping the guts of those objects fix the pointers up.
     if (isNative() && as<NativeObject>().inDictionaryMode())
         as<NativeObject>().shape()->listp = as<NativeObject>().shapePtr();
 }
 
 static MOZ_MUST_USE bool
-CopyProxyValuesBeforeSwap(ProxyObject* proxy, Vector<Value>& values)
+CopyProxyValuesBeforeSwap(JSContext* cx, ProxyObject* proxy, Vector<Value>& values)
 {
     MOZ_ASSERT(values.empty());
 
     // Remove the GCPtrValues we're about to swap from the store buffer, to
     // ensure we don't trace bogus values.
-    StoreBuffer& sb = proxy->zone()->group()->storeBuffer();
+    StoreBuffer& sb = cx->runtime()->gc.storeBuffer();
 
     // Reserve space for the private slot and the reserved slots.
     if (!values.reserve(1 + proxy->numReservedSlots()))
         return false;
 
     js::detail::ProxyValueArray* valArray = js::detail::GetProxyDataLayout(proxy)->values();
     sb.unputValue(&valArray->privateSlot);
     values.infallibleAppend(valArray->privateSlot);
@@ -1630,18 +1630,18 @@ JSObject::swap(JSContext* cx, HandleObje
     if (!JSObject::getGroup(cx, b))
         oomUnsafe.crash("JSObject::swap");
 
     /*
      * Neither object may be in the nursery, but ensure we update any embedded
      * nursery pointers in either object.
      */
     MOZ_ASSERT(!IsInsideNursery(a) && !IsInsideNursery(b));
-    cx->zone()->group()->storeBuffer().putWholeCell(a);
-    cx->zone()->group()->storeBuffer().putWholeCell(b);
+    cx->runtime()->gc.storeBuffer().putWholeCell(a);
+    cx->runtime()->gc.storeBuffer().putWholeCell(b);
 
     unsigned r = NotifyGCPreSwap(a, b);
 
     // Do the fundamental swapping of the contents of two objects.
     MOZ_ASSERT(a->compartment() == b->compartment());
     MOZ_ASSERT(a->is<JSFunction>() == b->is<JSFunction>());
 
     // Don't try to swap functions with different sizes.
@@ -1717,21 +1717,21 @@ JSObject::swap(JSContext* cx, HandleObje
             }
         }
 
         // Do the same for proxies storing ProxyValueArray inline.
         ProxyObject* proxyA = a->is<ProxyObject>() ? &a->as<ProxyObject>() : nullptr;
         ProxyObject* proxyB = b->is<ProxyObject>() ? &b->as<ProxyObject>() : nullptr;
 
         if (aIsProxyWithInlineValues) {
-            if (!CopyProxyValuesBeforeSwap(proxyA, avals))
+            if (!CopyProxyValuesBeforeSwap(cx, proxyA, avals))
                 oomUnsafe.crash("CopyProxyValuesBeforeSwap");
         }
         if (bIsProxyWithInlineValues) {
-            if (!CopyProxyValuesBeforeSwap(proxyB, bvals))
+            if (!CopyProxyValuesBeforeSwap(cx, proxyB, bvals))
                 oomUnsafe.crash("CopyProxyValuesBeforeSwap");
         }
 
         // Swap the main fields of the objects, whether they are native objects or proxies.
         char tmp[sizeof(JSObject_Slots0)];
         js_memcpy(&tmp, a, sizeof tmp);
         js_memcpy(a, b, sizeof tmp);
         js_memcpy(b, &tmp, sizeof tmp);
@@ -3880,17 +3880,17 @@ JSObject::addSizeOfExcludingThis(mozilla
 size_t
 JSObject::sizeOfIncludingThisInNursery() const
 {
     // This function doesn't concern itself yet with typed objects (bug 1133593)
     // nor unboxed objects (bug 1133592).
 
     MOZ_ASSERT(!isTenured());
 
-    const Nursery& nursery = zone()->group()->nursery();
+    const Nursery& nursery = runtimeFromActiveCooperatingThread()->gc.nursery();
     size_t size = Arena::thingSize(allocKindForTenure(nursery));
 
     if (is<NativeObject>()) {
         const NativeObject& native = as<NativeObject>();
 
         size += native.numFixedSlots() * sizeof(Value);
         size += native.numDynamicSlots() * sizeof(Value);
 
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -119,19 +119,20 @@ NativeObject::markDenseElementsNotPacked
 }
 
 inline void
 NativeObject::elementsRangeWriteBarrierPost(uint32_t start, uint32_t count)
 {
     for (size_t i = 0; i < count; i++) {
         const Value& v = elements_[start + i];
         if ((v.isObject() || v.isString()) && IsInsideNursery(v.toGCThing())) {
-            zone()->group()->storeBuffer().putSlot(this, HeapSlot::Element,
-                                                   unshiftedIndex(start + i),
-                                                   count - i);
+            JSRuntime* rt = runtimeFromActiveCooperatingThread();
+            rt->gc.storeBuffer().putSlot(this, HeapSlot::Element,
+                                         unshiftedIndex(start + i),
+                                         count - i);
             return;
         }
     }
 }
 
 inline void
 NativeObject::copyDenseElements(uint32_t dstStart, const Value* src, uint32_t count)
 {
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -429,17 +429,17 @@ InvokeInterruptCallback(JSContext* cx)
 {
     MOZ_ASSERT(cx->requestDepth >= 1);
     MOZ_ASSERT(!cx->compartment()->isAtomsCompartment());
 
     cx->runtime()->gc.gcIfRequested();
 
     // A worker thread may have requested an interrupt after finishing an Ion
     // compilation.
-    jit::AttachFinishedCompilations(cx->zone()->group(), cx);
+    jit::AttachFinishedCompilations(cx);
 
     // Important: Additional callbacks can occur inside the callback handler
     // if it re-enters the JS engine. The embedding must ensure that the
     // callback is disconnected before attempting such re-entry.
     if (cx->interruptCallbackDisabled)
         return true;
 
     bool stop = false;
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1141,27 +1141,15 @@ SetValueRangeToNull(Value* beg, Value* e
 static MOZ_ALWAYS_INLINE void
 SetValueRangeToNull(Value* vec, size_t len)
 {
     SetValueRangeToNull(vec, vec + len);
 }
 
 extern const JSSecurityCallbacks NullSecurityCallbacks;
 
-inline Nursery&
-ZoneGroup::nursery()
-{
-    return runtime->gc.nursery();
-}
-
-inline gc::StoreBuffer&
-ZoneGroup::storeBuffer()
-{
-    return runtime->gc.storeBuffer();
-}
-
 // This callback is set by JS::SetProcessLargeAllocationFailureCallback
 // and may be null. See comment in jsapi.h.
 extern mozilla::Atomic<JS::LargeAllocationFailureCallback> OnLargeAllocationFailure;
 
 } /* namespace js */
 
 #endif /* vm_Runtime_h */
--- a/js/src/vm/StringType.cpp
+++ b/js/src/vm/StringType.cpp
@@ -505,33 +505,33 @@ JSRope::flattenInternal(JSContext* maybe
             str->setNonInlineChars(wholeChars);
             pos = wholeChars + left.d.u1.length;
             if (IsSame<CharT, char16_t>::value)
                 left.d.u1.flags = DEPENDENT_FLAGS;
             else
                 left.d.u1.flags = DEPENDENT_FLAGS | LATIN1_CHARS_BIT;
             left.d.s.u3.base = (JSLinearString*)this;  /* will be true on exit */
             BarrierMethods<JSString*>::postBarrier((JSString**)&left.d.s.u3.base, nullptr, this);
-            Nursery& nursery = zone()->group()->nursery();
+            Nursery& nursery = runtimeFromActiveCooperatingThread()->gc.nursery();
             if (isTenured() && !left.isTenured())
                 nursery.removeMallocedBuffer(wholeChars);
             else if (!isTenured() && left.isTenured())
                 nursery.registerMallocedBuffer(wholeChars);
             goto visit_right_child;
         }
     }
 
     if (!AllocChars(this, wholeLength, &wholeChars, &wholeCapacity)) {
         if (maybecx)
             ReportOutOfMemory(maybecx);
         return nullptr;
     }
 
     if (!isTenured()) {
-        Nursery& nursery = zone()->group()->nursery();
+        Nursery& nursery = runtimeFromActiveCooperatingThread()->gc.nursery();
         if (!nursery.registerMallocedBuffer(wholeChars)) {
             js_free(wholeChars);
             if (maybecx)
                 ReportOutOfMemory(maybecx);
             return nullptr;
         }
     }
 
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -680,18 +680,18 @@ class TypeSetRef : public BufferableRef
         types->trace(zone, trc);
     }
 };
 
 void
 ConstraintTypeSet::postWriteBarrier(JSContext* cx, Type type)
 {
     if (type.isSingletonUnchecked() && IsInsideNursery(type.singletonNoBarrier())) {
-        cx->zone()->group()->storeBuffer().putGeneric(TypeSetRef(cx->zone(), this));
-        cx->zone()->group()->storeBuffer().setShouldCancelIonCompilations();
+        cx->runtime()->gc.storeBuffer().putGeneric(TypeSetRef(cx->zone(), this));
+        cx->runtime()->gc.storeBuffer().setShouldCancelIonCompilations();
     }
 }
 
 void
 ConstraintTypeSet::addType(JSContext* cx, Type type)
 {
     checkMagic();
 
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -191,17 +191,17 @@ TypedArrayObject::objectMoved(JSObject* 
     if (!IsInsideNursery(old)) {
         // Update the data slot pointer if it points to the old JSObject.
         if (oldObj->hasInlineElements())
             newObj->setInlineElements();
 
         return 0;
     }
 
-    Nursery& nursery = obj->zone()->group()->nursery();
+    Nursery& nursery = obj->runtimeFromActiveCooperatingThread()->gc.nursery();
     void* buf = oldObj->elements();
 
     if (!nursery.isInside(buf)) {
         nursery.removeMallocedBuffer(buf);
         return 0;
     }
 
     // Determine if we can use inline data for the target array. If this is
@@ -489,17 +489,17 @@ class TypedArrayObjectTemplate : public 
                 // need to fail here if isSharedMemory.  However, mmap() can
                 // place a SharedArrayRawBuffer up against the bottom end of a
                 // nursery chunk, and a zero-length buffer will erroneously be
                 // perceived as being inside the nursery; sidestep that.
                 if (isSharedMemory) {
                     MOZ_ASSERT(buffer->byteLength() == 0 &&
                                (uintptr_t(ptr.unwrapValue()) & gc::ChunkMask) == 0);
                 } else {
-                    cx->zone()->group()->storeBuffer().putWholeCell(obj);
+                    cx->runtime()->gc.storeBuffer().putWholeCell(obj);
                 }
             }
         } else {
             void* data = obj->fixedData(FIXED_DATA_START);
             obj->initPrivate(data);
             memset(data, 0, len * sizeof(NativeType));
 #ifdef DEBUG
             if (len == 0) {
--- a/js/src/vm/UnboxedObject-inl.h
+++ b/js/src/vm/UnboxedObject-inl.h
@@ -42,64 +42,16 @@ GetUnboxedValue(uint8_t* p, JSValueType 
       case JSVAL_TYPE_OBJECT:
         return ObjectOrNullValue(*reinterpret_cast<JSObject**>(p));
 
       default:
         MOZ_CRASH("Invalid type for unboxed value");
     }
 }
 
-static inline void
-SetUnboxedValueNoTypeChange(JSObject* unboxedObject,
-                            uint8_t* p, JSValueType type, const Value& v,
-                            bool preBarrier)
-{
-    switch (type) {
-      case JSVAL_TYPE_BOOLEAN:
-        *p = v.toBoolean();
-        return;
-
-      case JSVAL_TYPE_INT32:
-        *reinterpret_cast<int32_t*>(p) = v.toInt32();
-        return;
-
-      case JSVAL_TYPE_DOUBLE:
-        *reinterpret_cast<double*>(p) = v.toNumber();
-        return;
-
-      case JSVAL_TYPE_STRING: {
-        MOZ_ASSERT(!IsInsideNursery(v.toString()));
-        JSString** np = reinterpret_cast<JSString**>(p);
-        if (preBarrier)
-            JSString::writeBarrierPre(*np);
-        *np = v.toString();
-        return;
-      }
-
-      case JSVAL_TYPE_OBJECT: {
-        JSObject** np = reinterpret_cast<JSObject**>(p);
-
-        // Manually trigger post barriers on the whole object. If we treat
-        // the pointer as a HeapPtrObject we will get confused later if the
-        // object is converted to its native representation.
-        JSObject* obj = v.toObjectOrNull();
-        if (IsInsideNursery(obj) && !IsInsideNursery(unboxedObject))
-            unboxedObject->zone()->group()->storeBuffer().putWholeCell(unboxedObject);
-
-        if (preBarrier)
-            JSObject::writeBarrierPre(*np);
-        *np = obj;
-        return;
-      }
-
-      default:
-        MOZ_CRASH("Invalid type for unboxed value");
-    }
-}
-
 static inline bool
 SetUnboxedValue(JSContext* cx, JSObject* unboxedObject, jsid id,
                 uint8_t* p, JSValueType type, const Value& v, bool preBarrier)
 {
     switch (type) {
       case JSVAL_TYPE_BOOLEAN:
         if (v.isBoolean()) {
             *p = v.toBoolean();
@@ -120,17 +72,17 @@ SetUnboxedValue(JSContext* cx, JSObject*
             return true;
         }
         return false;
 
       case JSVAL_TYPE_STRING:
         if (v.isString()) {
             JSString** np = reinterpret_cast<JSString**>(p);
             if (IsInsideNursery(v.toString()) && !IsInsideNursery(unboxedObject))
-                unboxedObject->zone()->group()->storeBuffer().putWholeCell(unboxedObject);
+                v.toString()->storeBuffer()->putWholeCell(unboxedObject);
 
             if (preBarrier)
                 JSString::writeBarrierPre(*np);
             *np = v.toString();
             return true;
         }
         return false;
 
@@ -140,18 +92,18 @@ SetUnboxedValue(JSContext* cx, JSObject*
 
             // Update property types when writing object properties. Types for
             // other properties were captured when the unboxed layout was
             // created.
             AddTypePropertyId(cx, unboxedObject, id, v);
 
             // As above, trigger post barriers on the whole object.
             JSObject* obj = v.toObjectOrNull();
-            if (IsInsideNursery(v.toObjectOrNull()) && !IsInsideNursery(unboxedObject))
-                unboxedObject->zone()->group()->storeBuffer().putWholeCell(unboxedObject);
+            if (IsInsideNursery(obj) && !IsInsideNursery(unboxedObject))
+                obj->storeBuffer()->putWholeCell(unboxedObject);
 
             if (preBarrier)
                 JSObject::writeBarrierPre(*np);
             *np = obj;
             return true;
         }
         return false;
 
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -379,17 +379,17 @@ UnboxedPlainObject::ensureExpando(JSCont
     // See WholeCellEdges::mark.
     MOZ_ASSERT_IF(!IsInsideNursery(expando), !IsInsideNursery(obj));
 
     // As with setValue(), we need to manually trigger post barriers on the
     // whole object. If we treat the field as a GCPtrObject and later
     // convert the object to its native representation, we will end up with a
     // corrupted store buffer entry.
     if (IsInsideNursery(expando) && !IsInsideNursery(obj))
-        cx->zone()->group()->storeBuffer().putWholeCell(obj);
+        expando->storeBuffer()->putWholeCell(obj);
 
     obj->setExpandoUnsafe(expando);
     return expando;
 }
 
 bool
 UnboxedPlainObject::containsUnboxedOrExpandoProperty(JSContext* cx, jsid id) const
 {
@@ -594,17 +594,17 @@ UnboxedPlainObject::convertToNative(JSCo
     // pre barrier.
     JSObject::writeBarrierPre(expando);
 
     // Additionally trigger a post barrier on the expando itself. Whole cell
     // store buffer entries can be added on the original unboxed object for
     // writes to the expando (see WholeCellEdges::trace), so after conversion
     // we need to make sure the expando itself will still be traced.
     if (expando && !IsInsideNursery(expando))
-        cx->zone()->group()->storeBuffer().putWholeCell(expando);
+        cx->runtime()->gc.storeBuffer().putWholeCell(expando);
 
     obj->setGroup(layout.nativeGroup());
     obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape());
 
     for (size_t i = 0; i < values.length(); i++)
         obj->as<PlainObject>().initSlotUnchecked(i, values[i]);
 
     if (!expando)
--- a/modules/freetype2/README.moz-patches
+++ b/modules/freetype2/README.moz-patches
@@ -1,11 +1,12 @@
 This directory contains FreeType v2.9 downloaded from
 https://download.savannah.gnu.org/releases/freetype/
 
 The following post-2.9 commits have been cherry-picked from
-the upstream FreeType repository (see bug 1434697, bug 1438522):
+the upstream FreeType repository (see bug 1434697, bug 1438522, bug 1453653):
 
 http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=994eb2b34934bc5face9f83b2d3b12cf7a9262ab
 http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=cc2f3cdecff5a351e7e8961b9f2e389ab740231a
 http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=4a03f17449ae45f0dacf4de4694ccd6e5e1b24d1
 http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=68dddcdcbe18a08d778026efc01b1369e35cbf6a
 http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=29c759284e305ec428703c9a5831d0b1fc3497ef
+http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=bd9400bd464f6cd7c74f52ece1c1065fe2a87aab
--- a/modules/freetype2/src/truetype/ttinterp.c
+++ b/modules/freetype2/src/truetype/ttinterp.c
@@ -2160,26 +2160,26 @@
     FT_F26Dot6  val;
 
 
     if ( distance >= 0 )
     {
       val = ADD_LONG( distance,
                       exc->threshold - exc->phase + compensation ) &
               -exc->period;
-      val += exc->phase;
+      val = ADD_LONG( val, exc->phase );
       if ( val < 0 )
         val = exc->phase;
     }
     else
     {
       val = NEG_LONG( SUB_LONG( exc->threshold - exc->phase + compensation,
                                 distance ) &
                         -exc->period );
-      val -= exc->phase;
+      val = SUB_LONG( val, exc->phase );
       if ( val > 0 )
         val = -exc->phase;
     }
 
     return val;
   }
 
 
@@ -2211,26 +2211,26 @@
     FT_F26Dot6  val;
 
 
     if ( distance >= 0 )
     {
       val = ( ADD_LONG( distance,
                         exc->threshold - exc->phase + compensation ) /
                 exc->period ) * exc->period;
-      val += exc->phase;
+      val = ADD_LONG( val, exc->phase );
       if ( val < 0 )
         val = exc->phase;
     }
     else
     {
       val = NEG_LONG( ( SUB_LONG( exc->threshold - exc->phase + compensation,
                                   distance ) /
                           exc->period ) * exc->period );
-      val -= exc->phase;
+      val = SUB_LONG( val, exc->phase );
       if ( val > 0 )
         val = -exc->phase;
     }
 
     return val;
   }
 
 
--- a/taskcluster/ci/config.yml
+++ b/taskcluster/ci/config.yml
@@ -53,16 +53,17 @@ treeherder:
         'WM64': 'MinGW builds for Windows 64-bits'
         'Searchfox': 'Searchfox builds'
         'SM': 'Spidermonkey builds'
         'pub': 'APK publishing'
         'p': 'Partial generation'
         'ps': 'Partials signing'
         'Rel': 'Release promotion'
         'Snap': 'Snap image generation'
+        'langpack': 'Langpack sigatures and uploads'
 
 index:
     products:
         - 'firefox'
         - 'fennec'
         - 'mobile'
         - 'static-analysis'
         - 'devedition'
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/post-langpack-dummy/kind.yml
@@ -0,0 +1,48 @@
+# 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/.
+
+loader: taskgraph.loader.transform:loader
+
+transforms:
+   - taskgraph.transforms.reverse_chunk_deps:transforms
+   - taskgraph.transforms.release_notifications:transforms
+   - taskgraph.transforms.task:transforms
+
+kind-dependencies:
+   - release-beetmover-signed-langpacks
+
+jobs:
+   firefox-promote:
+      name: post-langpack-dummy
+      description: Dummy task to deal with max_dependencies
+      run-on-projects: []
+      shipping-phase: promote
+      shipping-product: firefox
+      worker-type: aws-provisioner-v1/gecko-{level}-b-linux
+      worker:
+         implementation: docker-worker
+         os: linux
+         docker-image: "ubuntu:16.10"
+         max-run-time: 600
+         command:
+            - /bin/bash
+            - -c
+            - echo "Dummy task"
+
+   devedition-promote:
+      name: post-langpack-dummy
+      description: Dummy task to deal with max_dependencies
+      run-on-projects: []
+      shipping-phase: promote
+      shipping-product: devedition
+      worker-type: aws-provisioner-v1/gecko-{level}-b-linux
+      worker:
+         implementation: docker-worker
+         os: linux
+         docker-image: "ubuntu:16.10"
+         max-run-time: 600
+         command:
+            - /bin/bash
+            - -c
+            - echo "Dummy task"
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/release-beetmover-signed-langpacks/kind.yml
@@ -0,0 +1,28 @@
+# 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/.
+
+loader: taskgraph.loader.single_dep:loader
+
+transforms:
+   - taskgraph.transforms.name_sanity:transforms
+   - taskgraph.transforms.release_beetmover_signed_addons:transforms
+   - taskgraph.transforms.release_notifications:transforms
+   - taskgraph.transforms.task:transforms
+
+kind-dependencies:
+   - release-sign-and-push-langpacks
+
+only-for-attributes:
+   - nightly
+
+job-template:
+   description: Beetmover submission for platform-independent langpacks {locales} in {platform} directory
+   worker-type:
+      by-project:
+         mozilla-beta: scriptworker-prov-v1/beetmoverworker-v1
+         mozilla-release: scriptworker-prov-v1/beetmoverworker-v1
+         default: scriptworker-prov-v1/beetmoverworker-dev
+   run-on-projects: []
+   shipping-phase: promote
+   shipping-product: firefox
--- a/taskcluster/ci/release-generate-checksums/kind.yml
+++ b/taskcluster/ci/release-generate-checksums/kind.yml
@@ -2,16 +2,17 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 kind-dependencies:
    - beetmover-source
    - post-beetmover-checksums-dummy
+   - release-beetmover-signed-langpacks
 
 transforms:
    - taskgraph.transforms.build:transforms
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.release_generate_checksums:transforms
    - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/release-sign-and-push-langpacks/kind.yml
@@ -0,0 +1,48 @@
+# 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/.
+
+loader: taskgraph.loader.single_dep:loader
+
+transforms:
+   - taskgraph.transforms.release_sign_and_push_langpacks:transforms
+   - taskgraph.transforms.release_notifications:transforms
+   - taskgraph.transforms.task:transforms
+
+kind-dependencies:
+   - build
+   - nightly-l10n
+
+
+only-for-build-platforms:
+   - linux64-nightly/opt    # addons.mozilla.org only support 1 platform per locale. That's why we use linux64
+   - macosx64-nightly/opt   # Although, we need the special locale "ja-JP-Mac" from this platform
+   # TODO Activate devedition
+
+
+job-template:
+   description: Signs {locales} XPIs for platform via addons.mozilla.org and pushes them
+   worker-type:
+      by-project:
+         mozilla-beta: scriptworker-prov-v1/addon-v1
+         mozilla-release: scriptworker-prov-v1/addon-v1
+         default: scriptworker-prov-v1/addon-dev
+   worker:
+      implementation: sign-and-push-addons
+      channel:
+         by-project:
+            # Only release langpacks are listed publicly
+            mozilla-release: listed
+            default: unlisted
+      upstream-artifacts:   # See transforms
+   run-on-projects: []
+   scopes:
+      by-project:
+         mozilla-beta:
+            - project:releng:addons.mozilla.org:server:production
+         mozilla-release:
+            - project:releng:addons.mozilla.org:server:production
+         default:
+            - project:releng:addons.mozilla.org:server:staging
+   shipping-phase: promote
+   shipping-product: firefox
--- a/taskcluster/ci/release-snap-repackage/kind.yml
+++ b/taskcluster/ci/release-snap-repackage/kind.yml
@@ -7,16 +7,17 @@ loader: taskgraph.loader.transform:loade
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.release_snap_repackage:transforms
    - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - post-beetmover-dummy
+   - post-langpack-dummy
 
 job-defaults:
    description: Generates snap image
    run-on-projects: []  # to make sure this never runs as part of CI
    shipping-phase: promote
    scopes: []
    treeherder:
       platform: linux64/opt
--- a/taskcluster/ci/test/talos.yml
+++ b/taskcluster/ci/test/talos.yml
@@ -49,16 +49,48 @@ talos-chrome-profiling:
     max-run-time: 1200
     mozharness:
         extra-options:
             - --suite=chromez
             - --geckoProfile
             - --add-option
             - --webServer,localhost
 
+talos-damp:
+    description: "Talos devtools (damp)"
+    try-name: damp
+    treeherder-symbol: T(damp)
+    max-run-time:
+        by-test-platform:
+            linux64.*: 2700
+            default: 5400
+    run-on-projects:
+        by-test-platform:
+            .*-qr/.*: ['mozilla-central', 'try']
+            linux64-ccov/.*: ['try']  # Bug 1407593
+            default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
+    mozharness:
+        extra-options:
+            - --suite=damp
+            - --add-option
+            - --webServer,localhost
+
+talos-damp-profiling:
+    description: "Talos profiling devtools"
+    try-name: damp-profiling
+    treeherder-symbol: T-P(damp)
+    run-on-projects: ['mozilla-central', 'try']
+    max-run-time: 1200
+    mozharness:
+        extra-options:
+            - --suite=damp
+            - --geckoProfile
+            - --add-option
+            - --webServer,localhost
+
 talos-dromaeojs:
     description: "Talos dromaeojs"
     try-name: dromaeojs
     treeherder-symbol: T(d)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
@@ -124,51 +156,16 @@ talos-g1-profiling:
             default: 7200
     mozharness:
         extra-options:
             - --suite=g1
             - --geckoProfile
             - --add-option
             - --webServer,localhost
 
-talos-g2:
-    description: "Talos g2"
-    try-name: g2
-    treeherder-symbol: T(g2)
-    max-run-time:
-        by-test-platform:
-            linux64.*: 2700
-            default: 7200
-    run-on-projects:
-        by-test-platform:
-            .*-qr/.*: ['mozilla-central', 'try']
-            linux64-ccov/.*: ['try']  # Bug 1407593
-            default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
-    mozharness:
-        extra-options:
-            - --suite=g2
-            - --add-option
-            - --webServer,localhost
-
-talos-g2-profiling:
-    description: "Talos profiling g2"
-    try-name: g2-profiling
-    treeherder-symbol: T-P(g2)
-    max-run-time:
-        by-test-platform:
-            linux64.*: 2700
-            default: 7200
-    run-on-projects: ['mozilla-central', 'try']
-    mozharness:
-        extra-options:
-            - --suite=g2
-            - --geckoProfile
-            - --add-option
-            - --webServer,localhost
-
 talos-g3:
     description: "Talos g3"
     try-name: g3
     treeherder-symbol: T(g3)
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
@@ -522,16 +519,45 @@ talos-tp6-stylo-threads:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
             macosx.*: ['mozilla-beta', 'autoland', 'try']
             default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
     mozharness:
         extra-options:
             - --suite=tp6-stylo-threads
 
+talos-tps:
+    description: "Talos page scroll (tps)"
+    try-name: tps
+    treeherder-symbol: T(tps)
+    max-run-time: 900
+    run-on-projects:
+        by-test-platform:
+            .*-qr/.*: ['mozilla-central', 'try']
+            linux64-ccov/.*: ['try']  # Bug 1407593
+            default: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
+    mozharness:
+        extra-options:
+            - --suite=tps
+            - --add-option
+            - --webServer,localhost
+
+talos-tps-profiling:
+    description: "Talos page scroll profiling (tps)"
+    try-name: tps-profiling
+    treeherder-symbol: T-P(tps)
+    max-run-time: 900
+    run-on-projects: ['mozilla-central', 'try']
+    mozharness:
+        extra-options:
+            - --suite=tps
+            - --geckoProfile
+            - --add-option
+            - --webServer,localhost
+
 talos-xperf:
     description: "Talos xperf"
     try-name: xperf
     treeherder-symbol: T(x)
     virtualization: virtual
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['mozilla-central', 'try']
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -51,29 +51,30 @@ web-platform-tests-headless:
     - web-platform-tests-reftests-headless
     - web-platform-tests-wdspec-headless
 
 opt-only-tests:
     - mochitest-valgrind
 
 talos:
     - talos-chrome
+    - talos-damp
     - talos-dromaeojs
     - talos-g1
-    - talos-g2
     - talos-g3
     - talos-g4
     - talos-g5
     - talos-other
     - talos-svgr
     - talos-tp5o
     - talos-perf-reftest
     - talos-perf-reftest-singletons
     - talos-tp6
     - talos-tp6-stylo-threads
+    - talos-tps
     - talos-speedometer
     - talos-motionmark
     - talos-h1
     - talos-h2
 
 awsy:
     - awsy
 
@@ -114,30 +115,31 @@ linux-qr-tests:
     - mochitest-webgl
     - reftest
     - test-verify
     - test-verify-wpt
     - xpcshell
 
 linux-talos-profiling:
     - talos-chrome-profiling
+    - talos-damp-profiling
     - talos-dromaeojs-profiling
     - talos-g1-profiling
-    - talos-g2-profiling
     - talos-g3-profiling
     - talos-g4-profiling
     - talos-g5-profiling
     - talos-other-profiling
     - talos-perf-reftest-profiling
     - talos-perf-reftest-singletons-profiling
     - talos-speedometer-profiling
     - talos-motionmark-profiling
     - talos-svgr-profiling
     - talos-tp5o-profiling
     - talos-tp6-profiling
+    # - talos-tps-profiling # Bug 1453007 - times out
 
 linux-talos-flex:
     - talos-flex
 
 windows-qr-tests:
     - crashtest
     - mochitest-gpu
     - mochitest-media
@@ -189,48 +191,50 @@ windows-tests:
     - test-verify
     - test-verify-wpt
     - web-platform-tests
     - web-platform-tests-reftests
     - xpcshell
 
 windows-talos:
     - talos-chrome
+    - talos-damp
     - talos-dromaeojs
     - talos-g1
-    - talos-g2
     - talos-g4
     - talos-g5
     - talos-other
     - talos-perf-reftest
     - talos-perf-reftest-singletons
     - talos-svgr
     - talos-tp5o
     - talos-xperf
     - talos-speedometer
     - talos-tp6
+    - talos-tps
     - talos-motionmark
     - talos-h1
 
 windows-talos-profiling:
     - talos-chrome-profiling
+    - talos-damp-profiling
     - talos-dromaeojs-profiling
     - talos-g1-profiling
-    - talos-g2-profiling
     - talos-g3-profiling
     - talos-g4-profiling
     - talos-g5-profiling
     - talos-motionmark-profiling
     - talos-other-profiling
     - talos-perf-reftest-profiling
     - talos-perf-reftest-singletons-profiling
     - talos-speedometer-profiling
     - talos-svgr-profiling
     - talos-tp5o-profiling
     - talos-tp6-profiling
+    - talos-tps-profiling
 
 macosx64-tests:
     - cppunit
     - crashtest
     - firefox-ui-functional-local
     - firefox-ui-functional-remote
     - gtest
     - jittest
@@ -250,48 +254,50 @@ macosx64-tests:
     - test-verify
     - test-verify-wpt
     - web-platform-tests
     - web-platform-tests-reftests
     - xpcshell
 
 macosx64-talos:
     - talos-chrome
+    - talos-damp
     - talos-dromaeojs
     - talos-g1
-    - talos-g2
     - talos-g4
     - talos-g5
     - talos-other
     - talos-svgr
     - talos-tp5o
     - talos-perf-reftest
     - talos-perf-reftest-singletons
     - talos-tp6
     - talos-tp6-stylo-threads
+    # - talos-tps # Bug 1453007 times out
     - talos-speedometer
     - talos-motionmark
     - talos-h1
 
 macosx64-talos-profiling:
     - talos-chrome-profiling
+    - talos-damp-profiling
     - talos-dromaeojs-profiling
     - talos-g1-profiling
-    - talos-g2-profiling
     - talos-g3-profiling
     - talos-g4-profiling
     - talos-g5-profiling
     - talos-other-profiling
     - talos-perf-reftest-profiling
     - talos-perf-reftest-singletons-profiling
     - talos-speedometer-profiling
     - talos-motionmark-profiling
     - talos-svgr-profiling
     - talos-tp5o-profiling
     - talos-tp6-profiling
+    # - talos-tps-profiling # Bug 1453007 times out
 
 linux32-tests:
     - cppunit
     - crashtest
     - firefox-ui-functional-local
     - firefox-ui-functional-remote
     - gtest
     - jittest
--- a/taskcluster/docs/kinds.rst
+++ b/taskcluster/docs/kinds.rst
@@ -305,16 +305,24 @@ Submit to S3 the artifacts produced by t
 release-final-verify
 --------------------
 Verifies the contents and package of release update MARs.
 
 release-secondary-final-verify
 ------------------------------
 Verifies the contents and package of release update MARs for RC releases.
 
+release-sign-and-push-langpacks
+-------------------------------
+Sign a langpack XPI and publishes it onto addons.mozilla.org.
+
+release-beetmover-signed-langpacks
+----------------------------------
+Publishes signed langpacks to archive.mozilla.org
+
 release-update-verify
 ---------------------
 Verifies the contents and package of release update MARs.
 
 release-secondary-update-verify
 ---------------------
 Verifies the contents and package of release update MARs.
 
@@ -390,16 +398,20 @@ Dummy tasks to consolidate balrog depend
 post-beetmover-dummy
 --------------------
 Dummy tasks to consolidate beetmover dependencies to avoid taskcluster limits on number of dependencies per task.
 
 post-beetmover-checksums-dummy
 ------------------------------
 Dummy tasks to consolidate beetmover-checksums dependencies to avoid taskcluster limits on number of dependencies per task.
 
+post-langpack-dummy
+------------------------------
+Dummy tasks to consolidate language pack beetmover dependencies to avoid taskcluster limits on number of dependencies per task.
+
 packages
 --------
 Tasks used to build packages for use in docker images.
 
 diffoscope
 ----------
 Tasks used to compare pairs of Firefox builds using https://diffoscope.org/.
 As of writing, this is mainly meant to be used in try builds, by editing
--- a/taskcluster/taskgraph/transforms/beetmover_repackage.py
+++ b/taskcluster/taskgraph/transforms/beetmover_repackage.py
@@ -50,44 +50,53 @@ logger = logging.getLogger(__name__)
     "target.awsy.tests.zip",
     "target.test_packages.json",
     "target.txt",
     "target.web-platform.tests.tar.gz",
     "target.xpcshell.tests.zip",
     "target_info.txt",
     "target.jsshell.zip",
     "mozharness.zip",
-    "target.langpack.xpi",
-]
-
-# Until bug 1331141 is fixed, if you are adding any new artifacts here that
-# need to be transfered to S3, please be aware you also need to follow-up
-# with a beetmover patch in https://github.com/mozilla-releng/beetmoverscript/.
-# See example in bug 1348286
-_DESKTOP_UPSTREAM_ARTIFACTS_UNSIGNED_L10N = [
-    "target.langpack.xpi",
 ]
 
 # Until bug 1331141 is fixed, if you are adding any new artifacts here that
 # need to be transfered to S3, please be aware you also need to follow-up
 # with a beetmover patch in https://github.com/mozilla-releng/beetmoverscript/.
 # See example in bug 1348286
 UPSTREAM_ARTIFACT_UNSIGNED_PATHS = {
-    r'^(linux(|64)|macosx64)(|-devedition)-nightly$':
+    r'^(linux(|64)|macosx64)-nightly$':
         _DESKTOP_UPSTREAM_ARTIFACTS_UNSIGNED_EN_US + [
             'host/bin/mar',
             'host/bin/mbsdiff',
         ],
-    r'^win(32|64)(|-devedition)-nightly$':
+    r'^(linux(|64)|macosx64)-devedition-nightly$':
+        _DESKTOP_UPSTREAM_ARTIFACTS_UNSIGNED_EN_US + [
+            'host/bin/mar',
+            'host/bin/mbsdiff',
+            # TODO Bug 1453033: Sign devedition langpacks
+            'target.langpack.xpi',
+        ],
+    r'^win(32|64)-nightly$':
         _DESKTOP_UPSTREAM_ARTIFACTS_UNSIGNED_EN_US + [
-            "host/bin/mar.exe",
-            "host/bin/mbsdiff.exe",
+            'host/bin/mar.exe',
+            'host/bin/mbsdiff.exe',
         ],
-    r'^(linux(|64)|macosx64|win(32|64))(|-devedition)-nightly-l10n$':
-        _DESKTOP_UPSTREAM_ARTIFACTS_UNSIGNED_L10N,
+    r'^win(32|64)-devedition-nightly$':
+        _DESKTOP_UPSTREAM_ARTIFACTS_UNSIGNED_EN_US + [
+            'host/bin/mar.exe',
+            'host/bin/mbsdiff.exe',
+            # TODO Bug 1453033: Sign devedition langpacks
+            'target.langpack.xpi',
+        ],
+    r'^(linux(|64)|macosx64|win(32|64))-nightly-l10n$': [],
+    r'^(linux(|64)|macosx64|win(32|64))-devedition-nightly-l10n$':
+        [
+            # TODO Bug 1453033: Sign devedition langpacks
+            'target.langpack.xpi',
+        ],
 }
 
 # Until bug 1331141 is fixed, if you are adding any new artifacts here that
 # need to be transfered to S3, please be aware you also need to follow-up
 # with a beetmover patch in https://github.com/mozilla-releng/beetmoverscript/.
 # See example in bug 1348286
 UPSTREAM_ARTIFACT_SIGNED_PATHS = {
     r'^linux(|64)(|-devedition)-nightly(|-l10n)$': ['target.tar.bz2', 'target.tar.bz2.asc'],
@@ -282,22 +291,23 @@ def generate_upstream_artifacts(build_ta
 
     for ref, tasktype, mapping in zip(task_refs, tasktypes, mapping):
         plarform_was_previously_matched_by_regex = None
         for platform_regex, paths in mapping.iteritems():
             if platform_regex.match(platform) is not None:
                 _check_platform_matched_only_one_regex(
                     tasktype, platform, plarform_was_previously_matched_by_regex, platform_regex
                 )
-                upstream_artifacts.append({
-                    "taskId": {"task-reference": ref},
-                    "taskType": tasktype,
-                    "paths": ["{}/{}".format(artifact_prefix, path) for path in paths],
-                    "locale": locale or "en-US",
-                })
+                if paths:
+                    upstream_artifacts.append({
+                        "taskId": {"task-reference": ref},
+                        "taskType": tasktype,
+                        "paths": ["{}/{}".format(artifact_prefix, path) for path in paths],
+                        "locale": locale or "en-US",
+                    })
                 plarform_was_previously_matched_by_regex = platform_regex
 
     return upstream_artifacts
 
 
 def generate_partials_upstream_artifacts(artifacts, platform, locale=None):
     if not locale or locale == 'en-US':
         artifact_prefix = 'public/build'
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/transforms/release_beetmover_signed_addons.py
@@ -0,0 +1,208 @@
+# 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/.
+"""
+Transform the beetmover task into an actual task description.
+"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+from taskgraph.transforms.base import TransformSequence
+from taskgraph.transforms.beetmover import craft_release_properties
+from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema, Schema, optionally_keyed_by, resolve_keyed_by
+from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
+                                         get_beetmover_action_scope)
+from taskgraph.transforms.task import task_description_schema
+from taskgraph.transforms.release_sign_and_push_langpacks import get_upstream_task_ref
+from voluptuous import Required, Optional
+
+import logging
+import copy
+
+logger = logging.getLogger(__name__)
+
+
+task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
+
+
+transforms = TransformSequence()
+
+
+beetmover_description_schema = Schema({
+    # the dependent task (object) for this beetmover job, used to inform beetmover.
+    Required('dependent-task'): object,
+
+    # depname is used in taskref's to identify the taskID of the unsigned things
+    Required('depname', default='build'): basestring,
+
+    # unique label to describe this beetmover task, defaults to {dep.label}-beetmover
+    Optional('label'): basestring,
+
+    # treeherder is allowed here to override any defaults we use for beetmover.  See
+    # taskcluster/taskgraph/transforms/task.py for the schema details, and the
+    # below transforms for defaults of various values.
+    Optional('treeherder'): task_description_schema['treeherder'],
+
+    Required('description'): basestring,
+    Required('worker-type'): optionally_keyed_by('project', basestring),
+    Required('run-on-projects'): [],
+
+    # locale is passed only for l10n beetmoving
+    Optional('locale'): basestring,
+    Optional('shipping-phase'): task_description_schema['shipping-phase'],
+    Optional('shipping-product'): task_description_schema['shipping-product'],
+})
+
+
+@transforms.add
+def set_label(config, jobs):
+    for job in jobs:
+        job['label'] = job['dependent-task'].label.replace(
+            'sign-and-push-langpacks', 'beetmover-signed-langpacks'
+        )
+
+        yield job
+
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        validate_schema(
+            beetmover_description_schema, job,
+            "In beetmover ({!r} kind) task for {!r}:".format(config.kind, job['label'])
+        )
+        yield job
+
+
+@transforms.add
+def resolve_keys(config, jobs):
+    for job in jobs:
+        resolve_keyed_by(
+            job, 'worker-type', item_name=job['label'], project=config.params['project']
+        )
+        yield job
+
+
+@transforms.add
+def make_task_description(config, jobs):
+    for job in jobs:
+        dep_job = job['dependent-task']
+        attributes = dep_job.attributes
+
+        treeherder = job.get('treeherder', {})
+        treeherder.setdefault('symbol', 'langpack(BM{})'.format(attributes.get('l10n_chunk', '')))
+        dep_th_platform = dep_job.task.get('extra', {}).get(
+            'treeherder', {}).get('machine', {}).get('platform', '')
+        treeherder.setdefault('platform',
+                              "{}/opt".format(dep_th_platform))
+        treeherder.setdefault('tier', 1)
+        treeherder.setdefault('kind', 'build')
+
+        job['attributes'] = copy_attributes_from_dependent_job(dep_job)
+        job['attributes']['chunk_locales'] = dep_job.attributes['chunk_locales']
+
+        job['description'] = job['description'].format(
+            locales='/'.join(job['attributes']['chunk_locales']),
+            platform=job['attributes']['build_platform']
+        )
+
+        job['scopes'] = [
+            get_beetmover_bucket_scope(config),
+            get_beetmover_action_scope(config),
+        ]
+
+        job['dependencies'] = {
+            str(dep_job.kind): dep_job.label
+        }
+
+        job['run-on-projects'] = dep_job.attributes['run_on_projects']
+        job['treeherder'] = treeherder
+        job['shipping-phase'] = dep_job.attributes['shipping_phase']
+        job['shipping-product'] = dep_job.attributes['shipping_product']
+
+        yield job
+
+
+@transforms.add
+def make_task_worker(config, jobs):
+    for job in jobs:
+        signing_task_ref = get_upstream_task_ref(
+            job, expected_kinds=('release-sign-and-push-langpacks',)
+        )
+
+        job['worker'] = {
+            'implementation': 'beetmover',
+            'release-properties': craft_release_properties(config, job),
+            'upstream-artifacts': generate_upstream_artifacts(
+                signing_task_ref, job['attributes']['chunk_locales']
+            ),
+        }
+
+        yield job
+
+
+def generate_upstream_artifacts(upstream_task_ref, locales):
+    return [{
+        'taskId': {'task-reference': upstream_task_ref},
+        'taskType': 'scriptworker',
+        'locale': locale,
+        'paths': [
+            # addonscript uploads en-US XPI in the en-US folder
+            'public/build/{}/target.langpack.xpi'.format(locale)
+        ],
+    } for locale in locales]
+
+
+@transforms.add
+def strip_unused_data(config, jobs):
+    for job in jobs:
+        del job['dependent-task']
+
+        yield job
+
+
+@transforms.add
+def yield_all_platform_jobs(config, jobs):
+    # Even though langpacks are now platform independent, we keep beetmoving them at old
+    # platform-specific locations. That's why this transform exist
+    for job in jobs:
+        if 'ja-JP-mac' in job['label']:
+            # This locale must not be copied on any other platform than macos
+            yield job
+        else:
+            for platform in ('linux', 'linux64', 'macosx64', 'win32', 'win64'):
+                platform_job = copy.deepcopy(job)
+                if 'ja' in platform_job['attributes']['chunk_locales'] and platform == 'macosx64':
+                    platform_job = _strip_ja_data_from_linux_job(platform_job)
+
+                platform_job = _change_platform_data(platform_job, platform)
+
+                yield platform_job
+
+
+def _strip_ja_data_from_linux_job(platform_job):
+    # Let's take "ja" out the description. This locale is in a substring like "aa/bb/cc/dd", where
+    # "ja" could be any of "aa", "bb", "cc", "dd"
+    platform_job['description'] = platform_job['description'].replace('ja/', '')
+    platform_job['description'] = platform_job['description'].replace('/ja', '')
+
+    platform_job['worker']['upstream-artifacts'] = [
+        artifact
+        for artifact in platform_job['worker']['upstream-artifacts']
+        if artifact['locale'] != 'ja'
+    ]
+
+    return platform_job
+
+
+def _change_platform_data(platform_job, platform):
+    platform_job['attributes']['build_platform'] = platform
+    platform_job['label'] = platform_job['label'].replace('linux64', platform)
+    platform_job['description'] = platform_job['description'].replace('linux64', platform)
+    platform_job['treeherder']['platform'] = platform_job['treeherder']['platform'].replace(
+        'linux64', platform
+    )
+    platform_job['worker']['release-properties']['platform'] = platform
+
+    return platform_job
new file mode 100644
--- /dev/null
+++ b/taskcluster/taskgraph/transforms/release_sign_and_push_langpacks.py
@@ -0,0 +1,175 @@
+# 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/.
+"""
+Transform the release-sign-and-push task into an actual task description.
+"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+from taskgraph.transforms.base import TransformSequence
+from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema, Schema, resolve_keyed_by, optionally_keyed_by
+from taskgraph.transforms.task import task_description_schema
+from voluptuous import Any, Required
+
+
+transforms = TransformSequence()
+
+task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
+
+
+transforms = TransformSequence()
+
+
+langpack_sign_push_description_schema = Schema({
+    Required('dependent-task'): object,
+    Required('label'): basestring,
+    Required('description'): basestring,
+    Required('worker-type'): optionally_keyed_by('project', basestring),
+    Required('worker'): {
+        Required('implementation'): 'sign-and-push-addons',
+        Required('channel'): optionally_keyed_by('project', Any('listed', 'unlisted')),
+        Required('upstream-artifacts'): None,   # Processed here below
+    },
+
+    Required('run-on-projects'): [],
+    Required('scopes'): optionally_keyed_by('project', [basestring]),
+    Required('shipping-phase'): task_description_schema['shipping-phase'],
+    Required('shipping-product'): task_description_schema['shipping-product'],
+})
+
+
+@transforms.add
+def set_label(config, jobs):
+    for job in jobs:
+        label = 'sign-and-push-langpacks-{}'.format(job['dependent-task'].label)
+        job['label'] = label
+
+        yield job
+
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        validate_schema(
+            langpack_sign_push_description_schema, job,
+            'In sign-and-push-langpacks ({} kind) task for {}:'.format(config.kind, job['label'])
+        )
+        yield job
+
+
+@transforms.add
+def resolve_keys(config, jobs):
+    for job in jobs:
+        resolve_keyed_by(
+            job, 'worker-type', item_name=job['label'], project=config.params['project']
+        )
+        resolve_keyed_by(
+            job, 'scopes', item_name=job['label'], project=config.params['project']
+        )
+        resolve_keyed_by(
+            job, 'worker.channel', item_name=job['label'], project=config.params['project']
+        )
+
+        yield job
+
+
+@transforms.add
+def copy_attributes(config, jobs):
+    for job in jobs:
+        dep_job = job['dependent-task']
+        job['attributes'] = copy_attributes_from_dependent_job(dep_job)
+        job['attributes']['chunk_locales'] = dep_job.attributes.get('chunk_locales', ['en-US'])
+
+        yield job
+
+
+@transforms.add
+def filter_out_macos_jobs_but_mac_only_locales(config, jobs):
+    for job in jobs:
+        build_platform = job['dependent-task'].attributes.get('build_platform')
+
+        if build_platform == 'linux64-nightly':
+            yield job
+        elif build_platform == 'macosx64-nightly' and \
+                'ja-JP-mac' in job['attributes']['chunk_locales']:
+            # Other locales of the same job shouldn't be processed
+            job['attributes']['chunk_locales'] = ['ja-JP-mac']
+            job['label'] = job['label'].replace(
+                job['attributes']['l10n_chunk'], 'ja-JP-mac'
+            )
+            yield job
+
+
+@transforms.add
+def make_task_description(config, jobs):
+    for job in jobs:
+        dep_job = job['dependent-task']
+
+        treeherder = job.get('treeherder', {})
+        treeherder.setdefault('symbol', 'langpack(SnP{})'.format(
+            job['attributes'].get('l10n_chunk', '')
+        ))
+        dep_th_platform = dep_job.task.get('extra', {}).get(
+            'treeherder', {}).get('machine', {}).get('platform', '')
+        treeherder.setdefault('platform', '{}/opt'.format(dep_th_platform))
+        treeherder.setdefault('tier', 1)
+        treeherder.setdefault('kind', 'build')
+
+        job['description'] = job['description'].format(
+            locales='/'.join(job['attributes']['chunk_locales']),
+        )
+
+        job['dependencies'] = {
+            str(dep_job.kind): dep_job.label
+        }
+        job['treeherder'] = treeherder
+
+        yield job
+
+
+def generate_upstream_artifacts(upstream_task_ref, locales):
+    return [{
+        'taskId': {'task-reference': upstream_task_ref},
+        'taskType': 'build',
+        'paths': [
+            'public/build{locale}/target.langpack.xpi'.format(
+                locale='' if locale == 'en-US' else '/' + locale
+            )
+            for locale in locales
+        ],
+    }]
+
+
+@transforms.add
+def make_task_worker(config, jobs):
+    for job in jobs:
+        upstream_task_ref = get_upstream_task_ref(job, expected_kinds=('build', 'nightly-l10n'))
+
+        job['worker']['upstream-artifacts'] = generate_upstream_artifacts(
+            upstream_task_ref, job['attributes']['chunk_locales']
+        )
+
+        yield job
+
+
+def get_upstream_task_ref(job, expected_kinds):
+    upstream_tasks = [
+        job_kind
+        for job_kind in job['dependencies'].keys()
+        if job_kind in expected_kinds
+    ]
+
+    if len(upstream_tasks) > 1:
+        raise Exception('Only one dependency expected')
+
+    return '<{}>'.format(upstream_tasks[0])
+
+
+@transforms.add
+def strip_unused_data(config, jobs):
+    for job in jobs:
+        del job['dependent-task']
+
+        yield job
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -451,17 +451,17 @@ task_description_schema = Schema({
             Required('formats'): [basestring],
         }],
     }, {
         Required('implementation'): 'binary-transparency',
     }, {
         Required('implementation'): 'beetmover',
 
         # the maximum time to run, in seconds
-        Required('max-run-time'): int,
+        Required('max-run-time', default=600): int,
 
         # locale key, if this is a locale beetmover job
         Optional('locale'): basestring,
 
         Required('release-properties'): {
             'app-name': basestring,
             'app-version': basestring,
             'branch': basestring,
@@ -546,16 +546,24 @@ task_description_schema = Schema({
     }, {
         Required('implementation'): 'push-snap',
         Required('upstream-artifacts'): [{
             Required('taskId'): taskref_or_string,
             Required('taskType'): basestring,
             Required('paths'): [basestring],
         }],
     }, {
+        Required('implementation'): 'sign-and-push-addons',
+        Required('channel'): Any('listed', 'unlisted'),
+        Required('upstream-artifacts'): [{
+            Required('taskId'): taskref_or_string,
+            Required('taskType'): basestring,
+            Required('paths'): [basestring],
+        }],
+    }, {
         Required('implementation'): 'shipit',
         Required('release-name'): basestring,
     }, {
         Required('implementation'): 'treescript',
         Required('tag'): bool,
         Required('bump'): bool,
         Optional('bump-files'): [basestring],
         Required('force-dry-run', default=True): bool,
@@ -1094,17 +1102,17 @@ def build_bouncer_submission_payload(con
 
 
 @payload_builder('push-apk')
 def build_push_apk_payload(config, task, task_def):
     worker = task['worker']
 
     task_def['payload'] = {
         'commit': worker['commit'],
-        'upstreamArtifacts':  worker['upstream-artifacts'],
+        'upstreamArtifacts': worker['upstream-artifacts'],
         'google_play_track': worker['google-play-track'],
     }
 
     if worker.get('rollout-percentage', None):
         task_def['payload']['rollout_percentage'] = worker['rollout-percentage']
 
 
 @payload_builder('push-snap')
@@ -1120,16 +1128,26 @@ def build_push_snap_payload(config, task
 def build_ship_it_payload(config, task, task_def):
     worker = task['worker']
 
     task_def['payload'] = {
         'release_name': worker['release-name']
     }
 
 
+@payload_builder('sign-and-push-addons')
+def build_sign_and_push_addons_payload(config, task, task_def):
+    worker = task['worker']
+
+    task_def['payload'] = {
+        'channel': worker['channel'],
+        'upstreamArtifacts': worker['upstream-artifacts'],
+    }
+
+
 @payload_builder('treescript')
 def build_treescript_payload(config, task, task_def):
     worker = task['worker']
     release_config = get_release_config(config)
 
     task_def['payload'] = {}
     task_def.setdefault('scopes', [])
     if worker['tag']:
--- a/testing/talos/talos.json
+++ b/testing/talos/talos.json
@@ -11,18 +11,22 @@
         },
         "other-e10s": {
             "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_many_windows", "sessionrestore_no_auto_restore", "tabpaint", "cpstartup"]
         },
         "g1-e10s": {
             "tests": ["tp5o_scroll", "glterrain"],
             "pagesets_name": "tp5n.zip"
         },
-        "g2-e10s": {
-            "tests": ["damp", "tps"],
+        "damp-e10s": {
+            "tests": ["damp"],
+            "pagesets_name": "tp5n.zip"
+        },
+        "tps-e10s": {
+            "tests": ["tps"],
             "pagesets_name": "tp5n.zip"
         },
         "g3-e10s": {
             "tests": ["dromaeo_dom"]
         },
         "g4-e10s": {
             "tests": ["basic_compositor_video", "glvideo", "displaylist_mutate", "rasterflood_svg", "rasterflood_gradient"]
         },
--- a/toolkit/content/widgets/button.xml
+++ b/toolkit/content/widgets/button.xml
@@ -367,12 +367,9 @@
                   xbl:inherits="disabled,crop,image,label,accesskey,command,
                                 buttonover,buttondown,align,dir,pack,orient">
         <children/>
       </xul:button>
       <xul:dropmarker class="button-menubutton-dropmarker" xbl:inherits="open,disabled,label"/>
     </content>
   </binding>
 
-  <binding id="button-repeat" display="xul:autorepeatbutton"
-           extends="chrome://global/content/bindings/button.xml#button"/>
-
 </bindings>
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -101,20 +101,16 @@ vbox {
 }
 
 /********** button **********/
 
 button {
   -moz-binding: url("chrome://global/content/bindings/button.xml#button");
 }
 
-button[type="repeat"] {
-  -moz-binding: url("chrome://global/content/bindings/button.xml#button-repeat");
-}
-
 button[type="menu"] {
   -moz-binding: url("chrome://global/content/bindings/button.xml#menu");
 }
 
 button[type="menu-button"] {
   -moz-binding: url("chrome://global/content/bindings/button.xml#menu-button");
 }