Merge inbound to mozilla-central. a=merge
authorBrindusan Cristian <cbrindusan@mozilla.com>
Sat, 05 May 2018 00:35:50 +0300
changeset 473091 5207b1392b11db534550a5eb801302e6dbb58f95
parent 473086 82f4604be112cd1f90101218489327d3784b08a9 (current diff)
parent 473090 f124a57ef00abf098b3198c8e8958b982a4d2ef2 (diff)
child 473101 031ea9aeb00655ba825df029569d70dbc41f761e
child 473144 3b55fcc68a6d1b61d9ad38304a4df86115299392
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
5207b1392b11 / 61.0a1 / 20180504220115 / files
nightly linux64
5207b1392b11 / 61.0a1 / 20180504220115 / files
nightly mac
5207b1392b11 / 61.0a1 / 20180504220115 / files
nightly win32
5207b1392b11 / 61.0a1 / 20180504220115 / files
nightly win64
5207b1392b11 / 61.0a1 / 20180504220115 / 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
testing/mozharness/configs/releases/bouncer_fennec.py
testing/mozharness/configs/releases/bouncer_fennec_beta.py
testing/mozharness/configs/releases/bouncer_firefox_beta.py
testing/mozharness/configs/releases/bouncer_firefox_devedition.py
testing/mozharness/configs/releases/bouncer_firefox_esr.py
testing/mozharness/configs/releases/bouncer_firefox_release.py
testing/mozharness/configs/releases/dev_bouncer_firefox_beta.py
testing/mozharness/configs/releases/dev_bouncer_firefox_devedition.py
testing/mozharness/configs/releases/dev_bouncer_firefox_esr.py
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -2341,17 +2341,17 @@ Accessible::GetIndexOfEmbeddedChild(Acce
 
   return GetIndexOf(aChild);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // HyperLinkAccessible methods
 
 bool
-Accessible::IsLink()
+Accessible::IsLink() const
 {
   // Every embedded accessible within hypertext accessible implements
   // hyperlink interface.
   return mParent && mParent->IsHyperText() && !IsText();
 }
 
 uint32_t
 Accessible::StartOffset()
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -721,17 +721,17 @@ public:
 
   //////////////////////////////////////////////////////////////////////////////
   // HyperLinkAccessible (any embedded object in text can implement HyperLink,
   // which helps determine where it is located within containing text).
 
   /**
    * Return true if the accessible is hyper link accessible.
    */
-  virtual bool IsLink();
+  virtual bool IsLink() const;
 
   /**
    * Return the start offset of the link within the parent accessible.
    */
   virtual uint32_t StartOffset();
 
   /**
    * Return the end offset of the link within the parent accessible.
--- a/accessible/html/HTMLLinkAccessible.cpp
+++ b/accessible/html/HTMLLinkAccessible.cpp
@@ -116,17 +116,17 @@ HTMLLinkAccessible::DoAction(uint8_t aIn
   DoCommand();
   return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // HyperLinkAccessible
 
 bool
-HTMLLinkAccessible::IsLink()
+HTMLLinkAccessible::IsLink() const
 {
   // Expose HyperLinkAccessible unconditionally.
   return true;
 }
 
 already_AddRefed<nsIURI>
 HTMLLinkAccessible::AnchorURIAt(uint32_t aAnchorIndex)
 {
--- a/accessible/html/HTMLLinkAccessible.h
+++ b/accessible/html/HTMLLinkAccessible.h
@@ -27,17 +27,17 @@ public:
   virtual uint64_t NativeInteractiveState() const override;
 
   // ActionAccessible
   virtual uint8_t ActionCount() override;
   virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
   virtual bool DoAction(uint8_t aIndex) override;
 
   // HyperLinkAccessible
-  virtual bool IsLink() override;
+  virtual bool IsLink() const override;
   virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) override;
 
 protected:
   virtual ~HTMLLinkAccessible() {}
 
   enum { eAction_Jump = 0 };
 
   /**
--- a/accessible/xul/XULElementAccessibles.cpp
+++ b/accessible/xul/XULElementAccessibles.cpp
@@ -235,17 +235,17 @@ XULLinkAccessible::DoAction(uint8_t aInd
   DoCommand();
   return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // XULLinkAccessible: HyperLinkAccessible
 
 bool
-XULLinkAccessible::IsLink()
+XULLinkAccessible::IsLink() const
 {
   // Expose HyperLinkAccessible unconditionally.
   return true;
 }
 
 uint32_t
 XULLinkAccessible::StartOffset()
 {
--- a/accessible/xul/XULElementAccessibles.h
+++ b/accessible/xul/XULElementAccessibles.h
@@ -90,17 +90,17 @@ public:
   virtual uint64_t NativeLinkState() const override;
 
   // ActionAccessible
   virtual uint8_t ActionCount() override;
   virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
   virtual bool DoAction(uint8_t aIndex) override;
 
   // HyperLinkAccessible
-  virtual bool IsLink() override;
+  virtual bool IsLink() const override;
   virtual uint32_t StartOffset() override;
   virtual uint32_t EndOffset() override;
   virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) override;
 
 protected:
   virtual ~XULLinkAccessible();
 
   // Accessible
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -972,17 +972,16 @@ window._gBrowser = {
       this._callProgressListeners(null, "onUpdateCurrentBrowser",
                                   [listener.mStateFlags, listener.mStatus,
                                    listener.mMessage, listener.mTotalProgress],
                                   true, false);
     }
 
     if (!this._previewMode) {
       newTab.updateLastAccessed();
-      newTab.removeAttribute("unread");
       oldTab.updateLastAccessed();
 
       let oldFindBar = oldTab._findBar;
       if (oldFindBar &&
           oldFindBar.findMode == oldFindBar.FIND_NORMAL &&
           !oldFindBar.hidden)
         this._lastFindValue = oldFindBar._findField.value;
 
@@ -4384,18 +4383,16 @@ class TabProgressListener {
           } else {
             this.mTab.removeAttribute("notselectedsinceload");
           }
 
           this.mTab.setAttribute("bursting", "true");
         }
 
         gBrowser._tabAttrModified(this.mTab, ["busy"]);
-        if (!this.mTab.selected)
-          this.mTab.setAttribute("unread", "true");
       }
       this.mTab.removeAttribute("progress");
 
       if (aWebProgress.isTopLevel) {
         let isSuccessful = Components.isSuccessCode(aStatus);
         if (!isSuccessful && !isTabEmpty(this.mTab)) {
           // Restore the current document's location in case the
           // request was stopped (possibly from a content script)
--- a/browser/base/content/test/general/browser_contextmenu.js
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -52,16 +52,18 @@ add_task(async function test_xul_text_li
   // Clean up so won't affect HTML element test cases
   lastElementSelector = null;
   gBrowser.removeCurrentTab();
 });
 
 // Below are test cases for HTML element
 
 add_task(async function test_setup_html() {
+  await pushPrefs(["dom.webcomponents.shadowdom.enabled", true]);
+
   let url = example_base + "subtst_contextmenu.html";
 
   await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
 
   await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
     let doc = content.document;
     let audioIframe = doc.querySelector("#test-audio-in-iframe");
     // media documents always use a <video> tag.
@@ -126,16 +128,41 @@ add_task(async function test_link() {
      "context-copylink",      true,
      "context-searchselect",  true,
      "---", null,
      "context-sendlinktodevice", true, [], null,
     ]
   );
 });
 
+add_task(async function test_link_in_shadow_dom() {
+  await test_contextmenu("#shadow-host",
+    ["context-openlinkintab", true,
+     ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+     // We need a blank entry here because the containers submenu is
+     // dynamically generated with no ids.
+     ...(hasContainers ? ["", null] : []),
+     "context-openlink",      true,
+     "context-openlinkprivate", true,
+     "---",                   null,
+     "context-bookmarklink",  true,
+     "context-savelink",      true,
+     ...(hasPocket ? ["context-savelinktopocket", true] : []),
+     "context-copylink",      true,
+     "context-searchselect",  true,
+     "---", null,
+     "context-sendlinktodevice", true, [], null,
+    ],
+    {
+      offsetX: 6,
+      offsetY: 6
+    }
+  );
+});
+
 add_task(async function test_mailto() {
   await test_contextmenu("#test-mailto",
     ["context-copyemail", true,
      "context-searchselect", true
     ]
   );
 });
 
--- a/browser/base/content/test/general/subtst_contextmenu.html
+++ b/browser/base/content/test/general/subtst_contextmenu.html
@@ -3,16 +3,24 @@
 <head>
   <title>Subtest for browser context menu</title>
 </head>
 <body>
 Browser context menu subtest.
 
 <div id="test-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
 <a id="test-link" href="http://mozilla.com">Click the monkey!</a>
+<div id="shadow-host"></div>
+<script>
+// Create the shadow DOM in case shadow DOM is enabled.
+if ("ShadowRoot" in this) {
+  var sr = document.getElementById("shadow-host").attachShadow({mode: "closed"});
+  sr.innerHTML = "<a href='http://mozilla.com'>Click the monkey!</a>";
+}
+</script>
 <a id="test-mailto" href="mailto:codemonkey@mozilla.com">Mail the monkey!</a><br>
 <input id="test-input"><br>
 <img id="test-image" src="ctxmenu-image.png">
 <canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas>
 <video controls id="test-video-ok"  src="video.ogg" width="100" height="100" style="background-color: green"></video>
 <video id="test-audio-in-video" src="audio.ogg" width="100" height="100" style="background-color: red"></video>
 <video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video>
 <video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow">
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -18,17 +18,17 @@ skip-if = asan || debug || (os == 'win' 
 skip-if = !debug
 [browser_startup.js]
 [browser_startup_content.js]
 skip-if = !e10s
 [browser_startup_flicker.js]
 run-if = debug || devedition || nightly_build # Requires startupRecorder.js, which isn't shipped everywhere by default
 [browser_tabclose_grow.js]
 [browser_tabclose.js]
-skip-if = true # bug 1448497
+skip-if = os == 'linux' # bug 1448497
 [browser_tabopen.js]
 [browser_tabopen_squeeze.js]
 [browser_tabstrip_overflow_underflow.js]
 [browser_tabswitch.js]
 [browser_toolbariconcolor_restyles.js]
 [browser_urlbar_keyed_search.js]
 skip-if = (os == 'linux') || (os == 'win' && debug) # Disabled on Linux and Windows debug due to perma failures. Bug 1392320.
 [browser_urlbar_search.js]
--- a/browser/modules/ContextMenu.jsm
+++ b/browser/modules/ContextMenu.jsm
@@ -489,85 +489,86 @@ class ContextMenu {
 
   _handleContentContextMenu(aEvent) {
     let defaultPrevented = aEvent.defaultPrevented;
 
     if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
       let plugin = null;
 
       try {
-        plugin = aEvent.target.QueryInterface(Ci.nsIObjectLoadingContent);
+        plugin = aEvent.composedTarget.QueryInterface(Ci.nsIObjectLoadingContent);
       } catch (e) {}
 
       if (plugin && plugin.displayedType == Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
         // Don't open a context menu for plugins.
         return;
       }
 
       defaultPrevented = false;
     }
 
     if (defaultPrevented) {
       return;
     }
 
-    let doc = aEvent.target.ownerDocument;
+    let doc = aEvent.composedTarget.ownerDocument;
     let {
       mozDocumentURIIfNotForErrorPages: docLocation,
       characterSet: charSet,
       baseURI,
       referrer,
       referrerPolicy
     } = doc;
     docLocation = docLocation && docLocation.spec;
     let frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView);
-    let loginFillInfo = LoginManagerContent.getFieldContext(aEvent.target);
+    let loginFillInfo = LoginManagerContent.getFieldContext(aEvent.composedTarget);
 
     // The same-origin check will be done in nsContextMenu.openLinkInTab.
     let parentAllowsMixedContent = !!this.global.docShell.mixedContentChannel;
 
     // Get referrer attribute from clicked link and parse it
-    let referrerAttrValue = Services.netUtils.parseAttributePolicyString(aEvent.target.
-                            getAttribute("referrerpolicy"));
+    let referrerAttrValue =
+      Services.netUtils.parseAttributePolicyString(aEvent.composedTarget.
+                                                   getAttribute("referrerpolicy"));
 
     if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
       referrerPolicy = referrerAttrValue;
     }
 
     let disableSetDesktopBg = null;
 
     // Media related cache info parent needs for saving
     let contentType = null;
     let contentDisposition = null;
-    if (aEvent.target.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
-        aEvent.target instanceof Ci.nsIImageLoadingContent &&
-        aEvent.target.currentURI) {
-      disableSetDesktopBg = this._disableSetDesktopBackground(aEvent.target);
+    if (aEvent.composedTarget.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
+        aEvent.composedTarget instanceof Ci.nsIImageLoadingContent &&
+        aEvent.composedTarget.currentURI) {
+      disableSetDesktopBg = this._disableSetDesktopBackground(aEvent.composedTarget);
 
       try {
         let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                                                          .getImgCacheForDocument(doc);
         // The image cache's notion of where this image is located is
         // the currentURI of the image loading content.
-        let props = imageCache.findEntryProperties(aEvent.target.currentURI, doc);
+        let props = imageCache.findEntryProperties(aEvent.composedTarget.currentURI, doc);
 
         try {
           contentType = props.get("type", Ci.nsISupportsCString).data;
         } catch (e) {}
 
         try {
           contentDisposition = props.get("content-disposition", Ci.nsISupportsCString).data;
         } catch (e) {}
       } catch (e) {}
     }
 
     let selectionInfo = BrowserUtils.getSelectionDetails(this.content);
     let loadContext = this.global.docShell.QueryInterface(Ci.nsILoadContext);
     let userContextId = loadContext.originAttributes.userContextId;
-    let popupNodeSelectors = this._getNodeSelectors(aEvent.target);
+    let popupNodeSelectors = this._getNodeSelectors(aEvent.composedTarget);
 
     this._setContext(aEvent);
     let context = this.context;
     this.target = context.target;
 
     let spellInfo = null;
     let editFlags = null;
     let principal = null;
@@ -576,30 +577,30 @@ class ContextMenu {
     let targetAsCPOW = context.target;
     if (targetAsCPOW) {
       this._cleanContext();
     }
 
     let isRemote = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
 
     if (isRemote) {
-      editFlags = SpellCheckHelper.isEditable(aEvent.target, this.content);
+      editFlags = SpellCheckHelper.isEditable(aEvent.composedTarget, this.content);
 
       if (editFlags & SpellCheckHelper.SPELLCHECKABLE) {
         spellInfo = InlineSpellCheckerContent.initContextMenu(aEvent, editFlags, this.global);
       }
 
       // Set the event target first as the copy image command needs it to
       // determine what was context-clicked on. Then, update the state of the
       // commands on the context menu.
       this.global.docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
-                          .setCommandNode(aEvent.target);
-      aEvent.target.ownerGlobal.updateCommands("contentcontextmenu");
+                          .setCommandNode(aEvent.composedTarget);
+      aEvent.composedTarget.ownerGlobal.updateCommands("contentcontextmenu");
 
-      customMenuItems = PageMenuChild.build(aEvent.target);
+      customMenuItems = PageMenuChild.build(aEvent.composedTarget);
       principal = doc.nodePrincipal;
     }
 
     let data = {
       context,
       charSet,
       baseURI,
       isRemote,
@@ -702,17 +703,17 @@ class ContextMenu {
   _setContext(aEvent) {
     this.context = Object.create(null);
     const context = this.context;
 
     context.screenX = aEvent.screenX;
     context.screenY = aEvent.screenY;
     context.mozInputSource = aEvent.mozInputSource;
 
-    const node = aEvent.target;
+    const node = aEvent.composedTarget;
 
     const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
     context.shouldDisplay = true;
 
     if (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ||
         // Don't display for XUL element unless <label class="text-link">
         (node.namespaceURI == XUL_NS && !this._isXULTextLinkLabel(node))) {
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -291,17 +291,19 @@ Inspector.prototype = {
       this.selection.setNodeFront(defaultSelection, { reason: "inspector-open" });
       await onAllPanelsUpdated;
       await this.markup.expandNode(this.selection.nodeFront);
     }
 
     // Setup the toolbar only now because it may depend on the document.
     await this.setupToolbar();
 
-    if (this.show3PaneTooltip) {
+    // Show the 3 pane onboarding tooltip only if the inspector is visisble since the
+    // Accessibility panel initializes the Inspector.
+    if (this.show3PaneTooltip && this.toolbox.currentToolId === "inspector") {
       this.threePaneTooltip = new ThreePaneOnboardingTooltip(this.toolbox, this.panelDoc);
     }
 
     // Log the 3 pane inspector setting on inspector open. The question we want to answer
     // is:
     // "What proportion of users use the 3 pane vs 2 pane inspector on inspector open?"
     this.telemetry.logKeyedScalar(THREE_PANE_ENABLED_SCALAR, this.is3PaneModeEnabled, 1);
 
--- a/dom/animation/AnimationTimeline.cpp
+++ b/dom/animation/AnimationTimeline.cpp
@@ -43,16 +43,16 @@ AnimationTimeline::NotifyAnimationUpdate
     mAnimationOrder.insertBack(&aAnimation);
   }
 }
 
 void
 AnimationTimeline::RemoveAnimation(Animation* aAnimation)
 {
   MOZ_ASSERT(!aAnimation->GetTimeline() || aAnimation->GetTimeline() == this);
-  if (aAnimation->isInList()) {
-    aAnimation->remove();
+  if (static_cast<LinkedListElement<Animation>*>(aAnimation)->isInList()) {
+    static_cast<LinkedListElement<Animation>*>(aAnimation)->remove();
   }
   mAnimations.RemoveEntry(aAnimation);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/DocumentTimeline.cpp
+++ b/dom/animation/DocumentTimeline.cpp
@@ -162,17 +162,18 @@ DocumentTimeline::WillRefresh(mozilla::T
   // Note that this should be done before nsAutoAnimationMutationBatch.  If
   // PerformMicroTaskCheckpoint was called before nsAutoAnimationMutationBatch
   // is destroyed, some mutation records might not be delivered in this
   // checkpoint.
   nsAutoMicroTask mt;
   nsAutoAnimationMutationBatch mb(mDocument);
 
   for (Animation* animation = mAnimationOrder.getFirst(); animation;
-       animation = animation->getNext()) {
+       animation =
+         static_cast<LinkedListElement<Animation>*>(animation)->getNext()) {
     // Skip any animations that are longer need associated with this timeline.
     if (animation->GetTimeline() != this) {
       // If animation has some other timeline, it better not be also in the
       // animation list of this timeline object!
       MOZ_ASSERT(!animation->GetTimeline());
       animationsToRemove.AppendElement(animation);
       continue;
     }
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1954,17 +1954,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     }
   }
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader)
 
   // We own only the items in mDOMMediaQueryLists that have listeners;
   // this reference is managed by their AddListener and RemoveListener
   // methods.
-  for (auto mql : tmp->mDOMMediaQueryLists) {
+  for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;
+       mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
     if (mql->HasListeners()) {
       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
       cb.NoteXPCOMChild(mql);
     }
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocument)
@@ -2074,17 +2075,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
     tmp->mCSSLoader->DropDocumentReference();
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader)
   }
 
   // We own only the items in mDOMMediaQueryLists that have listeners;
   // this reference is managed by their AddListener and RemoveListener
   // methods.
   for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
-    MediaQueryList* next = mql->getNext();
+    MediaQueryList* next =
+      static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext();
     mql->Disconnect();
     mql = next;
   }
 
   tmp->mInUnlinkOrDeletion = false;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 nsresult
--- a/dom/base/nsIGlobalObject.cpp
+++ b/dom/base/nsIGlobalObject.cpp
@@ -20,17 +20,17 @@ using mozilla::dom::ServiceWorker;
 using mozilla::dom::ServiceWorkerDescriptor;
 using mozilla::dom::ServiceWorkerRegistration;
 using mozilla::dom::ServiceWorkerRegistrationDescriptor;
 
 nsIGlobalObject::~nsIGlobalObject()
 {
   UnlinkHostObjectURIs();
   DisconnectEventTargetObjects();
-  MOZ_DIAGNOSTIC_ASSERT(mEventTargetObjects.IsEmpty());
+  MOZ_DIAGNOSTIC_ASSERT(mEventTargetObjects.isEmpty());
 }
 
 nsIPrincipal*
 nsIGlobalObject::PrincipalOrNull()
 {
   JSObject *global = GetGlobalJSObject();
   if (NS_WARN_IF(!global))
     return nullptr;
@@ -126,45 +126,47 @@ nsIGlobalObject::TraverseHostObjectURIs(
     nsHostObjectProtocolHandler::Traverse(mHostObjectURIs[index], aCb);
   }
 }
 
 void
 nsIGlobalObject::AddEventTargetObject(DOMEventTargetHelper* aObject)
 {
   MOZ_DIAGNOSTIC_ASSERT(aObject);
-  MOZ_ASSERT(!mEventTargetObjects.Contains(aObject));
-  mEventTargetObjects.PutEntry(aObject);
+  MOZ_ASSERT(!aObject->isInList());
+  mEventTargetObjects.insertBack(aObject);
 }
 
 void
 nsIGlobalObject::RemoveEventTargetObject(DOMEventTargetHelper* aObject)
 {
   MOZ_DIAGNOSTIC_ASSERT(aObject);
-  MOZ_ASSERT(mEventTargetObjects.Contains(aObject));
-  mEventTargetObjects.RemoveEntry(aObject);
+  MOZ_ASSERT(aObject->isInList());
+  MOZ_ASSERT(aObject->GetOwnerGlobal() == this);
+  aObject->remove();
 }
 
 void
 nsIGlobalObject::ForEachEventTargetObject(const std::function<void(DOMEventTargetHelper*, bool* aDoneOut)>& aFunc) const
 {
-  // Protect against the function call triggering a mutation of the hash table
+  // Protect against the function call triggering a mutation of the list
   // while we are iterating by copying the DETH references to a temporary
   // list.
   AutoTArray<DOMEventTargetHelper*, 64> targetList;
-  for (auto iter = mEventTargetObjects.ConstIter(); !iter.Done(); iter.Next()) {
-    targetList.AppendElement(iter.Get()->GetKey());
+  for (const DOMEventTargetHelper* deth = mEventTargetObjects.getFirst();
+       deth; deth = deth->getNext()) {
+    targetList.AppendElement(const_cast<DOMEventTargetHelper*>(deth));
   }
 
   // Iterate the target list and call the function on each one.
   bool done = false;
   for (auto target : targetList) {
     // Check to see if a previous iteration's callback triggered the removal
     // of this target as a side-effect.  If it did, then just ignore it.
-    if (!mEventTargetObjects.Contains(target)) {
+    if (target->GetOwnerGlobal() != this) {
       continue;
     }
     aFunc(target, &done);
     if (done) {
       break;
     }
   }
 }
@@ -172,17 +174,17 @@ nsIGlobalObject::ForEachEventTargetObjec
 void
 nsIGlobalObject::DisconnectEventTargetObjects()
 {
   ForEachEventTargetObject([&] (DOMEventTargetHelper* aTarget, bool* aDoneOut) {
     aTarget->DisconnectFromOwner();
 
     // Calling DisconnectFromOwner() should result in
     // RemoveEventTargetObject() being called.
-    MOZ_DIAGNOSTIC_ASSERT(!mEventTargetObjects.Contains(aTarget));
+    MOZ_DIAGNOSTIC_ASSERT(aTarget->GetOwnerGlobal() != this);
   });
 }
 
 Maybe<ClientInfo>
 nsIGlobalObject::GetClientInfo() const
 {
   // By default globals do not expose themselves as a client.  Only real
   // window and worker globals are currently considered clients.
@@ -210,11 +212,10 @@ nsIGlobalObject::GetOrCreateServiceWorke
   MOZ_DIAGNOSTIC_ASSERT(false, "this global should not have any service worker registrations");
   return nullptr;
 }
 
 size_t
 nsIGlobalObject::ShallowSizeOfExcludingThis(MallocSizeOf aSizeOf) const
 {
   size_t rtn = mHostObjectURIs.ShallowSizeOfExcludingThis(aSizeOf);
-  rtn += mEventTargetObjects.ShallowSizeOfExcludingThis(aSizeOf);
   return rtn;
 }
--- a/dom/base/nsIGlobalObject.h
+++ b/dom/base/nsIGlobalObject.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsIGlobalObject_h__
 #define nsIGlobalObject_h__
 
+#include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/dom/ClientInfo.h"
 #include "mozilla/dom/DispatcherTrait.h"
 #include "mozilla/dom/ServiceWorkerDescriptor.h"
 #include "nsHashKeys.h"
 #include "nsISupports.h"
 #include "nsStringFwd.h"
 #include "nsTArray.h"
@@ -36,20 +37,18 @@ class ServiceWorkerRegistrationDescripto
 } // namespace mozilla
 
 class nsIGlobalObject : public nsISupports,
                         public mozilla::dom::DispatcherTrait
 {
   nsTArray<nsCString> mHostObjectURIs;
 
   // Raw pointers to bound DETH objects.  These are added by
-  // AddEventTargetObject().  The DETH object must call
-  // RemoveEventTargetObject() before its destroyed to clear
-  // its raw pointer here.
-  nsTHashtable<nsPtrHashKey<mozilla::DOMEventTargetHelper>> mEventTargetObjects;
+  // AddEventTargetObject().
+  mozilla::LinkedList<mozilla::DOMEventTargetHelper> mEventTargetObjects;
 
   bool mIsDying;
 
 protected:
   nsIGlobalObject()
    : mIsDying(false)
   {}
 
--- a/dom/events/DOMEventTargetHelper.h
+++ b/dom/events/DOMEventTargetHelper.h
@@ -12,16 +12,17 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsPIDOMWindow.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
 #include "nsIWeakReferenceUtils.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventListenerManager.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/dom/EventTarget.h"
 
 struct JSCompartment;
 class nsIDocument;
 
 namespace mozilla {
 
 class ErrorResult;
@@ -29,17 +30,18 @@ class ErrorResult;
 namespace dom {
 class Event;
 } // namespace dom
 
 #define NS_DOMEVENTTARGETHELPER_IID \
 { 0xa28385c6, 0x9451, 0x4d7e, \
   { 0xa3, 0xdd, 0xf4, 0xb6, 0x87, 0x2f, 0xa4, 0x76 } }
 
-class DOMEventTargetHelper : public dom::EventTarget
+class DOMEventTargetHelper : public dom::EventTarget,
+                             public LinkedListElement<DOMEventTargetHelper>
 {
 public:
   DOMEventTargetHelper()
     : mParentObject(nullptr)
     , mOwnerWindow(nullptr)
     , mHasOrHasHadOwnerWindow(false)
     , mIsKeptAlive(false)
   {
@@ -167,17 +169,17 @@ public:
   //       DOMEventTargetHelper::BindToOwner(aOwner).
   virtual void BindToOwner(nsIGlobalObject* aOwner);
 
   void BindToOwner(nsPIDOMWindowInner* aOwner);
   void BindToOwner(DOMEventTargetHelper* aOther);
 
   virtual void DisconnectFromOwner();
   using EventTarget::GetParentObject;
-  virtual nsIGlobalObject* GetOwnerGlobal() const override
+  nsIGlobalObject* GetOwnerGlobal() const final
   {
     return mParentObject;
   }
   bool HasOrHasHadOwner() { return mHasOrHasHadOwnerWindow; }
 
   virtual void EventListenerAdded(nsAtom* aType) override;
   virtual void EventListenerAdded(const nsAString& aType) override;
 
--- a/gfx/thebes/gfxFontEntry.cpp
+++ b/gfx/thebes/gfxFontEntry.cpp
@@ -27,16 +27,17 @@
 #include "nsUnicodeRange.h"
 #include "nsStyleConsts.h"
 #include "mozilla/AppUnits.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
+#include "mozilla/StaticPrefs.h"
 #include "mozilla/Telemetry.h"
 #include "gfxSVGGlyphs.h"
 #include "gfx2DGlue.h"
 
 #include "harfbuzz/hb.h"
 #include "harfbuzz/hb-ot.h"
 #include "graphite2/Font.h"
 
@@ -1019,44 +1020,50 @@ gfxFontEntry::SetupVariationRanges()
         return;
     }
     AutoTArray<gfxFontVariationAxis,4> axes;
     GetVariationAxes(axes);
     for (const auto& axis : axes) {
         switch (axis.mTag) {
         case HB_TAG('w','g','h','t'):
             // If the axis range looks like it doesn't fit the CSS font-weight
-            // scale, we don't hook up the high-level property. Setting 'wght'
+            // scale, we don't hook up the high-level property, and we mark
+            // the face (in mRangeFlags) as having non-standard weight. This
+            // means we won't map CSS font-weight to the axis. Setting 'wght'
             // with font-variation-settings will still work.
             // Strictly speaking, the min value should be checked against 1.0,
             // not 0.0, but we'll allow font makers that amount of leeway, as
             // in practice a number of fonts seem to use 0..1000.
             if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0f &&
                 // If axis.mMaxValue is less than the default weight we already
                 // set up, assume the axis has a non-standard range (like Skia)
                 // and don't try to map it.
                 Weight().Min() <= FontWeight(axis.mMaxValue)) {
                 if (FontWeight(axis.mDefaultValue) != Weight().Min()) {
                     mStandardFace = false;
                 }
                 mWeightRange =
                     WeightRange(FontWeight(std::max(1.0f, axis.mMinValue)),
                                 FontWeight(axis.mMaxValue));
+            } else {
+                mRangeFlags |= RangeFlags::eNonCSSWeight;
             }
             break;
 
         case HB_TAG('w','d','t','h'):
             if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0f &&
                 Stretch().Min() <= FontStretch(axis.mMaxValue)) {
                 if (FontStretch(axis.mDefaultValue) != Stretch().Min()) {
                     mStandardFace = false;
                 }
                 mStretchRange =
                     StretchRange(FontStretch(axis.mMinValue),
                                  FontStretch(axis.mMaxValue));
+            } else {
+                mRangeFlags |= RangeFlags::eNonCSSStretch;
             }
             break;
 
         case HB_TAG('s','l','n','t'):
             if (axis.mMinValue >= -90.0f && axis.mMaxValue <= 90.0f) {
                 if (FontSlantStyle::Oblique(axis.mDefaultValue) != SlantStyle().Min()) {
                     mStandardFace = false;
                 }
@@ -1101,36 +1108,49 @@ gfxFontEntry::HasBoldVariableWeight()
     return (mRangeFlags & RangeFlags::eBoldVariableWeight) ==
            RangeFlags::eBoldVariableWeight;
 }
 
 void
 gfxFontEntry::GetVariationsForStyle(nsTArray<gfxFontVariation>& aResult,
                                     const gfxFontStyle& aStyle)
 {
-    if (!gfxPlatform::GetPlatform()->HasVariationFontSupport()) {
+    if (!gfxPlatform::GetPlatform()->HasVariationFontSupport() ||
+        !StaticPrefs::layout_css_font_variations_enabled()) {
         return;
     }
 
     // Resolve high-level CSS properties from the requested style
     // (font-{style,weight,stretch}) to the appropriate variations.
     // The value used is clamped to the range available in the font face,
     // unless the face is a user font where no explicit descriptor was
     // given, indicated by the corresponding 'auto' range-flag.
-    float weight = (IsUserFont() && (mRangeFlags & RangeFlags::eAutoWeight))
-                   ? aStyle.weight.ToFloat()
-                   : Weight().Clamp(aStyle.weight).ToFloat();
-    aResult.AppendElement(gfxFontVariation{HB_TAG('w','g','h','t'),
-                                           weight});
+
+    // We don't do these mappings if the font entry has weight and/or stretch
+    // ranges that do not appear to use the CSS property scale. Some older
+    // fonts created for QuickDrawGX/AAT may use "normalized" values where the
+    // standard variation is 1.0 rather than 400.0 (weight) or 100.0 (stretch).
 
-    float stretch = (IsUserFont() && (mRangeFlags & RangeFlags::eAutoStretch))
-                    ? aStyle.stretch.Percentage()
-                    : Stretch().Clamp(aStyle.stretch).Percentage();
-    aResult.AppendElement(gfxFontVariation{HB_TAG('w','d','t','h'),
-                                           stretch});
+    if (!(mRangeFlags & RangeFlags::eNonCSSWeight)) {
+        float weight =
+            (IsUserFont() && (mRangeFlags & RangeFlags::eAutoWeight))
+                ? aStyle.weight.ToFloat()
+                : Weight().Clamp(aStyle.weight).ToFloat();
+        aResult.AppendElement(gfxFontVariation{HB_TAG('w','g','h','t'),
+                                               weight});
+    }
+
+    if (!(mRangeFlags & RangeFlags::eNonCSSStretch)) {
+        float stretch =
+            (IsUserFont() && (mRangeFlags & RangeFlags::eAutoStretch))
+                ? aStyle.stretch.Percentage()
+                : Stretch().Clamp(aStyle.stretch).Percentage();
+        aResult.AppendElement(gfxFontVariation{HB_TAG('w','d','t','h'),
+                                               stretch});
+    }
 
     if (SlantStyle().Min().IsOblique()) {
         // Figure out what slant angle we should try to match from the
         // requested style.
         float angle =
             aStyle.style.IsNormal()
                 ? 0.0f
                 : aStyle.style.IsItalic()
--- a/gfx/thebes/gfxFontEntry.h
+++ b/gfx/thebes/gfxFontEntry.h
@@ -434,17 +434,23 @@ public:
     enum class RangeFlags : uint8_t {
         eNoFlags        = 0,
         eAutoWeight     = (1 << 0),
         eAutoStretch    = (1 << 1),
         eAutoSlantStyle = (1 << 2),
         // Flag to record whether the face has a variable "wght" axis
         // that supports "bold" values, used to disable the application
         // of synthetic-bold effects.
-        eBoldVariableWeight = (1 << 3)
+        eBoldVariableWeight = (1 << 3),
+        // Flags to record if the face uses a non-CSS-compatible scale
+        // for weight and/or stretch, in which case we won't map the
+        // properties to the variation axes (though they can still be
+        // explicitly set using font-variation-settings).
+        eNonCSSWeight   = (1 << 4),
+        eNonCSSStretch  = (1 << 5)
     };
     RangeFlags       mRangeFlags = RangeFlags::eNoFlags;
 
     // NOTE that there are currently exactly 24 one-bit flags defined here,
     // so together with the 8-bit RangeFlags above, this packs neatly to a
     // 32-bit boundary. Worth considering if further flags are wanted.
     bool             mFixedPitch  : 1;
     bool             mIsBadUnderlineFont : 1;
--- a/ipc/mscom/Interceptor.cpp
+++ b/ipc/mscom/Interceptor.cpp
@@ -580,31 +580,40 @@ Interceptor::GetInitialInterceptorForIID
   ENSURE_HR_SUCCEEDED(hr);
 
   if (MarshalAs(aTargetIid) == aTargetIid) {
     hr = unkInterceptor->QueryInterface(aTargetIid, aOutInterceptor);
     ENSURE_HR_SUCCEEDED(hr);
     return hr;
   }
 
-  hr = GetInterceptorForIID(aTargetIid, aOutInterceptor);
+  hr = GetInterceptorForIID(aTargetIid, aOutInterceptor, &lock);
   ENSURE_HR_SUCCEEDED(hr);
   return hr;
 }
 
+HRESULT
+Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor)
+{
+  return GetInterceptorForIID(aIid, aOutInterceptor, nullptr);
+}
+
 /**
  * This method contains the core guts of the handling of QueryInterface calls
  * that are delegated to us from the ICallInterceptor.
  *
  * @param aIid ID of the desired interface
  * @param aOutInterceptor The resulting emulated vtable that corresponds to
  * the interface specified by aIid.
+ * @param aAlreadyLocked Proof of an existing lock on |mInterceptorMapMutex|,
+ *                       if present.
  */
 HRESULT
-Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor)
+Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor,
+                                  MutexAutoLock* aAlreadyLocked)
 {
   detail::LoggedQIResult result(aIid);
 
   if (!aOutInterceptor) {
     return E_INVALIDARG;
   }
 
   if (aIid == IID_IUnknown) {
@@ -616,24 +625,29 @@ Interceptor::GetInterceptorForIID(REFIID
 
   REFIID interceptorIid = MarshalAs(aIid);
 
   RefPtr<IUnknown> unkInterceptor;
   IUnknown* interfaceForQILog = nullptr;
 
   // (1) Check to see if we already have an existing interceptor for
   // interceptorIid.
-
-  { // Scope for lock
-    MutexAutoLock lock(mInterceptorMapMutex);
+  auto doLookup = [&]() -> void {
     MapEntry* entry = Lookup(interceptorIid);
     if (entry) {
       unkInterceptor = entry->mInterceptor;
       interfaceForQILog = entry->mTargetInterface;
     }
+  };
+
+  if (aAlreadyLocked) {
+    doLookup();
+  } else {
+    MutexAutoLock lock(mInterceptorMapMutex);
+    doLookup();
   }
 
   // (1a) A COM interceptor already exists for this interface, so all we need
   // to do is run a QI on it.
   if (unkInterceptor) {
     // Technically we didn't actually execute a QI on the target interface, but
     // for logging purposes we would like to record the fact that this interface
     // was requested.
@@ -683,19 +697,17 @@ Interceptor::GetInterceptorForIID(REFIID
   hr = unkInterceptor->QueryInterface(IID_ICallInterceptor,
                                       (void**)getter_AddRefs(interceptor));
   ENSURE_HR_SUCCEEDED(hr);
 
   hr = interceptor->RegisterSink(mEventSink);
   ENSURE_HR_SUCCEEDED(hr);
 
   // (5) Now that we have this new COM interceptor, insert it into the map.
-
-  { // Scope for lock
-    MutexAutoLock lock(mInterceptorMapMutex);
+  auto doInsertion = [&]() -> void {
     // We might have raced with another thread, so first check that we don't
     // already have an entry for this
     MapEntry* entry = Lookup(interceptorIid);
     if (entry && entry->mInterceptor) {
       // Bug 1433046: Because of aggregation, the QI for |interceptor|
       // AddRefed |this|, not |unkInterceptor|. Thus, releasing |unkInterceptor|
       // will destroy the object. Before we do that, we must first release
       // |interceptor|. Otherwise, |interceptor| would be invalidated when
@@ -706,16 +718,23 @@ Interceptor::GetInterceptorForIID(REFIID
       // MapEntry has a RefPtr to unkInterceptor, OTOH we must not touch the
       // refcount for the target interface because we are just moving it into
       // the map and its refcounting might not be thread-safe.
       IUnknown* rawTargetInterface = targetInterface.release();
       mInterceptorMap.AppendElement(MapEntry(interceptorIid,
                                              unkInterceptor,
                                              rawTargetInterface));
     }
+  };
+
+  if (aAlreadyLocked) {
+    doInsertion();
+  } else {
+    MutexAutoLock lock(mInterceptorMapMutex);
+    doInsertion();
   }
 
   hr = unkInterceptor->QueryInterface(interceptorIid, aOutInterceptor);
   ENSURE_HR_SUCCEEDED(hr);
   return hr;
 }
 
 HRESULT
@@ -830,17 +849,17 @@ Interceptor::WeakRefQueryInterface(REFII
     IDispatch* rawDisp = nullptr;
     HRESULT hr = QueryInterfaceTarget(aIid, (void**)&rawDisp);
     ENSURE_HR_SUCCEEDED(hr);
 
     disp.reset(rawDisp);
     return DispatchForwarder::Create(this, disp, aOutInterface);
   }
 
-  return GetInterceptorForIID(aIid, (void**)aOutInterface);
+  return GetInterceptorForIID(aIid, (void**)aOutInterface, nullptr);
 }
 
 ULONG
 Interceptor::AddRef()
 {
   return WeakReferenceSupport::AddRef();
 }
 
--- a/ipc/mscom/Interceptor.h
+++ b/ipc/mscom/Interceptor.h
@@ -139,16 +139,18 @@ private:
 
 private:
   explicit Interceptor(IInterceptorSink* aSink);
   ~Interceptor();
   HRESULT GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLiveSetLock,
                                       REFIID aTargetIid,
                                       STAUniquePtr<IUnknown> aTarget,
                                       void** aOutInterface);
+  HRESULT GetInterceptorForIID(REFIID aIid, void** aOutInterceptor,
+                               MutexAutoLock* aAlreadyLocked);
   MapEntry* Lookup(REFIID aIid);
   HRESULT QueryInterfaceTarget(REFIID aIid, void** aOutput,
                                TimeDuration* aOutDuration = nullptr);
   HRESULT WeakRefQueryInterface(REFIID aIid,
                                 IUnknown** aOutInterface) override;
   HRESULT CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput);
   REFIID MarshalAs(REFIID aIid) const;
   HRESULT PublishTarget(detail::LiveSetAutoLock& aLiveSetLock,
--- a/js/src/devtools/automation/autospider.py
+++ b/js/src/devtools/automation/autospider.py
@@ -496,17 +496,17 @@ if 'jstests' in test_suites:
     results.append(run_test_command([MAKE, 'check-jstests']))
 
 # FIXME bug 1291449: This would be unnecessary if we could run msan with -mllvm
 # -msan-keep-going, but in clang 3.8 it causes a hang during compilation.
 if variant.get('ignore-test-failures'):
     logging.warning("Ignoring test results %s" % (results,))
     results = [0]
 
-if args.variant in ('tsan', 'msan'):
+if args.variant == 'msan':
     files = filter(lambda f: f.startswith("sanitize_log."), os.listdir(OUTDIR))
     fullfiles = [os.path.join(OUTDIR, f) for f in files]
 
     # Summarize results
     sites = Counter()
     errors = Counter()
     for filename in fullfiles:
         with open(os.path.join(OUTDIR, filename), 'rb') as fh:
@@ -528,53 +528,16 @@ if args.variant in ('tsan', 'msan'):
     print(open(summary_filename, 'rb').read())
 
     if 'max-errors' in variant:
         max_allowed = variant['max-errors']
         print("Found %d errors out of %d allowed" % (len(sites), max_allowed))
         if len(sites) > max_allowed:
             results.append(1)
 
-    if 'expect-errors' in variant:
-        # Line numbers may shift around between versions, so just look for
-        # matching filenames and function names. This will still produce false
-        # positives when functions are renamed or moved between files, or
-        # things change so that the actual race is in a different place. But it
-        # still seems preferable to saying "You introduced an additional race.
-        # Here are the 21 races detected; please ignore the 20 known ones in
-        # this other list."
-
-        for site in sites:
-            # Grab out the file and function names.
-            m = re.search(r'/([^/]+):\d+ in (.+)', site)
-            if m:
-                error = tuple(m.groups())
-            else:
-                # will get here if eg tsan symbolication fails
-                error = (site, '(unknown)')
-            errors[error] += 1
-
-        remaining = Counter(errors)
-        for expect in variant['expect-errors']:
-            # expect-errors is an array of (filename, function) tuples.
-            expect = tuple(expect)
-            if remaining[expect] == 0:
-                print("Did not see known error in %s function %s" % expect)
-            else:
-                remaining[expect] -= 1
-
-        status = 0
-        for filename, function in (e for e, c in remaining.items() if c > 0):
-            if AUTOMATION:
-                print("TinderboxPrint: tsan error<br/>%s function %s" % (filename, function))
-                status = 1
-            else:
-                print("*** tsan error in %s function %s" % (filename, function))
-        results.append(status)
-
     # Gather individual results into a tarball. Note that these are
     # distinguished only by pid of the JS process running within each test, so
     # given the 16-bit limitation of pids, it's totally possible that some of
     # these files will be lost due to being overwritten.
     command = ['tar', '-C', OUTDIR, '-zcf',
                os.path.join(env['MOZ_UPLOAD_DIR'], '%s.tar.gz' % args.variant)]
     command += files
     subprocess.call(command)
--- a/js/src/devtools/automation/variants/tsan
+++ b/js/src/devtools/automation/variants/tsan
@@ -1,44 +1,12 @@
 {
     "configure-args": "--enable-debug-symbols='-gline-tables-only' --disable-jemalloc --enable-thread-sanitizer",
     "optimize": true,
     "debug": false,
     "compiler": "clang",
     "env": {
         "LLVM_SYMBOLIZER": "{TOOLTOOL_CHECKOUT}/clang/bin/llvm-symbolizer",
         "JITTEST_EXTRA_ARGS": "--jitflags=tsan --ignore-timeouts={DIR}/cgc-jittest-timeouts.txt --unusable-error-status --exclude-from={DIR}/tsan-sighandlers.txt",
-        "JSTESTS_EXTRA_ARGS": "--exclude-file={DIR}/cgc-jstests-slow.txt",
-        "TSAN_OPTIONS": "exitcode=0 log_path={OUTDIR}/sanitize_log"
+        "JSTESTS_EXTRA_ARGS": "--exclude-file={DIR}/cgc-jstests-slow.txt"
     },
-    "use_minidump": false,
-    "[comment on expect-errors]": "Note that expect-errors may contain duplicates. These indicate that tsan reports errors as two distinct line numbers in the same function. (Line number shift around, so we cannot just include them here.)",
-    "expect-errors": [
-        [ "Shape.h", "inDictionary" ],
-        [ "Shape.h", "maybeSlot" ],
-        [ "Shape.h", "slotSpan" ],
-        [ "Shape.h", "setSlotWithType" ],
-        [ "Shape.h", "search<js::MaybeAdding::NotAdding>" ],
-        [ "Shape.h", "setOverwritten" ],
-        [ "Shape.h", "incrementNumLinearSearches" ],
-        [ "Shape.h", "isBigEnoughForAShapeTable" ],
-        [ "Shape.h", "js::jit::CodeGenerator::emitGetPropertyPolymorphic(js::jit::LInstruction*, js::jit::Register, js::jit::Register, js::jit::TypedOrValueRegister const&)" ],
-        [ "ProtectedData.h", "operator=<js::CooperatingContext>" ],
-        [ "ProtectedData.h", "js::ZoneGroup::leave()" ],
-        [ "jsfriendapi.h", "GetObjectClass" ],
-        [ "jsfriendapi.h", "numFixedSlots" ],
-        [ "jsfriendapi.h", "numDynamicSlots" ],
-        [ "jsfriendapi.h", "EmulatesUndefined" ],
-        [ "jitprofiling.c", "iJIT_GetNewMethodID" ],
-        [ "Marking.cpp", "js::GCMarker::reset()" ],
-        [ "Statistics.h", "js::gc::GCRuntime::pickChunk(js::AutoLockGC const&, js::gc::AutoMaybeStartBackgroundAllocation&)" ],
-        [ "Statistics.h", "js::gc::GCRuntime::getOrAllocChunk(js::AutoLockGC const&, js::gc::AutoMaybeStartBackgroundAllocation&)" ],
-        [ "Barrier.h", "set" ],
-        [ "Stack.h", "context" ],
-        [ "Stack.h", "js::HelperThread::handleIonWorkload(js::AutoLockHelperThreadState&)" ],
-        [ "ObjectGroup.h", "addendumKind" ],
-        [ "ObjectGroup.h", "generation" ],
-        [ "ObjectGroup.h", "js::jit::MObjectState::New(js::jit::TempAllocator&, js::jit::MDefinition*)" ],
-        [ "TypeInference-inl.h", "setBasePropertyCount" ],
-        [ "jsfun.h", "setResolvedLength" ],
-        [ "jsfun.h", "needsSomeEnvironmentObject" ]
-    ]
+    "use_minidump": false
 }
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -59,34 +59,43 @@ struct DoCallbackFunctor : public Identi
         return js::gc::RewrapTaggedPointer<S, T>::wrap(DoCallback(trc, &t, name));
     }
 };
 
 template <>
 Value
 DoCallback<Value>(JS::CallbackTracer* trc, Value* vp, const char* name)
 {
-    *vp = DispatchTyped(DoCallbackFunctor<Value>(), *vp, trc, name);
-    return *vp;
+    // Only update *vp if the value changed, to avoid TSan false positives for
+    // template objects when using DumpHeapTracer or UbiNode tracers while Ion
+    // compiling off-thread.
+    Value v = DispatchTyped(DoCallbackFunctor<Value>(), *vp, trc, name);
+    if (*vp != v)
+        *vp = v;
+    return v;
 }
 
 template <>
 jsid
 DoCallback<jsid>(JS::CallbackTracer* trc, jsid* idp, const char* name)
 {
-    *idp = DispatchTyped(DoCallbackFunctor<jsid>(), *idp, trc, name);
-    return *idp;
+    jsid id = DispatchTyped(DoCallbackFunctor<jsid>(), *idp, trc, name);
+    if (*idp != id)
+        *idp = id;
+    return id;
 }
 
 template <>
 TaggedProto
 DoCallback<TaggedProto>(JS::CallbackTracer* trc, TaggedProto* protop, const char* name)
 {
-    *protop = DispatchTyped(DoCallbackFunctor<TaggedProto>(), *protop, trc, name);
-    return *protop;
+    TaggedProto proto = DispatchTyped(DoCallbackFunctor<TaggedProto>(), *protop, trc, name);
+    if (*protop != proto)
+        *protop = proto;
+    return proto;
 }
 
 void
 JS::CallbackTracer::getTracingEdgeName(char* buffer, size_t bufferSize)
 {
     MOZ_ASSERT(bufferSize > 0);
     if (contextFunctor_) {
         (*contextFunctor_)(this, buffer, bufferSize);
--- a/js/src/jit-test/lib/jitopts.js
+++ b/js/src/jit-test/lib/jitopts.js
@@ -33,16 +33,17 @@ function withJitOptions(opts, fn) {
 
 // N.B. Ion opts *must come before* baseline opts because there's some kind of
 // "undo eager compilation" logic. If we don't set the baseline warmup-counter
 // *after* the Ion warmup-counter we end up setting the baseline warmup-counter
 // to be the default if we hit the "undo eager compilation" logic.
 var Opts_BaselineEager =
     {
       'ion.enable': 1,
+      'ion.warmup.trigger': 100,
       'baseline.enable': 1,
       'baseline.warmup.trigger': 0,
       'offthread-compilation.enable': 1
     };
 
 // Checking for offthread compilation being off is often helpful if the test
 // requires a function be Ion compiled. Each individual test will usually
 // finish before the Ion compilation thread has a chance to attach the
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -53,16 +53,17 @@
 #include "vm/TraceLogging.h"
 #include "vm/TypedArrayObject.h"
 #include "vtune/VTuneWrapper.h"
 
 #include "builtin/Boolean-inl.h"
 #include "jit/MacroAssembler-inl.h"
 #include "jit/shared/CodeGenerator-shared-inl.h"
 #include "jit/shared/Lowering-shared-inl.h"
+#include "jit/TemplateObject-inl.h"
 #include "vm/Interpreter-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::AssertedCast;
 using mozilla::DebugOnly;
 using mozilla::FloatingPoint;
@@ -1366,24 +1367,26 @@ typedef JSObject* (*CloneRegExpObjectFn)
 static const VMFunction CloneRegExpObjectInfo =
     FunctionInfo<CloneRegExpObjectFn>(CloneRegExpObject, "CloneRegExpObject");
 
 void
 CodeGenerator::visitRegExp(LRegExp* lir)
 {
     Register output = ToRegister(lir->output());
     Register temp = ToRegister(lir->temp());
-    JSObject* templateObject = lir->mir()->source();
-
-    OutOfLineCode *ool = oolCallVM(CloneRegExpObjectInfo, lir, ArgList(ImmGCPtr(lir->mir()->source())),
+    JSObject* source = lir->mir()->source();
+
+    OutOfLineCode* ool = oolCallVM(CloneRegExpObjectInfo, lir, ArgList(ImmGCPtr(source)),
                                    StoreRegisterTo(output));
-    if (lir->mir()->hasShared())
+    if (lir->mir()->hasShared()) {
+        TemplateObject templateObject(source);
         masm.createGCObject(output, temp, templateObject, gc::DefaultHeap, ool->entry());
-    else
+    } else {
         masm.jump(ool->entry());
+    }
     masm.bind(ool->rejoin());
 }
 
 // Amount of space to reserve on the stack when executing RegExps inline.
 static const size_t RegExpReservedStack = sizeof(irregexp::InputOutputData)
                                         + sizeof(MatchPairs)
                                         + RegExpObject::MaxPairCount * sizeof(MatchPair);
 
@@ -1882,17 +1885,18 @@ CreateMatchResultFallback(MacroAssembler
     masm.passABIArg(temp5);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, CreateMatchResultFallbackFunc));
     masm.storeCallPointerResult(object);
 
     masm.PopRegsInMask(regsToSave);
 
     masm.branchPtr(Assembler::Equal, object, ImmWord(0), fail);
 
-    masm.initGCThing(object, temp2, templateObj, true, false);
+    TemplateObject templateObject(templateObj);
+    masm.initGCThing(object, temp2, templateObject, true);
 }
 
 JitCode*
 JitCompartment::generateRegExpMatcherStub(JSContext* cx)
 {
     Register regexp = RegExpMatcherRegExpReg;
     Register input = RegExpMatcherStringReg;
     Register lastIndex = RegExpMatcherLastIndexReg;
@@ -1946,17 +1950,18 @@ JitCompartment::generateRegExpMatcherStu
                                  RegExpShared::Normal, &notFound, &oolEntry))
     {
         return nullptr;
     }
 
     // Construct the result.
     Register object = temp1;
     Label matchResultFallback, matchResultJoin;
-    masm.createGCObject(object, temp2, templateObject, gc::DefaultHeap, &matchResultFallback);
+    TemplateObject templateObj(templateObject);
+    masm.createGCObject(object, temp2, templateObj, gc::DefaultHeap, &matchResultFallback);
     masm.bind(&matchResultJoin);
 
     // Initialize slots of result object.
     masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), temp2);
     masm.storeValue(templateObject->getSlot(0), Address(temp2, 0));
     masm.storeValue(templateObject->getSlot(1), Address(temp2, sizeof(Value)));
 
     size_t elementsOffset = NativeObject::offsetOfFixedElements();
@@ -2859,17 +2864,18 @@ CodeGenerator::visitLambda(LLambda* lir)
     Register tempReg = ToRegister(lir->temp());
     const LambdaFunctionInfo& info = lir->mir()->info();
 
     OutOfLineCode* ool = oolCallVM(LambdaInfo, lir, ArgList(ImmGCPtr(info.funUnsafe()), envChain),
                                    StoreRegisterTo(output));
 
     MOZ_ASSERT(!info.singletonType);
 
-    masm.createGCObject(output, tempReg, info.funUnsafe(), gc::DefaultHeap, ool->entry());
+    TemplateObject templateObject(info.funUnsafe());
+    masm.createGCObject(output, tempReg, templateObject, gc::DefaultHeap, ool->entry());
 
     emitLambdaInit(output, envChain, info);
 
     if (info.flags & JSFunction::EXTENDED) {
         static_assert(FunctionExtended::NUM_EXTENDED_SLOTS == 2, "All slots must be initialized");
         masm.storeValue(UndefinedValue(), Address(output, FunctionExtended::offsetOfExtendedSlot(0)));
         masm.storeValue(UndefinedValue(), Address(output, FunctionExtended::offsetOfExtendedSlot(1)));
     }
@@ -2950,17 +2956,18 @@ CodeGenerator::visitLambdaArrow(LLambdaA
     }
 
     // There's not enough registers on x86 with the profiler enabled to request
     // a temp. Instead, spill part of one of the values, being prepared to
     // restore it if necessary on the out of line path.
     Register tempReg = newTarget.scratchReg();
     masm.push(newTarget.scratchReg());
 
-    masm.createGCObject(output, tempReg, info.funUnsafe(), gc::DefaultHeap, ool->entry());
+    TemplateObject templateObject(info.funUnsafe());
+    masm.createGCObject(output, tempReg, templateObject, gc::DefaultHeap, ool->entry());
 
     masm.pop(newTarget.scratchReg());
 
     emitLambdaInit(output, envChain, info);
 
     // Initialize extended slots. Lexical |this| is stored in the first one.
     MOZ_ASSERT(info.flags & JSFunction::EXTENDED);
     static_assert(FunctionExtended::NUM_EXTENDED_SLOTS == 2, "All slots must be initialized");
@@ -5928,32 +5935,33 @@ CodeGenerator::visitHypot(LHypot* lir)
     MOZ_ASSERT(ToFloatRegister(lir->output()) == ReturnDoubleReg);
 }
 
 void
 CodeGenerator::visitNewArray(LNewArray* lir)
 {
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
-    JSObject* templateObject = lir->mir()->templateObject();
     DebugOnly<uint32_t> length = lir->mir()->length();
 
     MOZ_ASSERT(length <= NativeObject::MAX_DENSE_ELEMENTS_COUNT);
 
     if (lir->mir()->isVMCall()) {
         visitNewArrayCallVM(lir);
         return;
     }
 
     OutOfLineNewArray* ool = new(alloc()) OutOfLineNewArray(lir);
     addOutOfLineCode(ool, lir->mir());
 
+    TemplateObject templateObject(lir->mir()->templateObject());
+    if (lir->mir()->convertDoubleElements())
+        templateObject.setConvertDoubleElements();
     masm.createGCObject(objReg, tempReg, templateObject, lir->mir()->initialHeap(),
-                        ool->entry(), /* initContents = */ true,
-                        lir->mir()->convertDoubleElements());
+                        ool->entry());
 
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitOutOfLineNewArray(OutOfLineNewArray* ool)
 {
     visitNewArrayCallVM(ool->lir());
@@ -5968,17 +5976,19 @@ CodeGenerator::visitNewArrayCopyOnWrite(
     ArrayObject* templateObject = lir->mir()->templateObject();
     gc::InitialHeap initialHeap = lir->mir()->initialHeap();
 
     // If we have a template object, we can inline call object creation.
     OutOfLineCode* ool = oolCallVM(NewArrayCopyOnWriteInfo, lir,
                                    ArgList(ImmGCPtr(templateObject), Imm32(initialHeap)),
                                    StoreRegisterTo(objReg));
 
-    masm.createGCObject(objReg, tempReg, templateObject, initialHeap, ool->entry());
+    TemplateObject templateObj(templateObject);
+    templateObj.setDenseElementsAreCopyOnWrite();
+    masm.createGCObject(objReg, tempReg, templateObj, initialHeap, ool->entry());
 
     masm.bind(ool->rejoin());
 }
 
 typedef ArrayObject* (*ArrayConstructorOneArgFn)(JSContext*, HandleObjectGroup, int32_t length);
 static const VMFunction ArrayConstructorOneArgInfo =
     FunctionInfo<ArrayConstructorOneArgFn>(ArrayConstructorOneArg, "ArrayConstructorOneArg");
 
@@ -6008,17 +6018,18 @@ CodeGenerator::visitNewArrayDynamicLengt
     if (canInline) {
         // Try to do the allocation inline if the template object is big enough
         // for the length in lengthReg. If the length is bigger we could still
         // use the template object and not allocate the elements, but it's more
         // efficient to do a single big allocation than (repeatedly) reallocating
         // the array later on when filling it.
         masm.branch32(Assembler::Above, lengthReg, Imm32(inlineLength), ool->entry());
 
-        masm.createGCObject(objReg, tempReg, templateObject, initialHeap, ool->entry());
+        TemplateObject templateObj(templateObject);
+        masm.createGCObject(objReg, tempReg, templateObj, initialHeap, ool->entry());
 
         size_t lengthOffset = NativeObject::offsetOfFixedElements() + ObjectElements::offsetOfLength();
         masm.store32(lengthReg, Address(objReg, lengthOffset));
     } else {
         masm.jump(ool->entry());
     }
 
     masm.bind(ool->rejoin());
@@ -6032,17 +6043,16 @@ typedef StringIteratorObject* (*NewStrin
 static const VMFunction NewStringIteratorObjectInfo =
     FunctionInfo<NewStringIteratorObjectFn>(NewStringIteratorObject, "NewStringIteratorObject");
 
 void
 CodeGenerator::visitNewIterator(LNewIterator* lir)
 {
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
-    JSObject* templateObject = lir->mir()->templateObject();
 
     OutOfLineCode* ool;
     switch (lir->mir()->type()) {
       case MNewIterator::ArrayIterator:
         ool = oolCallVM(NewArrayIteratorObjectInfo, lir,
                         ArgList(Imm32(GenericObject)),
                         StoreRegisterTo(objReg));
         break;
@@ -6050,16 +6060,17 @@ CodeGenerator::visitNewIterator(LNewIter
         ool = oolCallVM(NewStringIteratorObjectInfo, lir,
                         ArgList(Imm32(GenericObject)),
                         StoreRegisterTo(objReg));
         break;
       default:
           MOZ_CRASH("unexpected iterator type");
     }
 
+    TemplateObject templateObject(lir->mir()->templateObject());
     masm.createGCObject(objReg, tempReg, templateObject, gc::DefaultHeap, ool->entry());
 
     masm.bind(ool->rejoin());
 }
 
 typedef TypedArrayObject* (*TypedArrayConstructorOneArgFn)(JSContext*, HandleObject, int32_t length);
 static const VMFunction TypedArrayConstructorOneArgInfo =
     FunctionInfo<TypedArrayConstructorOneArgFn>(TypedArrayCreateWithTemplate,
@@ -6078,18 +6089,18 @@ CodeGenerator::visitNewTypedArray(LNewTy
 
     TypedArrayObject* ttemplate = &templateObject->as<TypedArrayObject>();
     uint32_t n = ttemplate->length();
 
     OutOfLineCode* ool = oolCallVM(TypedArrayConstructorOneArgInfo, lir,
                                    ArgList(ImmGCPtr(templateObject), Imm32(n)),
                                    StoreRegisterTo(objReg));
 
-    masm.createGCObject(objReg, tempReg, templateObject, initialHeap,
-                        ool->entry(), /*initContents*/true, /*convertDoubleElements*/false);
+    TemplateObject templateObj(templateObject);
+    masm.createGCObject(objReg, tempReg, templateObj, initialHeap, ool->entry());
 
     masm.initTypedArraySlots(objReg, tempReg, lengthReg, liveRegs, ool->entry(),
                              ttemplate, MacroAssembler::TypedArrayLength::Fixed);
 
     masm.bind(ool->rejoin());
 }
 
 void
@@ -6104,18 +6115,18 @@ CodeGenerator::visitNewTypedArrayDynamic
     gc::InitialHeap initialHeap = lir->mir()->initialHeap();
 
     TypedArrayObject* ttemplate = &templateObject->as<TypedArrayObject>();
 
     OutOfLineCode* ool = oolCallVM(TypedArrayConstructorOneArgInfo, lir,
                                    ArgList(ImmGCPtr(templateObject), lengthReg),
                                    StoreRegisterTo(objReg));
 
-    masm.createGCObject(objReg, tempReg, templateObject, initialHeap,
-                        ool->entry(), /*initContents*/true, /*convertDoubleElements*/false);
+    TemplateObject templateObj(templateObject);
+    masm.createGCObject(objReg, tempReg, templateObj, initialHeap, ool->entry());
 
     masm.initTypedArraySlots(objReg, tempReg, lengthReg, liveRegs, ool->entry(),
                              ttemplate, MacroAssembler::TypedArrayLength::Dynamic);
 
     masm.bind(ool->rejoin());
 }
 
 // Out-of-line object allocation for JSOP_NEWOBJECT.
@@ -6184,36 +6195,36 @@ CodeGenerator::visitNewObjectVMCall(LNew
 
     if (ReturnReg != objReg)
         masm.movePtr(ReturnReg, objReg);
 
     restoreLive(lir);
 }
 
 static bool
-ShouldInitFixedSlots(LInstruction* lir, JSObject* obj)
-{
-    if (!obj->isNative())
+ShouldInitFixedSlots(LInstruction* lir, const TemplateObject& obj)
+{
+    if (!obj.isNative())
         return true;
-    NativeObject* templateObj = &obj->as<NativeObject>();
+    const NativeTemplateObject& templateObj = obj.asNativeTemplateObject();
 
     // Look for StoreFixedSlot instructions following an object allocation
     // that write to this object before a GC is triggered or this object is
     // passed to a VM call. If all fixed slots will be initialized, the
     // allocation code doesn't need to set the slots to |undefined|.
 
-    uint32_t nfixed = templateObj->numUsedFixedSlots();
+    uint32_t nfixed = templateObj.numUsedFixedSlots();
     if (nfixed == 0)
         return false;
 
     // Only optimize if all fixed slots are initially |undefined|, so that we
     // can assume incremental pre-barriers are not necessary. See also the
     // comment below.
     for (uint32_t slot = 0; slot < nfixed; slot++) {
-        if (!templateObj->getSlot(slot).isUndefined())
+        if (!templateObj.getSlot(slot).isUndefined())
             return true;
     }
 
     // Keep track of the fixed slots that are initialized. initializedSlots is
     // a bit mask with a bit for each slot.
     MOZ_ASSERT(nfixed <= NativeObject::MAX_FIXED_SLOTS);
     static_assert(NativeObject::MAX_FIXED_SLOTS <= 32, "Slot bits must fit in 32 bits");
     uint32_t initializedSlots = 0;
@@ -6276,26 +6287,27 @@ ShouldInitFixedSlots(LInstruction* lir, 
     MOZ_CRASH("Shouldn't get here");
 }
 
 void
 CodeGenerator::visitNewObject(LNewObject* lir)
 {
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
-    JSObject* templateObject = lir->mir()->templateObject();
 
     if (lir->mir()->isVMCall()) {
         visitNewObjectVMCall(lir);
         return;
     }
 
     OutOfLineNewObject* ool = new(alloc()) OutOfLineNewObject(lir);
     addOutOfLineCode(ool, lir->mir());
 
+    TemplateObject templateObject(lir->mir()->templateObject());
+
     bool initContents = ShouldInitFixedSlots(lir, templateObject);
     masm.createGCObject(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry(),
                         initContents);
 
     masm.bind(ool->rejoin());
 }
 
 void
@@ -6316,17 +6328,18 @@ CodeGenerator::visitNewTypedObject(LNewT
     Register temp = ToRegister(lir->temp());
     InlineTypedObject* templateObject = lir->mir()->templateObject();
     gc::InitialHeap initialHeap = lir->mir()->initialHeap();
 
     OutOfLineCode* ool = oolCallVM(NewTypedObjectInfo, lir,
                                    ArgList(ImmGCPtr(templateObject), Imm32(initialHeap)),
                                    StoreRegisterTo(object));
 
-    masm.createGCObject(object, temp, templateObject, initialHeap, ool->entry());
+    TemplateObject templateObj(templateObject);
+    masm.createGCObject(object, temp, templateObj, initialHeap, ool->entry());
 
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitSimdBox(LSimdBox* lir)
 {
     FloatRegister in = ToFloatRegister(lir->input());
@@ -6338,17 +6351,18 @@ CodeGenerator::visitSimdBox(LSimdBox* li
 
     addSimdTemplateToReadBarrier(lir->mir()->simdType());
 
     MOZ_ASSERT(lir->safepoint()->liveRegs().has(in), "Save the input register across oolCallVM");
     OutOfLineCode* ool = oolCallVM(NewTypedObjectInfo, lir,
                                    ArgList(ImmGCPtr(templateObject), Imm32(initialHeap)),
                                    StoreRegisterTo(object));
 
-    masm.createGCObject(object, temp, templateObject, initialHeap, ool->entry());
+    TemplateObject templateObj(templateObject);
+    masm.createGCObject(object, temp, templateObj, initialHeap, ool->entry());
     masm.bind(ool->rejoin());
 
     Address objectData(object, InlineTypedObject::offsetOfDataStart());
     switch (type) {
       case MIRType::Int8x16:
       case MIRType::Int16x8:
       case MIRType::Int32x4:
       case MIRType::Bool8x16:
@@ -6406,26 +6420,27 @@ static const VMFunction NewNamedLambdaOb
     FunctionInfo<NewNamedLambdaObjectFn>(NamedLambdaObject::createTemplateObject,
                                          "NamedLambdaObject::createTemplateObject");
 
 void
 CodeGenerator::visitNewNamedLambdaObject(LNewNamedLambdaObject* lir)
 {
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
-    EnvironmentObject* templateObj = lir->mir()->templateObj();
     const CompileInfo& info = lir->mir()->block()->info();
 
     // If we have a template object, we can inline call object creation.
     OutOfLineCode* ool = oolCallVM(NewNamedLambdaObjectInfo, lir,
                                    ArgList(ImmGCPtr(info.funMaybeLazy()), Imm32(gc::DefaultHeap)),
                                    StoreRegisterTo(objReg));
 
-    bool initContents = ShouldInitFixedSlots(lir, templateObj);
-    masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry(),
+    TemplateObject templateObject(lir->mir()->templateObj());
+
+    bool initContents = ShouldInitFixedSlots(lir, templateObject);
+    masm.createGCObject(objReg, tempReg, templateObject, gc::DefaultHeap, ool->entry(),
                         initContents);
 
     masm.bind(ool->rejoin());
 }
 
 typedef JSObject* (*NewCallObjectFn)(JSContext*, HandleShape, HandleObjectGroup);
 static const VMFunction NewCallObjectInfo =
     FunctionInfo<NewCallObjectFn>(NewCallObject, "NewCallObject");
@@ -6439,18 +6454,19 @@ CodeGenerator::visitNewCallObject(LNewCa
     CallObject* templateObj = lir->mir()->templateObject();
 
     OutOfLineCode* ool = oolCallVM(NewCallObjectInfo, lir,
                                    ArgList(ImmGCPtr(templateObj->lastProperty()),
                                            ImmGCPtr(templateObj->group())),
                                    StoreRegisterTo(objReg));
 
     // Inline call object creation, using the OOL path only for tricky cases.
-    bool initContents = ShouldInitFixedSlots(lir, templateObj);
-    masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry(),
+    TemplateObject templateObject(templateObj);
+    bool initContents = ShouldInitFixedSlots(lir, templateObject);
+    masm.createGCObject(objReg, tempReg, templateObject, gc::DefaultHeap, ool->entry(),
                         initContents);
 
     masm.bind(ool->rejoin());
 }
 
 typedef JSObject* (*NewSingletonCallObjectFn)(JSContext*, HandleShape);
 static const VMFunction NewSingletonCallObjectInfo =
     FunctionInfo<NewSingletonCallObjectFn>(NewSingletonCallObject, "NewSingletonCallObject");
@@ -6485,17 +6501,18 @@ CodeGenerator::visitNewStringObject(LNew
     Register output = ToRegister(lir->output());
     Register temp = ToRegister(lir->temp());
 
     StringObject* templateObj = lir->mir()->templateObj();
 
     OutOfLineCode* ool = oolCallVM(NewStringObjectInfo, lir, ArgList(input),
                                    StoreRegisterTo(output));
 
-    masm.createGCObject(output, temp, templateObj, gc::DefaultHeap, ool->entry());
+    TemplateObject templateObject(templateObj);
+    masm.createGCObject(output, temp, templateObject, gc::DefaultHeap, ool->entry());
 
     masm.loadStringLength(input, temp);
 
     masm.storeValue(JSVAL_TYPE_STRING, input, Address(output, StringObject::offsetOfPrimitiveValue()));
     masm.storeValue(JSVAL_TYPE_INT32, temp, Address(output, StringObject::offsetOfLength()));
 
     masm.bind(ool->rejoin());
 }
@@ -6638,19 +6655,19 @@ CodeGenerator::visitCreateThisWithTempla
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
 
     OutOfLineCode* ool = oolCallVM(NewInitObjectWithTemplateInfo, lir,
                                    ArgList(ImmGCPtr(templateObject)),
                                    StoreRegisterTo(objReg));
 
     // Allocate. If the FreeList is empty, call to VM, which may GC.
-    bool initContents = !templateObject->is<PlainObject>() ||
-                        ShouldInitFixedSlots(lir, &templateObject->as<PlainObject>());
-    masm.createGCObject(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry(),
+    TemplateObject templateObj(templateObject);
+    bool initContents = !templateObj.isPlainObject() || ShouldInitFixedSlots(lir, templateObj);
+    masm.createGCObject(objReg, tempReg, templateObj, lir->mir()->initialHeap(), ool->entry(),
                         initContents);
 
     masm.bind(ool->rejoin());
 }
 
 typedef JSObject* (*NewIonArgumentsObjectFn)(JSContext* cx, JitFrameLayout* frame, HandleObject);
 static const VMFunction NewIonArgumentsObjectInfo =
     FunctionInfo<NewIonArgumentsObjectFn>((NewIonArgumentsObjectFn) ArgumentsObject::createForIon,
@@ -6671,17 +6688,18 @@ CodeGenerator::visitCreateArgumentsObjec
         Register cxTemp = ToRegister(lir->temp2());
 
         masm.Push(callObj);
 
         // Try to allocate an arguments object. This will leave the reserved
         // slots uninitialized, so it's important we don't GC until we
         // initialize these slots in ArgumentsObject::finishForIon.
         Label failure;
-        masm.createGCObject(objTemp, temp, templateObj, gc::DefaultHeap, &failure,
+        TemplateObject templateObject(templateObj);
+        masm.createGCObject(objTemp, temp, templateObject, gc::DefaultHeap, &failure,
                             /* initContents = */ false);
 
         masm.moveStackPtrTo(temp);
         masm.addPtr(Imm32(masm.framePushed()), temp);
 
         masm.setupUnalignedABICall(cxTemp);
         masm.loadJSContext(cxTemp);
         masm.passABIArg(cxTemp);
@@ -9684,17 +9702,18 @@ CodeGenerator::visitArraySlice(LArraySli
     Register begin = ToRegister(lir->begin());
     Register end = ToRegister(lir->end());
     Register temp1 = ToRegister(lir->temp1());
     Register temp2 = ToRegister(lir->temp2());
 
     Label call, fail;
 
     // Try to allocate an object.
-    masm.createGCObject(temp1, temp2, lir->mir()->templateObj(), lir->mir()->initialHeap(), &fail);
+    TemplateObject templateObject(lir->mir()->templateObj());
+    masm.createGCObject(temp1, temp2, templateObject, lir->mir()->initialHeap(), &fail);
 
     // Fixup the group of the result in case it doesn't match the template object.
     masm.copyObjGroupNoPreBarrier(object, temp1, temp2);
 
     masm.jump(&call);
     {
         masm.bind(&fail);
         masm.movePtr(ImmPtr(nullptr), temp1);
@@ -9996,17 +10015,18 @@ CodeGenerator::visitRest(LRest* lir)
     Register numActuals = ToRegister(lir->numActuals());
     Register temp0 = ToRegister(lir->getTemp(0));
     Register temp1 = ToRegister(lir->getTemp(1));
     Register temp2 = ToRegister(lir->getTemp(2));
     unsigned numFormals = lir->mir()->numFormals();
     ArrayObject* templateObject = lir->mir()->templateObject();
 
     Label joinAlloc, failAlloc;
-    masm.createGCObject(temp2, temp0, templateObject, gc::DefaultHeap, &failAlloc);
+    TemplateObject templateObj(templateObject);
+    masm.createGCObject(temp2, temp0, templateObj, gc::DefaultHeap, &failAlloc);
     masm.jump(&joinAlloc);
     {
         masm.bind(&failAlloc);
         masm.movePtr(ImmPtr(nullptr), temp2);
     }
     masm.bind(&joinAlloc);
 
     emitRest(lir, temp2, numActuals, temp0, temp1, numFormals, templateObject, false, ToRegister(lir->output()));
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -22,16 +22,17 @@
 #include "jit/Lowering.h"
 #include "jit/MIR.h"
 #include "js/Conversions.h"
 #include "js/Printf.h"
 #include "vm/TraceLogging.h"
 
 #include "gc/Nursery-inl.h"
 #include "jit/shared/Lowering-shared-inl.h"
+#include "jit/TemplateObject-inl.h"
 #include "vm/Interpreter-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/TypeInference-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using JS::GenericNaN;
@@ -992,36 +993,36 @@ MacroAssembler::allocateObject(Register 
     pop(temp);
     callFreeStub(temp);
     jump(fail);
 
     bind(&success);
 }
 
 void
-MacroAssembler::createGCObject(Register obj, Register temp, JSObject* templateObj,
-                               gc::InitialHeap initialHeap, Label* fail, bool initContents,
-                               bool convertDoubleElements)
+MacroAssembler::createGCObject(Register obj, Register temp, const TemplateObject& templateObj,
+                               gc::InitialHeap initialHeap, Label* fail, bool initContents)
 {
-    gc::AllocKind allocKind = templateObj->asTenured().getAllocKind();
+    gc::AllocKind allocKind = templateObj.getAllocKind();
     MOZ_ASSERT(gc::IsObjectAllocKind(allocKind));
 
     uint32_t nDynamicSlots = 0;
-    if (templateObj->isNative()) {
-        nDynamicSlots = templateObj->as<NativeObject>().numDynamicSlots();
+    if (templateObj.isNative()) {
+        const NativeTemplateObject& ntemplate = templateObj.asNativeTemplateObject();
+        nDynamicSlots = ntemplate.numDynamicSlots();
 
         // Arrays with copy on write elements do not need fixed space for an
         // elements header. The template object, which owns the original
         // elements, might have another allocation kind.
-        if (templateObj->as<NativeObject>().denseElementsAreCopyOnWrite())
+        if (ntemplate.denseElementsAreCopyOnWrite())
             allocKind = gc::AllocKind::OBJECT0_BACKGROUND;
     }
 
     allocateObject(obj, temp, allocKind, nDynamicSlots, initialHeap, fail);
-    initGCThing(obj, temp, templateObj, initContents, convertDoubleElements);
+    initGCThing(obj, temp, templateObj, initContents);
 }
 
 
 // Inlined equivalent of gc::AllocateNonObject, without failure case handling.
 // Non-object allocation does not need to worry about slots, so can take a
 // simpler path.
 void
 MacroAssembler::allocateNonObject(Register result, Register temp, gc::AllocKind allocKind, Label* fail)
@@ -1104,31 +1105,31 @@ MacroAssembler::newGCString(Register res
 void
 MacroAssembler::newGCFatInlineString(Register result, Register temp, Label* fail, bool attemptNursery)
 {
     allocateString(result, temp, js::gc::AllocKind::FAT_INLINE_STRING,
                    attemptNursery ? gc::DefaultHeap : gc::TenuredHeap, fail);
 }
 
 void
-MacroAssembler::copySlotsFromTemplate(Register obj, const NativeObject* templateObj,
+MacroAssembler::copySlotsFromTemplate(Register obj, const NativeTemplateObject& templateObj,
                                       uint32_t start, uint32_t end)
 {
-    uint32_t nfixed = Min(templateObj->numFixedSlots(), end);
+    uint32_t nfixed = Min(templateObj.numFixedSlots(), end);
     for (unsigned i = start; i < nfixed; i++) {
         // Template objects are not exposed to script and therefore immutable.
         // However, regexp template objects are sometimes used directly (when
         // the cloning is not observable), and therefore we can end up with a
         // non-zero lastIndex. Detect this case here and just substitute 0, to
         // avoid racing with the main thread updating this slot.
         Value v;
-        if (templateObj->is<RegExpObject>() && i == RegExpObject::lastIndexSlot())
+        if (templateObj.isRegExpObject() && i == RegExpObject::lastIndexSlot())
             v = Int32Value(0);
         else
-            v = templateObj->getFixedSlot(i);
+            v = templateObj.getSlot(i);
         storeValue(v, Address(obj, NativeObject::getFixedSlotOffset(i)));
     }
 }
 
 void
 MacroAssembler::fillSlotsWithConstantValue(Address base, Register temp,
                                            uint32_t start, uint32_t end, const Value& v)
 {
@@ -1164,33 +1165,33 @@ MacroAssembler::fillSlotsWithUndefined(A
 
 void
 MacroAssembler::fillSlotsWithUninitialized(Address base, Register temp, uint32_t start, uint32_t end)
 {
     fillSlotsWithConstantValue(base, temp, start, end, MagicValue(JS_UNINITIALIZED_LEXICAL));
 }
 
 static void
-FindStartOfUninitializedAndUndefinedSlots(NativeObject* templateObj, uint32_t nslots,
+FindStartOfUninitializedAndUndefinedSlots(const NativeTemplateObject& templateObj, uint32_t nslots,
                                           uint32_t* startOfUninitialized,
                                           uint32_t* startOfUndefined)
 {
-    MOZ_ASSERT(nslots == templateObj->lastProperty()->slotSpan(templateObj->getClass()));
+    MOZ_ASSERT(nslots == templateObj.slotSpan());
     MOZ_ASSERT(nslots > 0);
 
     uint32_t first = nslots;
     for (; first != 0; --first) {
-        if (templateObj->getSlot(first - 1) != UndefinedValue())
+        if (templateObj.getSlot(first - 1) != UndefinedValue())
             break;
     }
     *startOfUndefined = first;
 
-    if (first != 0 && IsUninitializedLexical(templateObj->getSlot(first - 1))) {
+    if (first != 0 && IsUninitializedLexical(templateObj.getSlot(first - 1))) {
         for (; first != 0; --first) {
-            if (!IsUninitializedLexical(templateObj->getSlot(first - 1)))
+            if (!IsUninitializedLexical(templateObj.getSlot(first - 1)))
                 break;
         }
         *startOfUninitialized = first;
     } else {
         *startOfUninitialized = *startOfUndefined;
     }
 }
 
@@ -1290,27 +1291,27 @@ MacroAssembler::initTypedArraySlots(Regi
         PopRegsInMask(liveRegs);
 
         // Fail when data elements is set to NULL.
         branchPtr(Assembler::Equal, Address(obj, dataSlotOffset), ImmWord(0), fail);
     }
 }
 
 void
-MacroAssembler::initGCSlots(Register obj, Register temp, NativeObject* templateObj,
+MacroAssembler::initGCSlots(Register obj, Register temp, const NativeTemplateObject& templateObj,
                             bool initContents)
 {
     // Slots of non-array objects are required to be initialized.
     // Use the values currently in the template object.
-    uint32_t nslots = templateObj->lastProperty()->slotSpan(templateObj->getClass());
+    uint32_t nslots = templateObj.slotSpan();
     if (nslots == 0)
         return;
 
-    uint32_t nfixed = templateObj->numUsedFixedSlots();
-    uint32_t ndynamic = templateObj->numDynamicSlots();
+    uint32_t nfixed = templateObj.numUsedFixedSlots();
+    uint32_t ndynamic = templateObj.numDynamicSlots();
 
     // Attempt to group slot writes such that we minimize the amount of
     // duplicated data we need to embed in code and load into registers. In
     // general, most template object slots will be undefined except for any
     // reserved slots. Since reserved slots come first, we split the object
     // logically into independent non-UndefinedValue writes to the head and
     // duplicated writes of UndefinedValue to the tail. For the majority of
     // objects, the "tail" will be the entire slot range.
@@ -1321,17 +1322,17 @@ MacroAssembler::initGCSlots(Register obj
     // has parameter expressions, in which case closed over parameters have
     // TDZ. Uninitialized slots come before undefined slots in CallObjects.
     uint32_t startOfUninitialized = nslots;
     uint32_t startOfUndefined = nslots;
     FindStartOfUninitializedAndUndefinedSlots(templateObj, nslots,
                                               &startOfUninitialized, &startOfUndefined);
     MOZ_ASSERT(startOfUninitialized <= nfixed); // Reserved slots must be fixed.
     MOZ_ASSERT(startOfUndefined >= startOfUninitialized);
-    MOZ_ASSERT_IF(!templateObj->is<CallObject>(), startOfUninitialized == startOfUndefined);
+    MOZ_ASSERT_IF(!templateObj.isCallObject(), startOfUninitialized == startOfUndefined);
 
     // Copy over any preserved reserved slots.
     copySlotsFromTemplate(obj, templateObj, 0, startOfUninitialized);
 
     // Fill the rest of the fixed slots with undefined and uninitialized.
     if (initContents) {
         size_t offset = NativeObject::getFixedSlotOffset(startOfUninitialized);
         fillSlotsWithUninitialized(Address(obj, offset), temp,
@@ -1359,106 +1360,102 @@ MacroAssembler::initGCSlots(Register obj
             fillSlotsWithUndefined(Address(obj, 0), temp, 0, ndynamic);
         }
 
         pop(obj);
     }
 }
 
 void
-MacroAssembler::initGCThing(Register obj, Register temp, JSObject* templateObj,
-                            bool initContents, bool convertDoubleElements)
+MacroAssembler::initGCThing(Register obj, Register temp, const TemplateObject& templateObj,
+                            bool initContents)
 {
     // Fast initialization of an empty object returned by allocateObject().
 
-    storePtr(ImmGCPtr(templateObj->group()), Address(obj, JSObject::offsetOfGroup()));
-
-    if (templateObj->is<ShapedObject>())
-        storePtr(ImmGCPtr(templateObj->maybeShape()), Address(obj, ShapedObject::offsetOfShape()));
-
-    MOZ_ASSERT_IF(convertDoubleElements, templateObj->is<ArrayObject>());
-
-    if (templateObj->isNative()) {
-        NativeObject* ntemplate = &templateObj->as<NativeObject>();
-        MOZ_ASSERT_IF(!ntemplate->denseElementsAreCopyOnWrite(), !ntemplate->hasDynamicElements());
+    storePtr(ImmGCPtr(templateObj.group()), Address(obj, JSObject::offsetOfGroup()));
+
+    if (gc::Cell* shape = templateObj.maybeShape())
+        storePtr(ImmGCPtr(shape), Address(obj, ShapedObject::offsetOfShape()));
+
+    if (templateObj.isNative()) {
+        const NativeTemplateObject& ntemplate = templateObj.asNativeTemplateObject();
+        MOZ_ASSERT_IF(!ntemplate.denseElementsAreCopyOnWrite(), !ntemplate.hasDynamicElements());
+        MOZ_ASSERT_IF(ntemplate.convertDoubleElements(), ntemplate.isArrayObject());
 
         // If the object has dynamic slots, the slots member has already been
         // filled in.
-        if (!ntemplate->hasDynamicSlots())
+        if (!ntemplate.hasDynamicSlots())
             storePtr(ImmPtr(nullptr), Address(obj, NativeObject::offsetOfSlots()));
 
-        if (ntemplate->denseElementsAreCopyOnWrite()) {
-            storePtr(ImmPtr((const Value*) ntemplate->getDenseElements()),
+        if (ntemplate.denseElementsAreCopyOnWrite()) {
+            storePtr(ImmPtr(ntemplate.getDenseElements()),
                      Address(obj, NativeObject::offsetOfElements()));
-        } else if (ntemplate->is<ArrayObject>()) {
+        } else if (ntemplate.isArrayObject()) {
             int elementsOffset = NativeObject::offsetOfFixedElements();
 
             computeEffectiveAddress(Address(obj, elementsOffset), temp);
             storePtr(temp, Address(obj, NativeObject::offsetOfElements()));
 
             // Fill in the elements header.
-            store32(Imm32(ntemplate->getDenseCapacity()),
+            store32(Imm32(ntemplate.getDenseCapacity()),
                     Address(obj, elementsOffset + ObjectElements::offsetOfCapacity()));
-            store32(Imm32(ntemplate->getDenseInitializedLength()),
+            store32(Imm32(ntemplate.getDenseInitializedLength()),
                     Address(obj, elementsOffset + ObjectElements::offsetOfInitializedLength()));
-            store32(Imm32(ntemplate->as<ArrayObject>().length()),
+            store32(Imm32(ntemplate.getArrayLength()),
                     Address(obj, elementsOffset + ObjectElements::offsetOfLength()));
-            store32(Imm32(convertDoubleElements
+            store32(Imm32(ntemplate.convertDoubleElements()
                           ? ObjectElements::CONVERT_DOUBLE_ELEMENTS
                           : 0),
                     Address(obj, elementsOffset + ObjectElements::offsetOfFlags()));
-            MOZ_ASSERT(!ntemplate->hasPrivate());
-        } else if (ntemplate->is<ArgumentsObject>()) {
+            MOZ_ASSERT(!ntemplate.hasPrivate());
+        } else if (ntemplate.isArgumentsObject()) {
             // The caller will initialize the reserved slots.
             MOZ_ASSERT(!initContents);
-            MOZ_ASSERT(!ntemplate->hasPrivate());
+            MOZ_ASSERT(!ntemplate.hasPrivate());
             storePtr(ImmPtr(emptyObjectElements), Address(obj, NativeObject::offsetOfElements()));
         } else {
             // If the target type could be a TypedArray that maps shared memory
             // then this would need to store emptyObjectElementsShared in that case.
-            MOZ_ASSERT(!ntemplate->isSharedMemory());
+            MOZ_ASSERT(!ntemplate.isSharedMemory());
 
             storePtr(ImmPtr(emptyObjectElements), Address(obj, NativeObject::offsetOfElements()));
 
             initGCSlots(obj, temp, ntemplate, initContents);
 
-            if (ntemplate->hasPrivate() && !ntemplate->is<TypedArrayObject>()) {
-                uint32_t nfixed = ntemplate->numFixedSlots();
+            if (ntemplate.hasPrivate() && !ntemplate.isTypedArrayObject()) {
+                uint32_t nfixed = ntemplate.numFixedSlots();
                 Address privateSlot(obj, NativeObject::getPrivateDataOffset(nfixed));
-                if (ntemplate->is<RegExpObject>()) {
+                if (ntemplate.isRegExpObject()) {
                     // RegExpObject stores a GC thing (RegExpShared*) in its
                     // private slot, so we have to use ImmGCPtr.
-                    RegExpObject* regexp = &ntemplate->as<RegExpObject>();
-                    MOZ_ASSERT(regexp->hasShared());
-                    MOZ_ASSERT(ntemplate->getPrivate() == regexp->sharedRef().get());
-                    storePtr(ImmGCPtr(regexp->sharedRef().get()), privateSlot);
+                    storePtr(ImmGCPtr(ntemplate.regExpShared()), privateSlot);
                 } else {
-                    storePtr(ImmPtr(ntemplate->getPrivate()), privateSlot);
+                    storePtr(ImmPtr(ntemplate.getPrivate()), privateSlot);
                 }
             }
         }
-    } else if (templateObj->is<InlineTypedObject>()) {
+    } else if (templateObj.isInlineTypedObject()) {
         JS::AutoAssertNoGC nogc; // off-thread, so cannot GC
-        size_t nbytes = templateObj->as<InlineTypedObject>().size();
-        const uint8_t* memory = templateObj->as<InlineTypedObject>().inlineTypedMem(nogc);
+        size_t nbytes = templateObj.getInlineTypedObjectSize();
+        const uint8_t* memory = templateObj.getInlineTypedObjectMem(nogc);
 
         // Memcpy the contents of the template object to the new object.
         size_t offset = 0;
         while (nbytes) {
             uintptr_t value = *(uintptr_t*)(memory + offset);
             storePtr(ImmWord(value),
                      Address(obj, InlineTypedObject::offsetOfDataStart() + offset));
             nbytes = (nbytes < sizeof(uintptr_t)) ? 0 : nbytes - sizeof(uintptr_t);
             offset += sizeof(uintptr_t);
         }
-    } else if (templateObj->is<UnboxedPlainObject>()) {
-        MOZ_ASSERT(!templateObj->as<UnboxedPlainObject>().maybeExpando());
+    } else if (templateObj.isUnboxedPlainObject()) {
+        MOZ_ASSERT(!templateObj.unboxedObjectHasExpando());
         storePtr(ImmPtr(nullptr), Address(obj, UnboxedPlainObject::offsetOfExpando()));
         if (initContents)
-            initUnboxedObjectContents(obj, &templateObj->as<UnboxedPlainObject>());
+            initUnboxedObjectContents(obj, templateObj.unboxedObjectLayout());
     } else {
         MOZ_CRASH("Unknown object");
     }
 
 #ifdef JS_GC_TRACE
     RegisterSet regs = RegisterSet::Volatile();
     PushRegsInMask(regs);
     regs.takeUnchecked(obj);
@@ -1470,20 +1467,18 @@ MacroAssembler::initGCThing(Register obj
     passABIArg(temp);
     callWithABI(JS_FUNC_TO_DATA_PTR(void*, js::gc::TraceCreateObject));
 
     PopRegsInMask(RegisterSet::Volatile());
 #endif
 }
 
 void
-MacroAssembler::initUnboxedObjectContents(Register object, UnboxedPlainObject* templateObject)
+MacroAssembler::initUnboxedObjectContents(Register object, const UnboxedLayout& layout)
 {
-    const UnboxedLayout& layout = templateObject->layoutDontCheckGeneration();
-
     // Initialize reference fields of the object, per UnboxedPlainObject::create.
     if (const int32_t* list = layout.traceList()) {
         while (*list != -1) {
             storePtr(ImmGCPtr(GetJitContext()->runtime->names().empty),
                      Address(object, UnboxedPlainObject::offsetOfData() + *list));
             list++;
         }
         list++;
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -29,16 +29,17 @@
 # include "jit/none/MacroAssembler-none.h"
 #else
 # error "Unknown architecture!"
 #endif
 #include "jit/AtomicOp.h"
 #include "jit/IonInstrumentation.h"
 #include "jit/IonTypes.h"
 #include "jit/JitCompartment.h"
+#include "jit/TemplateObject.h"
 #include "jit/VMFunctions.h"
 #include "vm/ProxyObject.h"
 #include "vm/Shape.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/UnboxedObject.h"
 
 using mozilla::FloatingPoint;
 
@@ -2232,42 +2233,42 @@ class MacroAssembler : public MacroAssem
     void freeListAllocate(Register result, Register temp, gc::AllocKind allocKind, Label* fail);
     void allocateObject(Register result, Register temp, gc::AllocKind allocKind,
                         uint32_t nDynamicSlots, gc::InitialHeap initialHeap, Label* fail);
     void nurseryAllocateString(Register result, Register temp, gc::AllocKind allocKind,
                                Label* fail);
     void allocateString(Register result, Register temp, gc::AllocKind allocKind,
                         gc::InitialHeap initialHeap, Label* fail);
     void allocateNonObject(Register result, Register temp, gc::AllocKind allocKind, Label* fail);
-    void copySlotsFromTemplate(Register obj, const NativeObject* templateObj,
+    void copySlotsFromTemplate(Register obj, const NativeTemplateObject& templateObj,
                                uint32_t start, uint32_t end);
     void fillSlotsWithConstantValue(Address addr, Register temp, uint32_t start, uint32_t end,
                                     const Value& v);
     void fillSlotsWithUndefined(Address addr, Register temp, uint32_t start, uint32_t end);
     void fillSlotsWithUninitialized(Address addr, Register temp, uint32_t start, uint32_t end);
 
-    void initGCSlots(Register obj, Register temp, NativeObject* templateObj, bool initContents);
+    void initGCSlots(Register obj, Register temp, const NativeTemplateObject& templateObj,
+                     bool initContents);
 
   public:
     void callMallocStub(size_t nbytes, Register result, Label* fail);
     void callFreeStub(Register slots);
-    void createGCObject(Register result, Register temp, JSObject* templateObj,
-                        gc::InitialHeap initialHeap, Label* fail, bool initContents = true,
-                        bool convertDoubleElements = false);
-
-    void initGCThing(Register obj, Register temp, JSObject* templateObj,
-                     bool initContents = true, bool convertDoubleElements = false);
+    void createGCObject(Register result, Register temp, const TemplateObject& templateObj,
+                        gc::InitialHeap initialHeap, Label* fail, bool initContents = true);
+
+    void initGCThing(Register obj, Register temp, const TemplateObject& templateObj,
+                     bool initContents = true);
 
     enum class TypedArrayLength { Fixed, Dynamic };
 
     void initTypedArraySlots(Register obj, Register temp, Register lengthReg,
                              LiveRegisterSet liveRegs, Label* fail,
                              TypedArrayObject* templateObj, TypedArrayLength lengthKind);
 
-    void initUnboxedObjectContents(Register object, UnboxedPlainObject* templateObject);
+    void initUnboxedObjectContents(Register object, const UnboxedLayout& layout);
 
     void newGCString(Register result, Register temp, Label* fail, bool attemptNursery);
     void newGCFatInlineString(Register result, Register temp, Label* fail, bool attemptNursery);
 
     // Compares two strings for equality based on the JSOP.
     // This checks for identical pointers, atoms and length and fails for everything else.
     void compareStrings(JSOp op, Register left, Register right, Register result,
                         Label* fail);
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -2622,17 +2622,18 @@ GenerateNewObjectWithTemplateCode(JSCont
 #endif
 
     Label failure;
     Register objReg = R0.scratchReg();
     Register tempReg = R1.scratchReg();
     masm.branchIfPretenuredGroup(templateObject->group(), tempReg, &failure);
     masm.branchPtr(Assembler::NotEqual, AbsoluteAddress(cx->compartment()->addressOfMetadataBuilder()),
                    ImmWord(0), &failure);
-    masm.createGCObject(objReg, tempReg, templateObject, gc::DefaultHeap, &failure);
+    TemplateObject templateObj(templateObject);
+    masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, &failure);
     masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0);
 
     EmitReturnFromIC(masm);
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
 
     Linker linker(masm);
     AutoFlushICache afc("GenerateNewObjectWithTemplateCode");
new file mode 100644
--- /dev/null
+++ b/js/src/jit/TemplateObject-inl.h
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef jit_TemplateObject_inl_h
+#define jit_TemplateObject_inl_h
+
+#include "jit/TemplateObject.h"
+
+#include "vm/RegExpObject.h"
+
+#include "vm/ShapedObject-inl.h"
+
+namespace js {
+namespace jit {
+
+inline gc::AllocKind
+TemplateObject::getAllocKind() const
+{
+    return obj_->asTenured().getAllocKind();
+}
+
+inline bool
+TemplateObject::isNative() const
+{
+    return obj_->isNative();
+}
+
+inline bool
+TemplateObject::isArrayObject() const
+{
+    return obj_->is<ArrayObject>();
+}
+
+inline bool
+TemplateObject::isArgumentsObject() const
+{
+    return obj_->is<ArgumentsObject>();
+}
+
+inline bool
+TemplateObject::isTypedArrayObject() const
+{
+    return obj_->is<TypedArrayObject>();
+}
+
+inline bool
+TemplateObject::isRegExpObject() const
+{
+    return obj_->is<RegExpObject>();
+}
+
+inline bool
+TemplateObject::isInlineTypedObject() const
+{
+    return obj_->is<InlineTypedObject>();
+}
+
+inline bool
+TemplateObject::isUnboxedPlainObject() const
+{
+    return obj_->is<UnboxedPlainObject>();
+}
+
+inline bool
+TemplateObject::isCallObject() const
+{
+    return obj_->is<CallObject>();
+}
+
+inline bool
+TemplateObject::isPlainObject() const
+{
+    return obj_->is<PlainObject>();
+}
+
+inline gc::Cell*
+TemplateObject::group() const
+{
+    MOZ_ASSERT(!obj_->hasLazyGroup());
+    return obj_->group();
+}
+
+inline gc::Cell*
+TemplateObject::maybeShape() const
+{
+    if (obj_->is<ShapedObject>()) {
+        Shape* shape = obj_->maybeShape();
+        MOZ_ASSERT(!shape->inDictionary());
+        return shape;
+    }
+    return nullptr;
+}
+
+inline uint32_t
+TemplateObject::getInlineTypedObjectSize() const
+{
+    return obj_->as<InlineTypedObject>().size();
+}
+
+inline uint8_t*
+TemplateObject::getInlineTypedObjectMem(const JS::AutoRequireNoGC& nogc) const
+{
+    return obj_->as<InlineTypedObject>().inlineTypedMem(nogc);
+}
+
+inline const UnboxedLayout&
+TemplateObject::unboxedObjectLayout() const
+{
+    return obj_->as<UnboxedPlainObject>().layoutDontCheckGeneration();
+}
+
+#ifdef DEBUG
+inline bool
+TemplateObject::unboxedObjectHasExpando() const
+{
+    return obj_->as<UnboxedPlainObject>().maybeExpando();
+}
+#endif
+
+inline const NativeTemplateObject&
+TemplateObject::asNativeTemplateObject() const
+{
+    MOZ_ASSERT(isNative());
+    return *static_cast<const NativeTemplateObject*>(this);
+}
+
+inline bool
+NativeTemplateObject::hasDynamicSlots() const
+{
+    return asNative().hasDynamicSlots();
+}
+
+inline uint32_t
+NativeTemplateObject::numDynamicSlots() const
+{
+    // We can't call numDynamicSlots because that uses shape->base->clasp and
+    // shape->base can change when we create a ShapeTable.
+    return NativeObject::dynamicSlotsCount(numFixedSlots(), slotSpan(), obj_->getClass());
+}
+
+inline uint32_t
+NativeTemplateObject::numUsedFixedSlots() const
+{
+    return asNative().numUsedFixedSlots();
+}
+
+inline uint32_t
+NativeTemplateObject::numFixedSlots() const
+{
+    return asNative().numFixedSlots();
+}
+
+inline uint32_t
+NativeTemplateObject::slotSpan() const
+{
+    // Don't call NativeObject::slotSpan, it uses shape->base->clasp and the
+    // shape's BaseShape can change when we create a ShapeTable for it.
+    return asNative().shape()->slotSpan(obj_->getClass());
+}
+
+inline Value
+NativeTemplateObject::getSlot(uint32_t i) const
+{
+    return asNative().getSlot(i);
+}
+
+inline const Value*
+NativeTemplateObject::getDenseElements() const
+{
+    return asNative().getDenseElementsAllowCopyOnWrite();
+}
+
+#ifdef DEBUG
+inline bool
+NativeTemplateObject::isSharedMemory() const
+{
+    return asNative().isSharedMemory();
+}
+#endif
+
+inline uint32_t
+NativeTemplateObject::getDenseCapacity() const
+{
+    return asNative().getDenseCapacity();
+}
+
+inline uint32_t
+NativeTemplateObject::getDenseInitializedLength() const
+{
+    return asNative().getDenseInitializedLength();
+}
+
+inline uint32_t
+NativeTemplateObject::getArrayLength() const
+{
+    return obj_->as<ArrayObject>().length();
+}
+
+inline bool
+NativeTemplateObject::hasDynamicElements() const
+{
+    return asNative().hasDynamicElements();
+}
+
+inline bool
+NativeTemplateObject::hasPrivate() const
+{
+    return asNative().hasPrivate();
+}
+
+inline gc::Cell*
+NativeTemplateObject::regExpShared() const
+{
+    RegExpObject* regexp = &obj_->as<RegExpObject>();
+    MOZ_ASSERT(regexp->hasShared());
+    MOZ_ASSERT(regexp->getPrivate() == regexp->sharedRef().get());
+    return regexp->sharedRef().get();
+}
+
+inline void*
+NativeTemplateObject::getPrivate() const
+{
+    return asNative().getPrivate();
+}
+
+} // namespace jit
+} // namespace js
+
+#endif /* jit_TemplateObject_inl_h */
new file mode 100644
--- /dev/null
+++ b/js/src/jit/TemplateObject.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef jit_TemplateObject_h
+#define jit_TemplateObject_h
+
+#include "vm/NativeObject.h"
+#include "vm/Shape.h"
+#include "vm/UnboxedObject.h"
+
+namespace js {
+namespace jit {
+
+class NativeTemplateObject;
+
+// Wrapper for template objects. This should only expose methods that can be
+// safely called off-thread without racing with the main thread.
+class TemplateObject
+{
+  protected:
+    JSObject* obj_;
+    bool denseElementsAreCopyOnWrite_;
+    bool convertDoubleElements_;
+
+  public:
+    explicit TemplateObject(JSObject* obj)
+      : obj_(obj),
+        denseElementsAreCopyOnWrite_(false),
+        convertDoubleElements_(false)
+    {}
+    void setDenseElementsAreCopyOnWrite() { denseElementsAreCopyOnWrite_ = true; }
+    void setConvertDoubleElements() { convertDoubleElements_ = true; }
+
+    inline gc::AllocKind getAllocKind() const;
+
+    // The following methods rely on the object's group->clasp. This is safe
+    // to read off-thread for template objects.
+    inline bool isNative() const;
+    inline const NativeTemplateObject& asNativeTemplateObject() const;
+    inline bool isArrayObject() const;
+    inline bool isArgumentsObject() const;
+    inline bool isTypedArrayObject() const;
+    inline bool isRegExpObject() const;
+    inline bool isInlineTypedObject() const;
+    inline bool isUnboxedPlainObject() const;
+    inline bool isCallObject() const;
+    inline bool isPlainObject() const;
+
+    // The group and shape should not change. This is true for template objects
+    // because they're never exposed to arbitrary script.
+    inline gc::Cell* group() const;
+    inline gc::Cell* maybeShape() const;
+
+    // Some TypedObject and UnboxedPlainObject methods that can be called
+    // off-thread.
+    inline uint32_t getInlineTypedObjectSize() const;
+    inline uint8_t* getInlineTypedObjectMem(const JS::AutoRequireNoGC& nogc) const;
+    inline const UnboxedLayout& unboxedObjectLayout() const;
+#ifdef DEBUG
+    inline bool unboxedObjectHasExpando() const;
+#endif
+};
+
+class NativeTemplateObject : public TemplateObject
+{
+  protected:
+    NativeObject& asNative() const {
+        return obj_->as<NativeObject>();
+    }
+
+  public:
+    // Reading slot counts and object slots is safe, as long as we don't touch
+    // the BaseShape (it can change when we create a ShapeTable for the shape).
+    inline bool hasDynamicSlots() const;
+    inline uint32_t numDynamicSlots() const;
+    inline uint32_t numUsedFixedSlots() const;
+    inline uint32_t numFixedSlots() const;
+    inline uint32_t slotSpan() const;
+    inline Value getSlot(uint32_t i) const;
+
+    // Reading ObjectElements fields is safe, except for the flags (we can set
+    // the convert-double-elements flag on the main thread for COW elements).
+    // isSharedMemory is an exception: it's debug-only and not called on arrays.
+    bool denseElementsAreCopyOnWrite() const { return denseElementsAreCopyOnWrite_; }
+    bool convertDoubleElements() const { return convertDoubleElements_; }
+#ifdef DEBUG
+    inline bool isSharedMemory() const;
+#endif
+    inline uint32_t getDenseCapacity() const;
+    inline uint32_t getDenseInitializedLength() const;
+    inline uint32_t getArrayLength() const;
+    inline bool hasDynamicElements() const;
+    inline const Value* getDenseElements() const;
+
+    // Reading private slots is safe.
+    inline bool hasPrivate() const;
+    inline gc::Cell* regExpShared() const;
+    inline void* getPrivate() const;
+};
+
+} // namespace jit
+} // namespace js
+
+#endif /* jit_TemplateObject_h */
--- a/js/src/tests/lib/tests.py
+++ b/js/src/tests/lib/tests.py
@@ -11,39 +11,40 @@ from threading import Thread
 from results import TestOutput
 
 # When run on tbpl, we run each test multiple times with the following
 # arguments.
 JITFLAGS = {
     'all': [
         [], # no flags, normal baseline and ion
         ['--ion-eager', '--ion-offthread-compile=off'], # implies --baseline-eager
-        ['--ion-eager', '--ion-offthread-compile=off', '--non-writable-jitcode',
+        ['--ion-eager', '--ion-offthread-compile=off',
          '--ion-check-range-analysis', '--ion-extra-checks', '--no-sse3', '--no-threads'],
         ['--baseline-eager'],
         ['--no-baseline', '--no-ion'],
     ],
     # used by jit_test.py
     'ion': [
         ['--baseline-eager'],
         ['--ion-eager', '--ion-offthread-compile=off']
     ],
     # Run reduced variants on debug builds, since they take longer time.
     'debug': [
         [], # no flags, normal baseline and ion
         ['--ion-eager', '--ion-offthread-compile=off'], # implies --baseline-eager
         ['--baseline-eager'],
     ],
     # Cover cases useful for tsan. Note that tsan on try messes up the signal
-    # handler (bug 1362239), so must avoid wasm/asmjs.
+    # handler (bug 1362239), so must avoid wasm/asmjs. Also note that we test
+    # --ion-eager without --ion-offthread-compile=off here, because it helps
+    # catch races.
     'tsan': [
         ['--no-asmjs', '--no-wasm'],
-        ['--no-asmjs', '--no-wasm',
-         '--ion-eager', '--ion-offthread-compile=off', '--non-writable-jitcode',
-         '--ion-check-range-analysis', '--ion-extra-checks', '--no-sse3', '--no-threads'],
+        ['--no-asmjs', '--no-wasm', '--ion-eager',
+         '--ion-check-range-analysis', '--ion-extra-checks', '--no-sse3'],
         ['--no-asmjs', '--no-wasm', '--no-baseline', '--no-ion'],
     ],
     'baseline': [
         ['--no-ion'],
     ],
     # Interpreter-only, for tools that cannot handle binary code generation.
     'interp': [
         ['--no-baseline', '--no-asmjs', '--no-wasm', '--no-native-regexp']
--- a/js/src/vm/JSCompartment.cpp
+++ b/js/src/vm/JSCompartment.cpp
@@ -1028,16 +1028,29 @@ JSCompartment::setAllocationMetadataBuil
     // Clear any jitcode in the runtime, which behaves differently depending on
     // whether there is a creation callback.
     ReleaseAllJITCode(runtime_->defaultFreeOp());
 
     allocationMetadataBuilder = builder;
 }
 
 void
+JSCompartment::forgetAllocationMetadataBuilder()
+{
+    // Unlike setAllocationMetadataBuilder, we don't have to discard all JIT
+    // code here (code is still valid, just a bit slower because it doesn't do
+    // inline GC allocations when a metadata builder is present), but we do want
+    // to cancel off-thread Ion compilations to avoid races when Ion calls
+    // hasAllocationMetadataBuilder off-thread.
+    CancelOffThreadIonCompile(this);
+
+    allocationMetadataBuilder = nullptr;
+}
+
+void
 JSCompartment::clearObjectMetadata()
 {
     js_delete(objectMetadataTable);
     objectMetadataTable = nullptr;
 }
 
 void
 JSCompartment::setNewObjectMetadata(JSContext* cx, HandleObject obj)
--- a/js/src/vm/JSCompartment.h
+++ b/js/src/vm/JSCompartment.h
@@ -951,19 +951,17 @@ struct JSCompartment
     void fixupGlobal();
     void fixupScriptMapsAfterMovingGC();
 
     bool hasAllocationMetadataBuilder() const { return allocationMetadataBuilder; }
     const js::AllocationMetadataBuilder* getAllocationMetadataBuilder() const {
         return allocationMetadataBuilder;
     }
     void setAllocationMetadataBuilder(const js::AllocationMetadataBuilder* builder);
-    void forgetAllocationMetadataBuilder() {
-        allocationMetadataBuilder = nullptr;
-    }
+    void forgetAllocationMetadataBuilder();
     void setNewObjectMetadata(JSContext* cx, JS::HandleObject obj);
     void clearObjectMetadata();
     const void* addressOfMetadataBuilder() const {
         return &allocationMetadataBuilder;
     }
 
     js::SavedStacks& savedStacks() { return savedStacks_; }
 
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -127,24 +127,25 @@ UnboxedLayout::makeConstructorCode(JSCon
         masm.push(ScratchDoubleReg);
 
     Label failure, tenuredObject, allocated;
     masm.branch32(Assembler::NotEqual, newKindReg, Imm32(GenericObject), &tenuredObject);
     masm.branchTest32(Assembler::NonZero, AbsoluteAddress(group->addressOfFlags()),
                       Imm32(OBJECT_FLAG_PRE_TENURE), &tenuredObject);
 
     // Allocate an object in the nursery
-    masm.createGCObject(object, scratch1, templateObject, gc::DefaultHeap, &failure,
+    TemplateObject templateObj(templateObject);
+    masm.createGCObject(object, scratch1, templateObj, gc::DefaultHeap, &failure,
                         /* initFixedSlots = */ false);
 
     masm.jump(&allocated);
     masm.bind(&tenuredObject);
 
     // Allocate an object in the tenured heap.
-    masm.createGCObject(object, scratch1, templateObject, gc::TenuredHeap, &failure,
+    masm.createGCObject(object, scratch1, templateObj, gc::TenuredHeap, &failure,
                         /* initFixedSlots = */ false);
 
     // If any of the properties being stored are in the nursery, add a store
     // buffer entry for the new object.
     Label postBarrier;
     for (size_t i = 0; i < layout.properties().length(); i++) {
         const UnboxedLayout::Property& property = layout.properties()[i];
         if (!UnboxedTypeNeedsPostBarrier(property.type))
@@ -262,17 +263,17 @@ UnboxedLayout::makeConstructorCode(JSCon
     masm.abiret();
 #endif
 
     masm.bind(&failureStoreOther);
 
     // There was a failure while storing a value which cannot be stored at all
     // in the unboxed object. Initialize the object so it is safe for GC and
     // return null.
-    masm.initUnboxedObjectContents(object, templateObject);
+    masm.initUnboxedObjectContents(object, templateObject->layoutDontCheckGeneration());
 
     masm.bind(&failure);
 
     masm.movePtr(ImmWord(0), object);
     masm.jump(&done);
 
     masm.bind(&failureStoreObject);
 
@@ -283,17 +284,17 @@ UnboxedLayout::makeConstructorCode(JSCon
     {
         Label isObject;
         masm.branchTestObject(Assembler::Equal, valueOperand, &isObject);
         masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther);
         masm.bind(&isObject);
     }
 
     // Initialize the object so it is safe for GC.
-    masm.initUnboxedObjectContents(object, templateObject);
+    masm.initUnboxedObjectContents(object, templateObject->layoutDontCheckGeneration());
 
     masm.movePtr(ImmWord(CLEAR_CONSTRUCTOR_CODE_TOKEN), object);
     masm.jump(&done);
 
     Linker linker(masm);
     AutoFlushICache afc("UnboxedObject");
     JitCode* code = linker.newCode(cx, CodeKind::Other);
     if (!code)
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -2079,17 +2079,18 @@ nsPresContext::FlushPendingMediaFeatureV
   }
 
   // We build a list of all the notifications we're going to send
   // before we send any of them.
 
   // Copy pointers to all the lists into a new array, in case one of our
   // notifications modifies the list.
   nsTArray<RefPtr<mozilla::dom::MediaQueryList>> localMediaQueryLists;
-  for (auto* mql : mDocument->MediaQueryLists()) {
+  for (MediaQueryList* mql = mDocument->MediaQueryLists().getFirst(); mql;
+       mql = static_cast<LinkedListElement<MediaQueryList>*>(mql)->getNext()) {
     localMediaQueryLists.AppendElement(mql);
   }
 
   // Now iterate our local array of the lists.
   for (const auto& mql : localMediaQueryLists) {
     nsAutoMicroTask mt;
     mql->MaybeNotify();
   }
--- a/layout/style/MediaQueryList.cpp
+++ b/layout/style/MediaQueryList.cpp
@@ -40,17 +40,17 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(MediaQuer
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaQueryList,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaQueryList,
                                                 DOMEventTargetHelper)
   if (tmp->mDocument) {
-    tmp->remove();
+    static_cast<LinkedListElement<MediaQueryList>*>(tmp)->remove();
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
   }
   tmp->Disconnect();
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaQueryList)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
--- a/layout/tools/reftest/selftest/test_reftest_output.py
+++ b/layout/tools/reftest/selftest/test_reftest_output.py
@@ -16,17 +16,17 @@ from mozharness.mozilla.buildbot import 
 here = os.path.abspath(os.path.dirname(__file__))
 get_mozharness_status = partial(get_mozharness_status, 'reftest')
 
 
 def test_output_pass(runtests):
     status, lines = runtests('reftest-pass.list')
     assert status == 0
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_SUCCESS
     assert log_level in (INFO, WARNING)
 
     test_status = filter_action('test_status', lines)
     assert len(test_status) == 3
     assert all(t['status'] == 'PASS' for t in test_status)
 
     test_end = filter_action('test_end', lines)
@@ -36,17 +36,17 @@ def test_output_pass(runtests):
 
 def test_output_fail(runtests):
     formatter = pytest.importorskip('output').ReftestFormatter()
 
     status, lines = runtests('reftest-fail.list')
     assert status == 0
 
     buf = StringIO()
-    tbpl_status, log_level = get_mozharness_status(
+    tbpl_status, log_level, summary = get_mozharness_status(
         lines, status, formatter=formatter, buf=buf)
 
     assert tbpl_status == TBPL_WARNING
     assert log_level == WARNING
 
     test_status = filter_action('test_status', lines)
     assert len(test_status) == 3
     assert all(t['status'] == 'FAIL' for t in test_status)
@@ -62,17 +62,17 @@ def test_output_fail(runtests):
     assert 'REFTEST   IMAGE 2' in formatted
 
 
 @pytest.mark.skip_mozinfo("!crashreporter")
 def test_output_crash(runtests):
     status, lines = runtests('reftest-crash.list', environment=["MOZ_CRASHREPORTER_SHUTDOWN=1"])
     assert status == 1
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_FAILURE
     assert log_level == ERROR
 
     crash = filter_action('crash', lines)
     assert len(crash) == 1
     assert crash[0]['action'] == 'crash'
     assert crash[0]['signature']
     assert crash[0]['minidump_path']
@@ -81,33 +81,33 @@ def test_output_crash(runtests):
     assert len(lines) == 0
 
 
 @pytest.mark.skip_mozinfo("!asan")
 def test_output_asan(runtests):
     status, lines = runtests('reftest-crash.list', environment=["MOZ_CRASHREPORTER_SHUTDOWN=1"])
     assert status == 0
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_FAILURE
     assert log_level == ERROR
 
     crash = filter_action('crash', lines)
     assert len(crash) == 0
 
     process_output = filter_action('process_output', lines)
     assert any('ERROR: AddressSanitizer' in l['data'] for l in process_output)
 
 
 @pytest.mark.skip_mozinfo("!debug")
 def test_output_assertion(runtests):
     status, lines = runtests('reftest-assert.list')
     assert status == 0
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_WARNING
     assert log_level == WARNING
 
     test_status = filter_action('test_status', lines)
     assert len(test_status) == 1
     assert test_status[0]['status'] == 'PASS'
 
     test_end = filter_action('test_end', lines)
@@ -129,17 +129,17 @@ def test_output_leak(monkeypatch, runtes
     def process_leak_log(*args, **kwargs):
         return old_process_leak_log(os.path.join(here, 'files', 'leaks.log'), *args[1:], **kwargs)
 
     monkeypatch.setattr('mozleak.process_leak_log', process_leak_log)
 
     status, lines = runtests('reftest-pass.list')
     assert status == 0
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_FAILURE
     assert log_level == ERROR
 
     errors = filter_action('log', lines)
     errors = [e for e in errors if e['level'] == 'ERROR']
     assert len(errors) == 1
     assert 'leakcheck' in errors[0]['message']
 
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -2867,17 +2867,17 @@ var NativeWindow = {
       // If the event was already defaultPrevented by somebody (web content, or
       // some other part of gecko), then don't do anything with it.
       if (event.defaultPrevented) {
         return;
       }
 
       // Use the highlighted element for the context menu target. When accessibility is
       // enabled, elements may not be highlighted so use the event target instead.
-      this._target = BrowserEventHandler._highlightElement || event.target;
+      this._target = BrowserEventHandler._highlightElement || event.composedTarget;
       if (!this._target) {
         return;
       }
 
       // Try to build a list of contextmenu items. If successful, actually show the
       // native context menu by passing the list to Java.
       this._buildMenu(event.clientX, event.clientY);
       if (this._shouldShow()) {
@@ -4851,17 +4851,17 @@ var BrowserEventHandler = {
 
   _handleRetargetedTouchStart: function(aEvent) {
     // we should only get this called just after a new touchstart with a single
     // touch point.
     if (!BrowserApp.isBrowserContentDocumentDisplayed() || aEvent.defaultPrevented) {
       return;
     }
 
-    let target = aEvent.target;
+    let target = aEvent.composedTarget;
     if (!target) {
       return;
     }
 
     this._inCluster = aEvent.hitCluster;
     if (this._inCluster) {
       return;  // No highlight for a cluster of links
     }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
@@ -16,17 +16,16 @@ import org.mozilla.geckoview.test.rule.G
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
 import org.mozilla.geckoview.test.util.Callbacks
 
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
 
 import org.hamcrest.Matchers.*
 import org.junit.Assume.assumeThat
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
 /**
  * Test for the GeckoSessionTestRule class, to ensure it properly sets up a session for
  * each test, and to ensure it can properly wait for and assert delegate
  * callbacks.
  */
@@ -1174,17 +1173,16 @@ class GeckoSessionTestRuleTest : BaseSes
         sessionRule.session.loadTestPath(CLICK_TO_RELOAD_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         sessionRule.session.synthesizeTap(5, 5)
         sessionRule.session.waitForPageStop()
     }
 
     @WithDevToolsAPI
-    @Ignore
     @Test fun evaluateJS() {
         assertThat("JS string result should be correct",
                    sessionRule.session.evaluateJS("'foo'") as String, equalTo("foo"))
 
         assertThat("JS number result should be correct",
                    sessionRule.session.evaluateJS("1+1") as Double, equalTo(2.0))
 
         assertThat("JS boolean result should be correct",
@@ -1203,45 +1201,42 @@ class GeckoSessionTestRuleTest : BaseSes
                    hasEntry("tagName", "BODY"))
 
         assertThat("JS DOM array result should be correct",
                    sessionRule.session.evaluateJS("document.childNodes").asJSList<Any>(),
                    not(empty()))
     }
 
     @WithDevToolsAPI
-    @Ignore
     @Test fun evaluateJS_windowObject() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         // Make sure we can access large objects like "window", which can strain our RDP connection.
         // Also make sure we can dynamically access sub-objects like Location.
         assertThat("JS DOM window result should be correct",
                    (sessionRule.session.evaluateJS("window")
                            dot "location"
                            dot "pathname") as String,
                    equalTo(HELLO_HTML_PATH))
     }
 
     @WithDevToolsAPI
-    @Ignore
     @Test fun evaluateJS_multipleSessions() {
         val newSession = sessionRule.createOpenSession()
 
         sessionRule.session.evaluateJS("this.foo = 42")
         assertThat("Variable should be set",
                    sessionRule.session.evaluateJS("this.foo") as Double, equalTo(42.0))
 
         assertThat("New session should have separate JS context",
                    newSession.evaluateJS("this.foo"), nullValue())
     }
 
     @WithDevToolsAPI
-    @Ignore
     @Test fun evaluateJS_jsToString() {
         val obj = sessionRule.session.evaluateJS("({foo:'bar'})")
         assertThat("JS object toString should follow lazy evaluation",
                    obj.toString(), equalTo("[Object]"))
 
         assertThat("JS object should be correct",
                    (obj dot "foo") as String, equalTo("bar"))
         assertThat("JS object toString should be expanded after evaluation",
@@ -1258,21 +1253,19 @@ class GeckoSessionTestRuleTest : BaseSes
     }
 
     @Test(expected = AssertionError::class)
     fun evaluateJS_throwOnNotWithDevTools() {
         sessionRule.session.evaluateJS("0")
     }
 
     @WithDevToolsAPI
-    @Ignore
     @Test(expected = RuntimeException::class)
     fun evaluateJS_throwOnJSException() {
         sessionRule.session.evaluateJS("throw Error()")
     }
 
     @WithDevToolsAPI
-    @Ignore
     @Test(expected = RuntimeException::class)
     fun evaluateJS_throwOnSyntaxError() {
         sessionRule.session.evaluateJS("<{[")
     }
 }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
@@ -720,17 +720,17 @@ public class GeckoSessionTestRule extend
     }
 
     protected void applyAnnotations(final Collection<Annotation> annotations,
                                     final GeckoSessionSettings settings) {
         for (final Annotation annotation : annotations) {
             if (TimeoutMillis.class.equals(annotation.annotationType())) {
                 // Scale timeout based on the default timeout to account for the device under test.
                 final long value = ((TimeoutMillis) annotation).value();
-                final long timeout = value * getDefaultTimeoutMillis() / DEFAULT_TIMEOUT_MILLIS;
+                final long timeout = value * getScaledTimeoutMillis() / DEFAULT_TIMEOUT_MILLIS;
                 mTimeoutMillis = Math.max(timeout, 1000);
             } else if (Setting.class.equals(annotation.annotationType())) {
                 ((Setting) annotation).key().set(settings, ((Setting) annotation).value());
             } else if (Setting.List.class.equals(annotation.annotationType())) {
                 for (final Setting setting : ((Setting.List) annotation).value()) {
                     setting.key().set(settings, setting.value());
                 }
             } else if (NullDelegate.class.equals(annotation.annotationType())) {
@@ -756,29 +756,33 @@ public class GeckoSessionTestRule extend
             return (RuntimeException) cause;
         } else if (e instanceof RuntimeException) {
             return (RuntimeException) e;
         }
 
         return new RuntimeException(cause != null ? cause : e);
     }
 
-    private long getDefaultTimeoutMillis() {
+    private long getScaledTimeoutMillis() {
         if ("x86".equals(env.getCPUArch())) {
             return env.isEmulator() ? DEFAULT_X86_EMULATOR_TIMEOUT_MILLIS
                                     : DEFAULT_X86_DEVICE_TIMEOUT_MILLIS;
         }
         return env.isEmulator() ? DEFAULT_ARM_EMULATOR_TIMEOUT_MILLIS
                                 : DEFAULT_ARM_DEVICE_TIMEOUT_MILLIS;
     }
 
+    private long getDefaultTimeoutMillis() {
+        return env.isDebugging() ? DEFAULT_IDE_DEBUG_TIMEOUT_MILLIS
+                                 : getScaledTimeoutMillis();
+    }
+
     protected void prepareStatement(final Description description) throws Throwable {
         final GeckoSessionSettings settings = new GeckoSessionSettings(mDefaultSettings);
-        mTimeoutMillis = env.isDebugging() ? DEFAULT_IDE_DEBUG_TIMEOUT_MILLIS
-                                           : getDefaultTimeoutMillis();
+        mTimeoutMillis = getDefaultTimeoutMillis();
         mNullDelegates = new HashSet<>();
         mClosedSession = false;
         mWithDevTools = false;
 
         applyAnnotations(Arrays.asList(description.getTestClass().getAnnotations()), settings);
         applyAnnotations(description.getAnnotations(), settings);
 
         final List<CallRecord> records = new ArrayList<>();
@@ -890,18 +894,17 @@ public class GeckoSessionTestRule extend
         if (mWithDevTools) {
             if (sRDPConnection == null) {
                 final String dataDir = InstrumentationRegistry.getTargetContext()
                                                               .getApplicationInfo().dataDir;
                 final LocalSocketAddress address = new LocalSocketAddress(
                         dataDir + "/firefox-debugger-socket",
                         LocalSocketAddress.Namespace.FILESYSTEM);
                 sRDPConnection = new RDPConnection(address);
-                sRDPConnection.setTimeout((int) Math.min(DEFAULT_TIMEOUT_MILLIS,
-                                                         Integer.MAX_VALUE));
+                sRDPConnection.setTimeout((int) mTimeoutMillis);
             }
             final Tab tab = sRDPConnection.getMostRecentTab();
             tab.attach();
             mRDPTabs.put(session, tab);
         }
     }
 
     private void waitForInitialLoad(final GeckoSession session) {
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -279,18 +279,17 @@ nsSocketInputStream::OnSocketReady(nsres
 
         // update condition, but be careful not to erase an already
         // existing error condition.
         if (NS_SUCCEEDED(mCondition))
             mCondition = condition;
 
         // ignore event if only waiting for closure and not closed.
         if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
-            callback = mCallback;
-            mCallback = nullptr;
+            callback = mCallback.forget();
             mCallbackFlags = 0;
         }
     }
 
     if (callback)
         callback->OnInputStreamReady(this);
 }
 
@@ -540,18 +539,17 @@ nsSocketOutputStream::OnSocketReady(nsre
 
         // update condition, but be careful not to erase an already
         // existing error condition.
         if (NS_SUCCEEDED(mCondition))
             mCondition = condition;
 
         // ignore event if only waiting for closure and not closed.
         if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
-            callback = mCallback;
-            mCallback = nullptr;
+            callback = mCallback.forget();
             mCallbackFlags = 0;
         }
     }
 
     if (callback)
         callback->OnOutputStreamReady(this);
 }
 
--- a/old-configure.in
+++ b/old-configure.in
@@ -1768,17 +1768,17 @@ dnl = If NSS was not detected in the sys
 dnl = use the one in the source tree (mozilla/security/nss)
 dnl ========================================================
 
 MOZ_ARG_WITH_BOOL(system-nss,
 [  --with-system-nss       Use system installed NSS],
     _USE_SYSTEM_NSS=1 )
 
 if test -n "$_USE_SYSTEM_NSS"; then
-    AM_PATH_NSS(3.36, [MOZ_SYSTEM_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
+    AM_PATH_NSS(3.37, [MOZ_SYSTEM_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
 fi
 
 if test -z "$MOZ_SYSTEM_NSS"; then
    NSS_CFLAGS="-I${DIST}/include/nss"
    case "${OS_ARCH}" in
         # Only few platforms have been tested with GYP
         WINNT|Darwin|Linux|DragonFly|FreeBSD|NetBSD|OpenBSD|SunOS)
             ;;
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-NSS_3_37_BETA2
+NSS_3_37_RTM
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,9 +5,8 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
-
--- a/security/nss/lib/nss/nss.h
+++ b/security/nss/lib/nss/nss.h
@@ -17,22 +17,22 @@
 
 /*
  * NSS's major version, minor version, patch level, build number, and whether
  * this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]"
  */
-#define NSS_VERSION "3.37" _NSS_CUSTOMIZED " Beta"
+#define NSS_VERSION "3.37" _NSS_CUSTOMIZED
 #define NSS_VMAJOR 3
 #define NSS_VMINOR 37
 #define NSS_VPATCH 0
 #define NSS_VBUILD 0
-#define NSS_BETA PR_TRUE
+#define NSS_BETA PR_FALSE
 
 #ifndef RC_INVOKED
 
 #include "seccomon.h"
 
 typedef struct NSSInitParametersStr NSSInitParameters;
 
 /*
--- a/security/nss/lib/softoken/softkver.h
+++ b/security/nss/lib/softoken/softkver.h
@@ -12,16 +12,16 @@
 
 /*
  * Softoken's major version, minor version, patch level, build number,
  * and whether this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]"
  */
-#define SOFTOKEN_VERSION "3.37" SOFTOKEN_ECC_STRING " Beta"
+#define SOFTOKEN_VERSION "3.37" SOFTOKEN_ECC_STRING
 #define SOFTOKEN_VMAJOR 3
 #define SOFTOKEN_VMINOR 37
 #define SOFTOKEN_VPATCH 0
 #define SOFTOKEN_VBUILD 0
-#define SOFTOKEN_BETA PR_TRUE
+#define SOFTOKEN_BETA PR_FALSE
 
 #endif /* _SOFTKVER_H_ */
--- a/security/nss/lib/util/nssutil.h
+++ b/security/nss/lib/util/nssutil.h
@@ -14,22 +14,22 @@
 
 /*
  * NSS utilities's major version, minor version, patch level, build number,
  * and whether this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <Beta>]"
  */
-#define NSSUTIL_VERSION "3.37 Beta"
+#define NSSUTIL_VERSION "3.37"
 #define NSSUTIL_VMAJOR 3
 #define NSSUTIL_VMINOR 37
 #define NSSUTIL_VPATCH 0
 #define NSSUTIL_VBUILD 0
-#define NSSUTIL_BETA PR_TRUE
+#define NSSUTIL_BETA PR_FALSE
 
 SEC_BEGIN_PROTOS
 
 /*
  * Returns a const string of the UTIL library version.
  */
 extern const char *NSSUTIL_GetVersion(void);
 
--- a/taskcluster/ci/release-bouncer-aliases/kind.yml
+++ b/taskcluster/ci/release-bouncer-aliases/kind.yml
@@ -68,23 +68,24 @@ jobs:
                firefox-beta-latest-ssl: installer-ssl
                firefox-beta-latest: installer
                firefox-beta-stub: stub-installer
             mozilla-release:
                firefox-latest-ssl: installer-ssl
                firefox-latest: installer
                firefox-stub: stub-installer
             mozilla-esr60:
-               firefox-esr-latest-ssl: installer-ssl
-               firefox-esr-latest: installer
+               # convert to firefox-esr-latest when ESR52 stops
+               firefox-esr-next-latest-ssl: installer-ssl
+               firefox-esr-next-latest: installer
             birch:
                firefox-latest-ssl: installer-ssl
                firefox-latest: installer
                firefox-stub: stub-installer
             jamun:
-               firefox-esr-latest-ssl: installer-ssl
-               firefox-esr-latest: installer
+               firefox-esr-next-latest-ssl: installer-ssl
+               firefox-esr-next-latest: installer
             maple:
                firefox-beta-latest-ssl: installer-ssl
                firefox-beta-latest: installer
                firefox-beta-stub: stub-installer
             default: {}
       shipping-product: firefox
--- a/taskcluster/ci/release-sign-and-push-langpacks/kind.yml
+++ b/taskcluster/ci/release-sign-and-push-langpacks/kind.yml
@@ -28,17 +28,20 @@ job-template:
          mozilla-release: scriptworker-prov-v1/addon-v1
          mozilla-esr60: 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
+            mozilla-release:
+               by-platform:
+                  macosx64.*: unlisted  # ja-JP-mac is only langpack on mac, and is unlisted always
+                  default: listed
             default: unlisted
       upstream-artifacts:   # See transforms
    run-on-projects: []
    scopes:
       by-project:
          mozilla-beta:
             - project:releng:addons.mozilla.org:server:production
          mozilla-release:
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -273,17 +273,17 @@ macosx64-talos:
     - 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
+    # - talos-h1 # too long to unpack profile- Bug 1442893
 
 macosx64-talos-profiling:
     - talos-chrome-profiling
     - talos-damp-profiling
     - talos-dromaeojs-profiling
     - talos-g1-profiling
     - talos-g3-profiling
     - talos-g4-profiling
--- a/taskcluster/taskgraph/transforms/release_sign_and_push_langpacks.py
+++ b/taskcluster/taskgraph/transforms/release_sign_and_push_langpacks.py
@@ -24,17 +24,19 @@ 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('channel'): optionally_keyed_by(
+            'project',
+            optionally_keyed_by('platform', 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'],
 })
@@ -64,17 +66,19 @@ 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']
+            job, 'worker.channel', item_name=job['label'],
+            project=config.params['project'],
+            platform=job['dependent-task'].attributes['build_platform'],
         )
 
         yield job
 
 
 @transforms.add
 def copy_attributes(config, jobs):
     for job in jobs:
--- a/testing/mochitest/tests/python/test_basic_mochitest_plain.py
+++ b/testing/mochitest/tests/python/test_basic_mochitest_plain.py
@@ -16,45 +16,45 @@ from mozharness.mozilla.buildbot import 
 here = os.path.abspath(os.path.dirname(__file__))
 get_mozharness_status = partial(get_mozharness_status, 'mochitest')
 
 
 def test_output_pass(runtests):
     status, lines = runtests('test_pass.html')
     assert status == 0
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_SUCCESS
     assert log_level in (INFO, WARNING)
 
     lines = filter_action('test_status', lines)
     assert len(lines) == 1
     assert lines[0]['status'] == 'PASS'
 
 
 def test_output_fail(runtests):
     status, lines = runtests('test_fail.html')
     assert status == 1
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_WARNING
     assert log_level == WARNING
 
     lines = filter_action('test_status', lines)
 
     assert len(lines) == 1
     assert lines[0]['status'] == 'FAIL'
 
 
 @pytest.mark.skip_mozinfo("!crashreporter")
 def test_output_crash(runtests):
     status, lines = runtests('test_crash.html', environment=["MOZ_CRASHREPORTER_SHUTDOWN=1"])
     assert status == 1
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_FAILURE
     assert log_level == ERROR
 
     crash = filter_action('crash', lines)
     assert len(crash) == 1
     assert crash[0]['action'] == 'crash'
     assert crash[0]['signature']
     assert crash[0]['minidump_path']
@@ -63,34 +63,34 @@ def test_output_crash(runtests):
     assert len(lines) == 0
 
 
 @pytest.mark.skip_mozinfo("!asan")
 def test_output_asan(runtests):
     status, lines = runtests('test_crash.html', environment=["MOZ_CRASHREPORTER_SHUTDOWN=1"])
     assert status == 1
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_FAILURE
     assert log_level == ERROR
 
     crash = filter_action('crash', lines)
     assert len(crash) == 0
 
     process_output = filter_action('process_output', lines)
     assert any('ERROR: AddressSanitizer' in l['data'] for l in process_output)
 
 
 @pytest.mark.skip_mozinfo("!debug")
 def test_output_assertion(runtests):
     status, lines = runtests('test_assertion.html')
     # TODO: mochitest should return non-zero here
     assert status == 0
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_WARNING
     assert log_level == WARNING
 
     test_end = filter_action('test_end', lines)
     assert len(test_end) == 1
     # TODO: this should be ASSERT, but moving the assertion check before
     # the test_end action caused a bunch of failures.
     assert test_end[0]['status'] == 'OK'
@@ -111,17 +111,17 @@ def test_output_leak(monkeypatch, runtes
         return old_process_leak_log(os.path.join(here, 'files', 'leaks.log'), *args[1:], **kwargs)
 
     monkeypatch.setattr('mozleak.process_leak_log', process_leak_log)
 
     status, lines = runtests('test_pass.html')
     # TODO: mochitest should return non-zero here
     assert status == 0
 
-    tbpl_status, log_level = get_mozharness_status(lines, status)
+    tbpl_status, log_level, summary = get_mozharness_status(lines, status)
     assert tbpl_status == TBPL_FAILURE
     assert log_level == ERROR
 
     errors = filter_action('log', lines)
     errors = [e for e in errors if e['level'] == 'ERROR']
     assert len(errors) == 1
     assert 'leakcheck' in errors[0]['message']
 
deleted file mode 100644
--- a/testing/mozharness/configs/releases/bouncer_fennec.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# lint_ignore=E501
-config = {
-    "bouncer_prefix": "https://download.mozilla.org/",
-    "products": {
-        "apk": {
-            "product-name": "Fennec-%(version)s",
-            "check_uptake": True,
-            "alias": "fennec-latest",
-            "ssl-only": True,
-            "add-locales": False,  # Do not add locales to let "multi" work
-            "paths": {
-                "android-api-16": {
-                    "path": "/mobile/releases/%(version)s/android-api-16/:lang/fennec-%(version)s.:lang.android-arm.apk",
-                    "bouncer-platform": "android",
-                },
-                "android-x86": {
-                    "path": "/mobile/releases/%(version)s/android-x86/:lang/fennec-%(version)s.:lang.android-i386.apk",
-                    "bouncer-platform": "android-x86",
-                },
-            },
-        },
-    },
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/bouncer_fennec_beta.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# lint_ignore=E501
-config = {
-    "bouncer_prefix": "https://download.mozilla.org/",
-    "products": {
-        "apk": {
-            "product-name": "Fennec-%(version)s",
-            "check_uptake": True,
-            "alias": "fennec-beta-latest",
-            "ssl-only": True,
-            "add-locales": False,  # Do not add locales to let "multi" work
-            "paths": {
-                "android-api-16": {
-                    "path": "/mobile/releases/%(version)s/android-api-16/:lang/fennec-%(version)s.:lang.android-arm.apk",
-                    "bouncer-platform": "android",
-                },
-                "android-x86": {
-                    "path": "/mobile/releases/%(version)s/android-x86/:lang/fennec-%(version)s.:lang.android-i386.apk",
-                    "bouncer-platform": "android-x86",
-                },
-            },
-        },
-    },
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/bouncer_firefox_beta.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# lint_ignore=E501
-config = {
-    "shipped-locales-url": "https://hg.mozilla.org/%(repo)s/raw-file/%(revision)s/browser/locales/shipped-locales",
-    "bouncer_prefix": "https://download.mozilla.org/",
-    "products": {
-        "installer": {
-            "product-name": "Firefox-%(version)s",
-            "check_uptake": True,
-            "alias": "firefox-beta-latest",
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "installer-ssl": {
-            "product-name": "Firefox-%(version)s-SSL",
-            "check_uptake": True,
-            "alias": "firefox-beta-latest-ssl",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "stub-installer": {
-            "product-name": "Firefox-%(version)s-stub",
-            "check_uptake": True,
-            "alias": "firefox-beta-stub",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Installer.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Installer.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "complete-mar": {
-            "product-name": "Firefox-%(version)s-Complete",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/update/mac/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/update/win32/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/update/win64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-    "partials": {
-        "releases-dir": {
-            "product-name": "Firefox-%(version)s-Partial-%(prev_version)s",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/update/mac/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/update/win32/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/update/win64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/bouncer_firefox_devedition.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# lint_ignore=E501
-config = {
-    "shipped-locales-url": "https://hg.mozilla.org/%(repo)s/raw-file/%(revision)s/browser/locales/shipped-locales",
-    "bouncer_prefix": "https://download.mozilla.org/",
-    "products": {
-        "installer": {
-            "product-name": "Devedition-%(version)s",
-            "check_uptake": True,
-            "alias": "firefox-devedition-latest",
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/devedition/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/devedition/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/devedition/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/devedition/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/devedition/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "installer-ssl": {
-            "product-name": "Devedition-%(version)s-SSL",
-            "check_uptake": True,
-            "alias": "firefox-devedition-latest-ssl",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/devedition/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/devedition/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/devedition/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/devedition/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/devedition/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "stub-installer": {
-            "product-name": "Devedition-%(version)s-stub",
-            "check_uptake": True,
-            "alias": "firefox-devedition-stub",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "win32": {
-                    "path": "/devedition/releases/%(version)s/win32/:lang/Firefox%%20Installer.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/devedition/releases/%(version)s/win32/:lang/Firefox%%20Installer.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "complete-mar": {
-            "product-name": "Devedition-%(version)s-Complete",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/devedition/releases/%(version)s/update/linux-i686/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/devedition/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/devedition/releases/%(version)s/update/mac/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/devedition/releases/%(version)s/update/win32/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/devedition/releases/%(version)s/update/win64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-    "partials": {
-        "releases-dir": {
-            "product-name": "Devedition-%(version)s-Partial-%(prev_version)s",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/devedition/releases/%(version)s/update/linux-i686/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/devedition/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/devedition/releases/%(version)s/update/mac/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/devedition/releases/%(version)s/update/win32/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/devedition/releases/%(version)s/update/win64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/bouncer_firefox_esr.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# lint_ignore=E501
-config = {
-    "shipped-locales-url": "https://hg.mozilla.org/%(repo)s/raw-file/%(revision)s/browser/locales/shipped-locales",
-    "bouncer_prefix": "https://download.mozilla.org/",
-    "products": {
-        "installer": {
-            "product-name": "Firefox-%(version)s",
-            "check_uptake": True,
-            "alias": "firefox-esr-latest",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "installer-ssl": {
-            "product-name": "Firefox-%(version)s-SSL",
-            "check_uptake": True,
-            "alias": "firefox-esr-latest-ssl",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "sha1-installer": {
-            "product-name": "Firefox-%(version)s-sha1",
-            "check_uptake": True,
-            # XP/Vista Release users are redicted to ESR52
-            "alias": "firefox-sha1",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32-sha1/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-            },
-        },
-        "complete-mar": {
-            "product-name": "Firefox-%(version)s-Complete",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/update/mac/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/update/win32/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/update/win64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-    "partials": {
-        "releases-dir": {
-            "product-name": "Firefox-%(version)s-Partial-%(prev_version)s",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/update/mac/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/update/win32/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/update/win64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/bouncer_firefox_release.py
+++ /dev/null
@@ -1,196 +0,0 @@
-# lint_ignore=E501
-config = {
-    "shipped-locales-url": "https://hg.mozilla.org/%(repo)s/raw-file/%(revision)s/browser/locales/shipped-locales",
-    "bouncer_prefix": "https://download.mozilla.org/",
-    "products": {
-        "installer": {
-            "product-name": "Firefox-%(version)s",
-            "check_uptake": True,
-            "alias": "firefox-latest",
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "installer-ssl": {
-            "product-name": "Firefox-%(version)s-SSL",
-            "check_uptake": True,
-            "alias": "firefox-latest-ssl",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "stub-installer": {
-            "product-name": "Firefox-%(version)s-stub",
-            "check_uptake": True,
-            "alias": "firefox-stub",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Installer.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Installer.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "complete-mar": {
-            "product-name": "Firefox-%(version)s-Complete",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/update/mac/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/update/win32/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/update/win64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "complete-mar-candidates": {
-            "product-name": "Firefox-%(version)sbuild%(build_number)s-Complete",
-            "check_uptake": False,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/candidates/%(version)s-candidates/build%(build_number)s/update/linux-i686/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/candidates/%(version)s-candidates/build%(build_number)s/update/linux-x86_64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/candidates/%(version)s-candidates/build%(build_number)s/update/mac/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/candidates/%(version)s-candidates/build%(build_number)s/update/win32/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/candidates/%(version)s-candidates/build%(build_number)s/update/win64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-    "partials": {
-        "releases-dir": {
-            "product-name": "Firefox-%(version)s-Partial-%(prev_version)s",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/update/mac/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/update/win32/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/update/win64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "candidates-dir": {
-            "product-name": "Firefox-%(version)sbuild%(build_number)s-Partial-%(prev_version)sbuild%(prev_build_number)s",
-            "check_uptake": False,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/candidates/%(version)s-candidates/build%(build_number)s/update/linux-i686/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/candidates/%(version)s-candidates/build%(build_number)s/update/linux-x86_64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/candidates/%(version)s-candidates/build%(build_number)s/update/mac/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/candidates/%(version)s-candidates/build%(build_number)s/update/win32/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/candidates/%(version)s-candidates/build%(build_number)s/update/win64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/dev_bouncer_firefox_beta.py
+++ /dev/null
@@ -1,139 +0,0 @@
-# lint_ignore=E501
-config = {
-    "bouncer_prefix": "https://bouncer-bouncer-releng.stage.mozaws.net/",
-    "locales": ["en-US"],
-    "products": {
-        "installer": {
-            "product-name": "Firefox-%(version)s",
-            "check_uptake": True,
-            "alias": "firefox-beta-latest",
-            "ssl-only": False,
-            "add-locales": False,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "installer-ssl": {
-            "product-name": "Firefox-%(version)s-SSL",
-            "check_uptake": True,
-            "ssl-only": True,
-            "add-locales": False,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "stub-installer": {
-            "product-name": "Firefox-%(version)s-stub",
-            "check_uptake": True,
-            "alias": "firefox-beta-stub",
-            "ssl-only": True,
-            "add-locales": False,
-            "paths": {
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Installer.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Installer.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "complete-mar": {
-            "product-name": "Firefox-%(version)s-Complete",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": False,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/update/mac/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/update/win32/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/update/win64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-    "partials": {
-        "releases-dir": {
-            "product-name": "Firefox-%(version)s-Partial-%(prev_version)s",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": False,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/update/mac/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/update/win32/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/update/win64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/dev_bouncer_firefox_devedition.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# lint_ignore=E501
-config = {
-    "shipped-locales-url": "https://hg.mozilla.org/%(repo)s/raw-file/%(revision)s/browser/locales/shipped-locales",
-    "locales": ["en-US"],
-    "bouncer_prefix": "https://bouncer-bouncer-releng.stage.mozaws.net/",
-    "products": {
-        "installer": {
-            "product-name": "Devedition-%(version)s",
-            "check_uptake": True,
-            "alias": "firefox-devedition-latest",
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/devedition/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/devedition/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/devedition/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/devedition/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/devedition/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "installer-ssl": {
-            "product-name": "Devedition-%(version)s-SSL",
-            "check_uptake": True,
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/devedition/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/devedition/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/devedition/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/devedition/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/devedition/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "stub-installer": {
-            "product-name": "Devedition-%(version)s-stub",
-            "check_uptake": True,
-            "alias": "firefox-devedition-stub",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "win32": {
-                    "path": "/devedition/releases/%(version)s/win32/:lang/Firefox%%20Installer.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/devedition/releases/%(version)s/win32/:lang/Firefox%%20Installer.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "complete-mar": {
-            "product-name": "Devedition-%(version)s-Complete",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/devedition/releases/%(version)s/update/linux-i686/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/devedition/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/devedition/releases/%(version)s/update/mac/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/devedition/releases/%(version)s/update/win32/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/devedition/releases/%(version)s/update/win64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-    "partials": {
-        "releases-dir": {
-            "product-name": "Devedition-%(version)s-Partial-%(prev_version)s",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/devedition/releases/%(version)s/update/linux-i686/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/devedition/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/devedition/releases/%(version)s/update/mac/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/devedition/releases/%(version)s/update/win32/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/devedition/releases/%(version)s/update/win64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/dev_bouncer_firefox_esr.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# lint_ignore=E501
-config = {
-    "shipped-locales-url": "https://hg.mozilla.org/%(repo)s/raw-file/%(revision)s/browser/locales/shipped-locales",
-    "bouncer_prefix": "https://bouncer-bouncer-releng.stage.mozaws.net/",
-    "products": {
-        "installer": {
-            "product-name": "Firefox-%(version)s",
-            "check_uptake": True,
-            "alias": "firefox-esr-latest",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "installer-ssl": {
-            "product-name": "Firefox-%(version)s-SSL",
-            "check_uptake": True,
-            "alias": "firefox-esr-latest-ssl",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/linux-i686/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/linux-x86_64/:lang/firefox-%(version)s.tar.bz2",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/mac/:lang/Firefox%%20%(version)s.dmg",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-        "complete-mar": {
-            "product-name": "Firefox-%(version)s-Complete",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/update/mac/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/update/win32/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/update/win64/:lang/firefox-%(version)s.complete.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-    "partials": {
-        "releases-dir": {
-            "product-name": "Firefox-%(version)s-Partial-%(prev_version)s",
-            "check_uptake": True,
-            "ssl-only": False,
-            "add-locales": True,
-            "paths": {
-                "linux": {
-                    "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux",
-                },
-                "linux64": {
-                    "path": "/firefox/releases/%(version)s/update/linux-x86_64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "linux64",
-                },
-                "macosx64": {
-                    "path": "/firefox/releases/%(version)s/update/mac/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "osx",
-                },
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/update/win32/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win",
-                },
-                "win64": {
-                    "path": "/firefox/releases/%(version)s/update/win64/:lang/firefox-%(prev_version)s-%(version)s.partial.mar",
-                    "bouncer-platform": "win64",
-                },
-            },
-        },
-    },
-}
--- a/testing/mozharness/mozharness/mozilla/structuredlog.py
+++ b/testing/mozharness/mozharness/mozilla/structuredlog.py
@@ -6,16 +6,21 @@
 import json
 
 from mozharness.base import log
 from mozharness.base.log import OutputParser, WARNING, INFO, ERROR
 from mozharness.mozilla.buildbot import TBPL_WARNING, TBPL_FAILURE
 from mozharness.mozilla.buildbot import TBPL_SUCCESS, TBPL_WORST_LEVEL_TUPLE
 from mozharness.mozilla.testing.unittest import tbox_print_summary
 
+from collections import (
+    defaultdict,
+    namedtuple,
+)
+
 
 class StructuredOutputParser(OutputParser):
     # The script class using this must inherit the MozbaseMixin to ensure
     # that mozlog is available.
     def __init__(self, **kwargs):
         """Object that tracks the overall status of the test run"""
         # The 'strict' argument dictates whether the presence of output
         # from the harness process other than line-delimited json indicates
@@ -94,20 +99,58 @@ class StructuredOutputParser(OutputParse
             if error_level is not None:
                 level = self.worst_level(error_level, level)
 
         log_data = self.formatter(data)
         if log_data is not None:
             self.log(log_data, level=level)
             self.update_levels(tbpl_level, level)
 
-    def evaluate_parser(self, return_code, success_codes=None):
+    def _subtract_tuples(self, old, new):
+        items = set(old.keys() + new.keys())
+        merged = defaultdict(int)
+        for item in items:
+            merged[item] = new.get(item, 0) - old.get(item, 0)
+            if merged[item] <= 0:
+                del merged[item]
+        return merged
+
+    def evaluate_parser(self, return_code, success_codes=None, previous_summary=None):
         success_codes = success_codes or [0]
         summary = self.handler.summarize()
 
+        """
+          We can run evaluate_parser multiple times, it will duplicate failures
+          and status which can mean that future tests will fail if a previous test fails.
+          When we have a previous summary, we want to do 2 things:
+            1) Remove previous data from the new summary to only look at new data
+            2) Build a joined summary to include the previous + new data
+        """
+        if previous_summary:
+            RunSummary = namedtuple("RunSummary",
+                                    ("unexpected_statuses",
+                                     "expected_statuses",
+                                     "log_level_counts",
+                                     "action_counts"))
+
+            self.tbpl_status = TBPL_SUCCESS
+
+            joined_summary = summary
+
+            # Remove previously known status messages
+            summary = RunSummary(self._subtract_tuples(previous_summary.unexpected_statuses, summary.unexpected_statuses),
+                                 self._subtract_tuples(previous_summary.expected_statuses, summary.expected_statuses),
+                                 summary.log_level_counts,
+                                 summary.action_counts)
+
+            # If we have previous data to ignore, cache it so we don't parse the log multiple times
+            self.summary = summary
+        else:
+           joined_summary = summary
+
         fail_pair = TBPL_WARNING, WARNING
         error_pair = TBPL_FAILURE, ERROR
 
         # These are warning/orange statuses.
         failure_conditions = [
             sum(summary.unexpected_statuses.values()) > 0,
             summary.action_counts.get('crash', 0) > summary.expected_statuses.get('CRASH', 0),
             summary.action_counts.get('valgrind_error', 0) > 0
@@ -137,44 +180,52 @@ class StructuredOutputParser(OutputParse
         if self.num_errors:
             self.update_levels(*error_pair)
 
         # Harnesses typically return non-zero on test failure, so don't promote
         # to error if we already have a failing status.
         if return_code not in success_codes and self.tbpl_status == TBPL_SUCCESS:
             self.update_levels(*error_pair)
 
-        return self.tbpl_status, self.worst_log_level
+        return self.tbpl_status, self.worst_log_level, joined_summary
 
     def update_levels(self, tbpl_level, log_level):
         self.worst_log_level = self.worst_level(log_level, self.worst_log_level)
         self.tbpl_status = self.worst_level(tbpl_level, self.tbpl_status,
                                             levels=TBPL_WORST_LEVEL_TUPLE)
 
     def print_summary(self, suite_name):
         # Summary text provided for compatibility. Counts are currently
         # in the format <pass count>/<fail count>/<todo count>,
         # <expected count>/<unexpected count>/<expected fail count> will yield the
         # expected info from a structured log (fail count from the prior implementation
         # includes unexpected passes from "todo" assertions).
-        summary = self.handler.summarize()
+        try:
+            summary = self.summary
+        except AttributeError:
+            summary = self.handler.summarize()
+
         unexpected_count = sum(summary.unexpected_statuses.values())
         expected_count = sum(summary.expected_statuses.values())
         expected_failures = summary.expected_statuses.get('FAIL', 0)
 
         if unexpected_count:
             fail_text = '<em class="testfail">%s</em>' % unexpected_count
         else:
             fail_text = '0'
 
         text_summary = "%s/%s/%s" % (expected_count, fail_text, expected_failures)
         self.info("TinderboxPrint: %s<br/>%s\n" % (suite_name, text_summary))
 
     def append_tinderboxprint_line(self, suite_name):
-        summary = self.handler.summarize()
+        try:
+            summary = self.summary
+        except AttributeError:
+            summary = self.handler.summarize()
+
         unexpected_count = sum(summary.unexpected_statuses.values())
         expected_count = sum(summary.expected_statuses.values())
         expected_failures = summary.expected_statuses.get('FAIL', 0)
         crashed = 0
         if 'crash' in summary.action_counts:
             crashed = summary.action_counts['crash']
         text_summary = tbox_print_summary(expected_count,
                                           unexpected_count,
--- a/testing/mozharness/mozharness/mozilla/testing/firefox_ui_tests.py
+++ b/testing/mozharness/mozharness/mozilla/testing/firefox_ui_tests.py
@@ -273,17 +273,17 @@ class FirefoxUITests(TestingMixin, VCSTo
             env['MOZ_ACCELERATED'] = '1'
 
         return_code = self.run_command(cmd,
                                        cwd=dirs['abs_fxui_dir'],
                                        output_timeout=1000,
                                        output_parser=parser,
                                        env=env)
 
-        tbpl_status, log_level = parser.evaluate_parser(return_code)
+        tbpl_status, log_level, summary = parser.evaluate_parser(return_code)
         self.buildbot_status(tbpl_status, level=log_level)
 
         return return_code
 
     @PreScriptAction('run-tests')
     def _pre_run_tests(self, action):
         if not self.installer_path and not self.installer_url:
             self.critical('Please specify an installer via --installer-path or --installer-url.')
--- a/testing/mozharness/mozharness/mozilla/testing/unittest.py
+++ b/testing/mozharness/mozharness/mozilla/testing/unittest.py
@@ -60,26 +60,52 @@ class TestSummaryOutputParserHelper(Outp
         m = self.regex.search(line)
         if m:
             try:
                 setattr(self, m.group(1), int(m.group(2)))
             except ValueError:
                 # ignore bad values
                 pass
 
-    def evaluate_parser(self, return_code, success_codes=None):
+    def evaluate_parser(self, return_code, success_codes=None, previous_summary=None):
+        """
+          We can run evaluate_parser multiple times, it will duplicate failures
+          and status which can mean that future tests will fail if a previous test fails.
+          When we have a previous summary, we want to do 2 things:
+            1) Remove previous data from the new summary to only look at new data
+            2) Build a joined summary to include the previous + new data
+        """
+        keys = ['passed', 'failed']
+        joined_summary = {}
+        for key in keys:
+            joined_summary[key] = getattr(self, key)
+
+        if previous_summary:
+            for key in keys:
+                joined_summary[key] += previous_summary[key]
+                value = getattr(self, key) - previous_summary[key]
+                if value < 0:
+                    value = 0
+                setattr(self, key, value)
+            self.tbpl_status = TBPL_SUCCESS
+            self.worst_log_level = INFO
+
+        joined_summary = {}
+        if previous_summary:
+            joined_summary = previous_summary
+
         if return_code == 0 and self.passed > 0 and self.failed == 0:
             self.tbpl_status = TBPL_SUCCESS
         elif return_code == 10 and self.failed > 0:
             self.tbpl_status = TBPL_WARNING
         else:
             self.tbpl_status = TBPL_FAILURE
             self.worst_log_level = ERROR
 
-        return (self.tbpl_status, self.worst_log_level)
+        return (self.tbpl_status, self.worst_log_level, joined_summary)
 
     def print_summary(self, suite_name):
         # generate the TinderboxPrint line for TBPL
         emphasize_fail_text = '<em class="testfail">%s</em>'
         failed = "0"
         if self.passed == 0 and self.failed == 0:
             self.tsummary = emphasize_fail_text % "T-FAIL"
         else:
@@ -165,23 +191,45 @@ class DesktopUnittestOutputParser(Output
         if self.harness_retry_re.search(line):
             self.critical(' %s' % line)
             self.worst_log_level = self.worst_level(CRITICAL, self.worst_log_level)
             self.tbpl_status = self.worst_level(TBPL_RETRY, self.tbpl_status,
                                                 levels=TBPL_WORST_LEVEL_TUPLE)
             return  # skip base parse_single_line
         super(DesktopUnittestOutputParser, self).parse_single_line(line)
 
-    def evaluate_parser(self, return_code, success_codes=None):
+    def evaluate_parser(self, return_code, success_codes=None, previous_summary=None):
         success_codes = success_codes or [0]
 
         if self.num_errors:  # mozharness ran into a script error
             self.tbpl_status = self.worst_level(TBPL_FAILURE, self.tbpl_status,
                                                 levels=TBPL_WORST_LEVEL_TUPLE)
 
+        """
+          We can run evaluate_parser multiple times, it will duplicate failures
+          and status which can mean that future tests will fail if a previous test fails.
+          When we have a previous summary, we want to do 2 things:
+            1) Remove previous data from the new summary to only look at new data
+            2) Build a joined summary to include the previous + new data
+        """
+        keys = ['pass_count', 'fail_count', 'known_fail_count', 'crashed', 'leaked']
+        joined_summary = {}
+        for key in keys:
+            joined_summary[key] = getattr(self, key)
+
+        if previous_summary:
+            for key in keys:
+                joined_summary[key] += previous_summary[key]
+                value = getattr(self, key) - previous_summary[key]
+                if value < 0:
+                    value = 0
+                setattr(self, key, value)
+            self.tbpl_status = TBPL_SUCCESS
+            self.worst_log_level = INFO
+
         # I have to put this outside of parse_single_line because this checks not
         # only if fail_count was more then 0 but also if fail_count is still -1
         # (no fail summary line was found)
         if self.fail_count != 0:
             self.worst_log_level = self.worst_level(WARNING, self.worst_log_level)
             self.tbpl_status = self.worst_level(TBPL_WARNING, self.tbpl_status,
                                                 levels=TBPL_WORST_LEVEL_TUPLE)
 
@@ -195,17 +243,17 @@ class DesktopUnittestOutputParser(Output
                                                 self.tbpl_status,
                                                 levels=TBPL_WORST_LEVEL_TUPLE)
 
         if return_code not in success_codes:
             self.tbpl_status = self.worst_level(TBPL_FAILURE, self.tbpl_status,
                                                 levels=TBPL_WORST_LEVEL_TUPLE)
 
         # we can trust in parser.worst_log_level in either case
-        return (self.tbpl_status, self.worst_log_level)
+        return (self.tbpl_status, self.worst_log_level, joined_summary)
 
     def append_tinderboxprint_line(self, suite_name):
         # We are duplicating a condition (fail_count) from evaluate_parser and
         # parse parse_single_line but at little cost since we are not parsing
         # the log more then once.  I figured this method should stay isolated as
         # it is only here for tbpl highlighted summaries and is not part of
         # buildbot evaluation or result status IIUC.
         summary = tbox_print_summary(self.pass_count,
--- a/testing/mozharness/scripts/android_emulator_unittest.py
+++ b/testing/mozharness/scripts/android_emulator_unittest.py
@@ -785,16 +785,17 @@ class AndroidEmulatorTest(TestingMixin, 
                 self.fatal("Don't know how to run --test-suite '%s'!" % self.test_suite)
             env = self.query_env()
             if minidump:
                 env['MINIDUMP_STACKWALK'] = minidump
             env['MOZ_UPLOAD_DIR'] = self.query_abs_dirs()['abs_blob_upload_dir']
             env['MINIDUMP_SAVE_PATH'] = self.query_abs_dirs()['abs_blob_upload_dir']
             env['RUST_BACKTRACE'] = 'full'
 
+            summary = None
             for per_test_args in self.query_args(per_test_suite):
                 if (datetime.datetime.now() - self.start_time) > max_per_test_time:
                     # Running tests has run out of time. That is okay! Stop running
                     # them so that a task timeout is not triggered, and so that
                     # (partial) results are made available in a timely manner.
                     self.info("TinderboxPrint: Running tests took too long: "
                               "Not all tests were executed.<br/>")
                     # Signal per-test time exceeded, to break out of suites and
@@ -815,17 +816,17 @@ class AndroidEmulatorTest(TestingMixin, 
 
                 suite_category = self.test_suite
                 parser = self.get_test_output_parser(
                     suite_category,
                     config=self.config,
                     log_obj=self.log_obj,
                     error_list=[])
                 self.run_command(final_cmd, cwd=cwd, env=env, output_parser=parser)
-                tbpl_status, log_level = parser.evaluate_parser(0)
+                tbpl_status, log_level, summary = parser.evaluate_parser(0, summary)
                 parser.append_tinderboxprint_line(self.test_suite)
 
                 self.info("##### %s log ends" % self.test_suite)
 
                 if len(per_test_args) > 0:
                     self.buildbot_status(tbpl_status, level=log_level)
                     self.log_per_test_status(per_test_args[-1], tbpl_status, log_level)
                 else:
--- a/testing/mozharness/scripts/awsy_script.py
+++ b/testing/mozharness/scripts/awsy_script.py
@@ -195,17 +195,17 @@ class AWSY(TestingMixin, MercurialScript
                                         strict=False)
         return_code = self.run_command(command=cmd,
                                        cwd=self.awsy_path,
                                        output_timeout=self.config.get("cmd_timeout"),
                                        env=env,
                                        output_parser=parser)
 
         level = INFO
-        tbpl_status, log_level = parser.evaluate_parser(
+        tbpl_status, log_level, summary = parser.evaluate_parser(
             return_code=return_code)
 
         self.log("AWSY exited with return code %s: %s" % (return_code, tbpl_status),
                  level=level)
         self.buildbot_status(tbpl_status)
 
 
 if __name__ == '__main__':
--- a/testing/mozharness/scripts/desktop_unittest.py
+++ b/testing/mozharness/scripts/desktop_unittest.py
@@ -849,16 +849,17 @@ class DesktopUnittest(TestingMixin, Merc
                 if self.config['single_stylo_traversal']:
                     env['STYLO_THREADS'] = '1'
                 else:
                     env['STYLO_THREADS'] = '4'
 
                 env = self.query_env(partial_env=env, log_level=INFO)
                 cmd_timeout = self.get_timeout_for_category(suite_category)
 
+                summary = None
                 for per_test_args in self.query_args(suite):
                     if (datetime.now() - self.start_time) > max_per_test_time:
                         # Running tests has run out of time. That is okay! Stop running
                         # them so that a task timeout is not triggered, and so that
                         # (partial) results are made available in a timely manner.
                         self.info("TinderboxPrint: Running tests took too long: Not all tests "
                                   "were executed.<br/>")
                         # Signal per-test time exceeded, to break out of suites and
@@ -900,18 +901,19 @@ class DesktopUnittest(TestingMixin, Merc
                     #    findings for harness/suite errors <- DesktopUnittestOutputParser
                     # 3) checking to see if the return code is in success_codes
 
                     success_codes = None
                     if self._is_windows() and suite_category != 'gtest':
                         # bug 1120644
                         success_codes = [0, 1]
 
-                    tbpl_status, log_level = parser.evaluate_parser(return_code,
-                                                                    success_codes=success_codes)
+                    tbpl_status, log_level, summary = parser.evaluate_parser(return_code,
+                                                                             success_codes,
+                                                                             summary)
                     parser.append_tinderboxprint_line(suite_name)
 
                     self.buildbot_status(tbpl_status, level=log_level)
                     if len(per_test_args) > 0:
                         self.log_per_test_status(per_test_args[-1], tbpl_status, log_level)
                     else:
                         self.log("The %s suite: %s ran with return status: %s" %
                                  (suite_category, suite, tbpl_status), level=log_level)
--- a/testing/mozharness/scripts/marionette.py
+++ b/testing/mozharness/scripts/marionette.py
@@ -349,17 +349,17 @@ class MarionetteTest(TestingMixin, Mercu
                                               error_list=BaseErrorList + HarnessErrorList,
                                               strict=False)
         return_code = self.run_command(cmd,
                                        cwd=cwd,
                                        output_timeout=1000,
                                        output_parser=marionette_parser,
                                        env=env)
         level = INFO
-        tbpl_status, log_level = marionette_parser.evaluate_parser(
+        tbpl_status, log_level, summary = marionette_parser.evaluate_parser(
             return_code=return_code)
         marionette_parser.append_tinderboxprint_line("marionette")
 
         qemu = os.path.join(dirs['abs_work_dir'], 'qemu.log')
         if os.path.isfile(qemu):
             self.copyfile(qemu, os.path.join(dirs['abs_blob_upload_dir'],
                                              'qemu.log'))
 
--- a/testing/mozharness/scripts/web_platform_tests.py
+++ b/testing/mozharness/scripts/web_platform_tests.py
@@ -332,16 +332,17 @@ class WebPlatformTest(TestingMixin, Merc
                     self.info("Skipping 'wdspec' tests - no geckodriver")
         else:
             test_types = self.config.get("test_type", [])
             suites = [None]
         for suite in suites:
             if suite:
                 test_types = [suite]
 
+            summary = None
             for per_test_args in self.query_args(suite):
                 if (datetime.now() - start_time) > max_per_test_time:
                     # Running tests has run out of time. That is okay! Stop running
                     # them so that a task timeout is not triggered, and so that
                     # (partial) results are made available in a timely manner.
                     self.info("TinderboxPrint: Running tests took too long: Not all tests "
                               "were executed.<br/>")
                     return
@@ -365,17 +366,17 @@ class WebPlatformTest(TestingMixin, Merc
                                                cwd=dirs['abs_work_dir'],
                                                output_timeout=1000,
                                                output_parser=parser,
                                                env=env)
 
                 if self.per_test_coverage:
                     self.add_per_test_coverage_report(gcov_dir, jsvm_dir, suite, per_test_args[-1])
 
-                tbpl_status, log_level = parser.evaluate_parser(return_code)
+                tbpl_status, log_level, summary = parser.evaluate_parser(return_code, summary)
                 self.buildbot_status(tbpl_status, level=log_level)
 
                 if len(per_test_args) > 0:
                     self.log_per_test_status(per_test_args[-1], tbpl_status, log_level)
 
 
 # main {{{1
 if __name__ == '__main__':