Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 19 Sep 2017 14:49:11 -0700
changeset 433734 a0eb21bf55e1c1ae0ba311e6f2273da05c712799
parent 433733 c0d1f9eb2a4090ab8a06ef3560ab5d0e06c5bb1f (current diff)
parent 433678 0902f7275334aeb271d494b6aac1ee2730add627 (diff)
child 433735 c17dc70d0163d5432e7bdea627237dea40dd2cf8
child 433745 d9db71772f1d5a324069b6033739dda5a462d78e
child 433774 fa4de7ce4de0d9d5fcb4baffa600e439992b9e4b
push id1567
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 12:36:05 +0000
treeherdermozilla-release@e512c14a0406 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone57.0a1
first release with
nightly linux64
a0eb21bf55e1 / 57.0a1 / 20170919220202 / files
nightly mac
a0eb21bf55e1 / 57.0a1 / 20170919220202 / files
nightly win32
a0eb21bf55e1 / 57.0a1 / 20170919220202 / files
nightly win64
a0eb21bf55e1 / 57.0a1 / 20170919220202 / files
nightly linux32
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: 5B5W6AHKcCb
browser/components/preferences/in-content/privacy.xul
dom/webauthn/u2f-hid-rs/build.rs
dom/webauthn/u2f-hid-rs/src/runloop.rs
gfx/layers/wr/WebRenderLayerManager.cpp
layout/style/ServoBindings.cpp
layout/style/nsStyleStruct.h
taskcluster/docker/android-build/buildprops.json
taskcluster/docker/android-build/oauth.txt
taskcluster/docker/recipes/install-mercurial.sh
taskcluster/scripts/builder/build-android-dependencies/after.sh
taskcluster/scripts/builder/build-android-dependencies/before.sh
testing/web-platform/meta/FileAPI/url/url_createobjecturl_blob.html.ini
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -39,17 +39,16 @@ function openContextMenu(aMessage) {
   gContextMenuContentData = { context: data.context,
                               isRemote: data.isRemote,
                               popupNodeSelectors: data.popupNodeSelectors,
                               browser,
                               editFlags: data.editFlags,
                               spellInfo,
                               principal: data.principal,
                               customMenuItems: data.customMenuItems,
-                              addonInfo: data.addonInfo,
                               documentURIObject,
                               docLocation: data.docLocation,
                               charSet: data.charSet,
                               referrer: data.referrer,
                               referrerPolicy: data.referrerPolicy,
                               contentType: data.contentType,
                               contentDisposition: data.contentDisposition,
                               frameOuterWindowID: data.frameOuterWindowID,
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -542,17 +542,17 @@ var DownloadsPanel = {
       if (this._state != this.kStateWaitingAnchor) {
         return;
       }
 
       // At this point, if the window is minimized, opening the panel could fail
       // without any notification, and there would be no way to either open or
       // close the panel any more.  To prevent this, check if the window is
       // minimized and in that case force the panel to the closed state.
-      if (window.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED) {
+      if (window.windowState == window.STATE_MINIMIZED) {
         DownloadsButton.releaseAnchor();
         this._state = this.kStateHidden;
         return;
       }
 
       if (!anchor) {
         DownloadsCommon.error("Downloads button cannot be found.");
         return;
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -1066,16 +1066,20 @@ var gPrivacyPane = {
     let safeBrowsingMalwarePref = document.getElementById("browser.safebrowsing.malware.enabled");
 
     let blockDownloadsPref = document.getElementById("browser.safebrowsing.downloads.enabled");
     let malwareTable = document.getElementById("urlclassifier.malwareTable");
 
     let blockUnwantedPref = document.getElementById("browser.safebrowsing.downloads.remote.block_potentially_unwanted");
     let blockUncommonPref = document.getElementById("browser.safebrowsing.downloads.remote.block_uncommon");
 
+    let learnMoreLink = document.getElementById("enableSafeBrowsingLearnMore");
+    let phishingUrl = "https://support.mozilla.org/kb/how-does-phishing-and-malware-protection-work";
+    learnMoreLink.setAttribute("href", phishingUrl);
+
     enableSafeBrowsing.addEventListener("command", function() {
       safeBrowsingPhishingPref.value = enableSafeBrowsing.checked;
       safeBrowsingMalwarePref.value = enableSafeBrowsing.checked;
 
       if (enableSafeBrowsing.checked) {
         if (blockDownloads) {
           blockDownloads.removeAttribute("disabled");
           if (blockDownloads.checked) {
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -757,19 +757,23 @@
       hidden="true"
       data-category="panePrivacy">
   <label class="header-name" flex="1">&security.label;</label>
 </hbox>
 
 <!-- addons, forgery (phishing) UI Security -->
 <groupbox id="browsingProtectionGroup" data-category="panePrivacy" hidden="true">
   <caption><label>&browsingProtection.label;</label></caption>
-  <checkbox id="enableSafeBrowsing"
-            label="&enableSafeBrowsing.label;"
-            accesskey="&enableSafeBrowsing.accesskey;" />
+  <hbox align = "center">
+    <checkbox id="enableSafeBrowsing"
+              label="&enableSafeBrowsing.label;"
+              accesskey="&enableSafeBrowsing.accesskey;" />
+    <label id="enableSafeBrowsingLearnMore"
+           class="learnMore text-link">&enableSafeBrowsingLearnMore.label;</label>
+  </hbox>
   <vbox class="indent">
 #ifdef MOZILLA_OFFICIAL
     <checkbox id="blockDownloads"
               label="&blockDownloads.label;"
               accesskey="&blockDownloads.accesskey;" />
 #endif
     <checkbox id="blockUncommonUnwanted"
               label="&blockUncommonAndUnwanted.label;"
--- a/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
@@ -112,8 +112,9 @@
 <!ENTITY  browserContainersEnabled.label        "Enable Container Tabs">
 <!ENTITY  browserContainersEnabled.accesskey    "n">
 <!ENTITY  browserContainersSettings.label        "Settings…">
 <!ENTITY  browserContainersSettings.accesskey    "i">
 
 <!ENTITY  a11yPrivacy.checkbox.label     "Prevent accessibility services from accessing your browser">
 <!ENTITY  a11yPrivacy.checkbox.accesskey "a">
 <!ENTITY  a11yPrivacy.learnmore.label    "Learn more">
+<!ENTITY enableSafeBrowsingLearnMore.label "Learn more">
\ No newline at end of file
--- a/browser/modules/ContextMenu.jsm
+++ b/browser/modules/ContextMenu.jsm
@@ -505,25 +505,16 @@ class ContextMenu {
 
       defaultPrevented = false;
     }
 
     if (defaultPrevented) {
       return;
     }
 
-    let addonInfo = Object.create(null);
-    let subject = {
-      aEvent,
-      addonInfo,
-    };
-
-    subject.wrappedJSObject = subject;
-    Services.obs.notifyObservers(subject, "content-contextmenu");
-
     let doc = aEvent.target.ownerDocument;
     let {
       mozDocumentURIIfNotForErrorPages: docLocation,
       characterSet: charSet,
       baseURI,
       referrer,
       referrerPolicy
     } = doc;
@@ -607,17 +598,16 @@ class ContextMenu {
     }
 
     let data = {
       context,
       charSet,
       baseURI,
       isRemote,
       referrer,
-      addonInfo,
       editFlags,
       principal,
       spellInfo,
       contentType,
       docLocation,
       loginFillInfo,
       selectionInfo,
       userContextId,
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -14,16 +14,17 @@
 #include "nsIBaseWindow.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIScrollable.h"
 #include "nsITextScroll.h"
 #include "nsIContentViewerContainer.h"
 #include "nsIDOMStorageManager.h"
 #include "nsDocLoader.h"
 #include "mozilla/BasePrincipal.h"
+#include "mozilla/Move.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "GeckoProfiler.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 #include "mozilla/LinkedList.h"
 #include "jsapi.h"
 
@@ -286,16 +287,51 @@ public:
 
   const Encoding* GetForcedCharset() { return mForcedCharset; }
 
   mozilla::HTMLEditor* GetHTMLEditorInternal();
   nsresult SetHTMLEditorInternal(mozilla::HTMLEditor* aHTMLEditor);
 
   nsDOMNavigationTiming* GetNavigationTiming() const;
 
+  /**
+   * Get the list of ancestor principals for this docshell.  The list is meant
+   * to be the list of principals of the documents this docshell is "nested
+   * through" in the sense of
+   * <https://html.spec.whatwg.org/multipage/browsers.html#browsing-context-nested-through>.
+   * In practice, it is defined as follows:
+   *
+   * If this is an <iframe mozbrowser> or a toplevel content docshell
+   * (i.e. toplevel document in spec terms), the list is empty.
+   *
+   * Otherwise the list is the list for the document we're nested through (again
+   * in the spec sense), with the principal of that document prepended.  Note
+   * that this matches the ordering specified for Location.ancestorOrigins.
+   */
+  const nsTArray<nsCOMPtr<nsIPrincipal>>& AncestorPrincipals() const
+  {
+    return mAncestorPrincipals;
+  }
+
+  /**
+   * Set the list of ancestor principals for this docshell.  This is really only
+   * needed for use by the frameloader.  We can't do this ourselves, inside
+   * docshell, because there's a bunch of state setup that frameloader does
+   * (like telling us whether we're a mozbrowser), some of which comes after the
+   * docshell is added to the docshell tree, which can affect what the ancestor
+   * principals should look like.
+   *
+   * This method steals the data from the passed-in array.
+   */
+  void SetAncestorPrincipals(
+    nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals)
+  {
+    mAncestorPrincipals = mozilla::Move(aAncestorPrincipals);
+  }
+
 private:
   bool CanSetOriginAttributes();
 
 public:
   const mozilla::OriginAttributes&
   GetOriginAttributes()
   {
     return mOriginAttributes;
@@ -1089,16 +1125,19 @@ private:
   // A depth count of how many times NotifyRunToCompletionStart
   // has been called without a matching NotifyRunToCompletionStop.
   uint32_t mJSRunToCompletionDepth;
 
   // Whether or not touch events are overridden. Possible values are defined
   // as constants in the nsIDocShell.idl file.
   uint32_t mTouchEventsOverride;
 
+  // Our list of ancestor principals.
+  nsTArray<nsCOMPtr<nsIPrincipal>> mAncestorPrincipals;
+
   // Separate function to do the actual name (i.e. not _top, _self etc.)
   // searching for FindItemWithName.
   nsresult DoFindItemWithName(const nsAString& aName,
                               nsIDocShellTreeItem* aRequestor,
                               nsIDocShellTreeItem* aOriginalRequestor,
                               bool aSkipTabGroup,
                               nsIDocShellTreeItem** aResult);
 
--- a/dom/base/DOMIntersectionObserver.cpp
+++ b/dom/base/DOMIntersectionObserver.cpp
@@ -156,16 +156,20 @@ DOMIntersectionObserver::Observe(Element
   aTarget.RegisterIntersectionObserver(this);
   mObservationTargets.AppendElement(&aTarget);
   Connect();
 }
 
 void
 DOMIntersectionObserver::Unobserve(Element& aTarget)
 {
+  if (!mObservationTargets.Contains(&aTarget)) {
+    return;
+  }
+
   if (mObservationTargets.Length() == 1) {
     Disconnect();
     return;
   }
 
   mObservationTargets.RemoveElement(&aTarget);
   aTarget.UnregisterIntersectionObserver(this);
 }
--- a/dom/base/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -45,8 +45,9 @@ DEPRECATED_OPERATION(AppCache)
 DEPRECATED_OPERATION(PrefixedImageSmoothingEnabled)
 DEPRECATED_OPERATION(PrefixedFullscreenAPI)
 DEPRECATED_OPERATION(LenientSetter)
 DEPRECATED_OPERATION(FileLastModifiedDate)
 DEPRECATED_OPERATION(ImageBitmapRenderingContext_TransferImageBitmap)
 DEPRECATED_OPERATION(URLCreateObjectURL_MediaStream)
 DEPRECATED_OPERATION(XMLBaseAttribute)
 DEPRECATED_OPERATION(XMLBaseAttributeForStyleAttr)
+DEPRECATED_OPERATION(WindowContentUntrusted)
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -4960,16 +4960,18 @@ nsIDocument::SetContainer(nsDocShell* aC
     NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!");
 
     if (sameTypeRoot == aContainer) {
       static_cast<nsDocument*>(this)->SetIsTopLevelContentDocument(true);
     }
 
     static_cast<nsDocument*>(this)->SetIsContentDocument(true);
   }
+
+  mAncestorPrincipals = aContainer->AncestorPrincipals();
 }
 
 nsISupports*
 nsIDocument::GetContainer() const
 {
   return static_cast<nsIDocShell*>(mDocumentContainer);
 }
 
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2527,17 +2527,17 @@ nsFrameLoader::MaybeCreateDocShell()
   if (!frameName.IsEmpty()) {
     mDocShell->SetName(frameName);
   }
 
   // Inform our docShell that it has a new child.
   // Note: This logic duplicates a lot of logic in
   // nsSubDocumentFrame::AttributeChanged.  We should fix that.
 
-  int32_t parentType = docShell->ItemType();
+  const int32_t parentType = docShell->ItemType();
 
   // XXXbz why is this in content code, exactly?  We should handle
   // this some other way.....  Not sure how yet.
   nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
   docShell->GetTreeOwner(getter_AddRefs(parentTreeOwner));
   NS_ENSURE_STATE(parentTreeOwner);
   mIsTopLevelContent =
     AddTreeItemToTreeOwner(mDocShell, parentTreeOwner, parentType, docShell);
@@ -2706,16 +2706,26 @@ nsFrameLoader::MaybeCreateDocShell()
         // and not inherited by its parent.
         attrs.SyncAttributesWithPrivateBrowsing(isPrivate);
       }
     }
   }
 
   nsDocShell::Cast(mDocShell)->SetOriginAttributes(attrs);
 
+  if (!mDocShell->GetIsMozBrowser() &&
+      parentType == mDocShell->ItemType()) {
+    // Propagate through the ancestor principals.
+    nsTArray<nsCOMPtr<nsIPrincipal>> ancestorPrincipals;
+    // Make a copy, so we can modify it.
+    ancestorPrincipals = doc->AncestorPrincipals();
+    ancestorPrincipals.InsertElementAt(0, doc->NodePrincipal());
+    nsDocShell::Cast(mDocShell)->SetAncestorPrincipals(Move(ancestorPrincipals));
+  }
+
   ReallyLoadFrameScripts();
   InitializeBrowserAPI();
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
                         "inprocess-browser-shown", nullptr);
   }
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1139,31 +1139,30 @@ public:
   bool hasOwn(JSContext *cx, JS::Handle<JSObject*> proxy,
               JS::Handle<jsid> id, bool *bp) const override;
   bool getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle<JSObject*> proxy,
                                     JS::AutoIdVector &props) const override;
   const char *className(JSContext *cx,
                         JS::Handle<JSObject*> wrapper) const override;
 
   void finalize(JSFreeOp *fop, JSObject *proxy) const override;
+  size_t objectMoved(JSObject* proxy, JSObject* old) const override;
 
   bool isCallable(JSObject *obj) const override {
     return false;
   }
   bool isConstructor(JSObject *obj) const override {
     return false;
   }
 
   bool watch(JSContext *cx, JS::Handle<JSObject*> proxy,
              JS::Handle<jsid> id, JS::Handle<JSObject*> callable) const override;
   bool unwatch(JSContext *cx, JS::Handle<JSObject*> proxy,
                JS::Handle<jsid> id) const override;
 
-  static void ObjectMoved(JSObject *obj, const JSObject *old);
-
   static const nsOuterWindowProxy singleton;
 
 protected:
   static nsGlobalWindow* GetOuterWindow(JSObject *proxy)
   {
     nsGlobalWindow* outerWindow = nsGlobalWindow::FromSupports(
       static_cast<nsISupports*>(js::GetProxyReservedSlot(proxy, 0).toPrivate()));
     MOZ_ASSERT_IF(outerWindow, outerWindow->IsOuterWindow());
@@ -1183,27 +1182,22 @@ protected:
   GetSubframeWindow(JSContext *cx,
                     JS::Handle<JSObject*> proxy,
                     JS::Handle<jsid> id) const;
 
   bool AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy,
                                   JS::AutoIdVector &props) const;
 };
 
-static const js::ClassExtension OuterWindowProxyClassExtension = PROXY_MAKE_EXT(
-    nsOuterWindowProxy::ObjectMoved
-);
-
 // Give OuterWindowProxyClass 2 reserved slots, like the other wrappers, so
 // JSObject::swap can swap it with CrossCompartmentWrappers without requiring
 // malloc.
-const js::Class OuterWindowProxyClass = PROXY_CLASS_WITH_EXT(
+const js::Class OuterWindowProxyClass = PROXY_CLASS_DEF(
     "Proxy",
-    JSCLASS_HAS_RESERVED_SLOTS(2), /* additional class flags */
-    &OuterWindowProxyClassExtension);
+    JSCLASS_HAS_RESERVED_SLOTS(2)); /* additional class flags */
 
 const char *
 nsOuterWindowProxy::className(JSContext *cx, JS::Handle<JSObject*> proxy) const
 {
     MOZ_ASSERT(js::IsProxy(proxy));
 
     return "Window";
 }
@@ -1521,23 +1515,24 @@ nsOuterWindowProxy::watch(JSContext *cx,
 
 bool
 nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle<JSObject*> proxy,
                             JS::Handle<jsid> id) const
 {
   return js::UnwatchGuts(cx, proxy, id);
 }
 
-void
-nsOuterWindowProxy::ObjectMoved(JSObject *obj, const JSObject *old)
+size_t
+nsOuterWindowProxy::objectMoved(JSObject *obj, JSObject *old) const
 {
   nsGlobalWindow* outerWindow = GetOuterWindow(obj);
   if (outerWindow) {
     outerWindow->UpdateWrapper(obj, old);
   }
+  return 0;
 }
 
 const nsOuterWindowProxy
 nsOuterWindowProxy::singleton;
 
 class nsChromeOuterWindowProxy : public nsOuterWindowProxy
 {
 public:
@@ -4984,16 +4979,19 @@ nsGlobalWindow::GetContentInternal(Error
   // If we're contained in <iframe mozbrowser>, then GetContent is the same as
   // window.top.
   if (mDocShell && mDocShell->GetIsInMozBrowser()) {
     return GetTopOuter();
   }
 
   nsCOMPtr<nsIDocShellTreeItem> primaryContent;
   if (aCallerType != CallerType::System) {
+    if (mDoc) {
+      mDoc->WarnOnceAbout(nsIDocument::eWindowContentUntrusted);
+    }
     // If we're called by non-chrome code, make sure we don't return
     // the primary content window if the calling tab is hidden. In
     // such a case we return the same-type root in the hidden tab,
     // which is "good enough", for now.
     nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(mDocShell));
 
     if (baseWin) {
       bool visible = false;
@@ -14062,179 +14060,114 @@ NS_IMPL_RELEASE_INHERITED(nsGlobalChrome
 /* static */ already_AddRefed<nsGlobalChromeWindow>
 nsGlobalChromeWindow::Create(nsGlobalWindow *aOuterWindow)
 {
   RefPtr<nsGlobalChromeWindow> window = new nsGlobalChromeWindow(aOuterWindow);
   window->InitWasOffline();
   return window.forget();
 }
 
-NS_IMETHODIMP
-nsGlobalChromeWindow::GetWindowState(uint16_t* aWindowState)
-{
-  FORWARD_TO_INNER_CHROME(GetWindowState, (aWindowState), NS_ERROR_UNEXPECTED);
-
-  *aWindowState = WindowState();
-  return NS_OK;
-}
+enum WindowState {
+  // These constants need to match the constants in Window.webidl
+  STATE_MAXIMIZED = 1,
+  STATE_MINIMIZED = 2,
+  STATE_NORMAL = 3,
+  STATE_FULLSCREEN = 4
+};
 
 uint16_t
 nsGlobalWindow::WindowState()
 {
   MOZ_ASSERT(IsInnerWindow());
   nsCOMPtr<nsIWidget> widget = GetMainWidget();
 
   int32_t mode = widget ? widget->SizeMode() : 0;
 
   switch (mode) {
     case nsSizeMode_Minimized:
-      return nsIDOMChromeWindow::STATE_MINIMIZED;
+      return STATE_MINIMIZED;
     case nsSizeMode_Maximized:
-      return nsIDOMChromeWindow::STATE_MAXIMIZED;
+      return STATE_MAXIMIZED;
     case nsSizeMode_Fullscreen:
-      return nsIDOMChromeWindow::STATE_FULLSCREEN;
+      return STATE_FULLSCREEN;
     case nsSizeMode_Normal:
-      return nsIDOMChromeWindow::STATE_NORMAL;
+      return STATE_NORMAL;
     default:
       NS_WARNING("Illegal window state for this chrome window");
       break;
   }
 
-  return nsIDOMChromeWindow::STATE_NORMAL;
+  return STATE_NORMAL;
 }
 
 bool
 nsGlobalWindow::IsFullyOccluded()
 {
   MOZ_ASSERT(IsInnerWindow());
 
   nsCOMPtr<nsIWidget> widget = GetMainWidget();
   return widget && widget->IsFullyOccluded();
 }
 
-NS_IMETHODIMP
-nsGlobalChromeWindow::Maximize()
-{
-  FORWARD_TO_INNER_CHROME(Maximize, (), NS_ERROR_UNEXPECTED);
-
-  nsGlobalWindow::Maximize();
-  return NS_OK;
-}
-
 void
 nsGlobalWindow::Maximize()
 {
   MOZ_ASSERT(IsInnerWindow());
 
   nsCOMPtr<nsIWidget> widget = GetMainWidget();
 
   if (widget) {
     widget->SetSizeMode(nsSizeMode_Maximized);
   }
 }
 
-NS_IMETHODIMP
-nsGlobalChromeWindow::Minimize()
-{
-  FORWARD_TO_INNER_CHROME(Minimize, (), NS_ERROR_UNEXPECTED);
-
-  nsGlobalWindow::Minimize();
-  return NS_OK;
-}
-
 void
 nsGlobalWindow::Minimize()
 {
   MOZ_ASSERT(IsInnerWindow());
 
   nsCOMPtr<nsIWidget> widget = GetMainWidget();
 
   if (widget) {
     widget->SetSizeMode(nsSizeMode_Minimized);
   }
 }
 
-NS_IMETHODIMP
-nsGlobalChromeWindow::Restore()
-{
-  FORWARD_TO_INNER_CHROME(Restore, (), NS_ERROR_UNEXPECTED);
-
-  nsGlobalWindow::Restore();
-  return NS_OK;
-}
-
 void
 nsGlobalWindow::Restore()
 {
   MOZ_ASSERT(IsInnerWindow());
 
   nsCOMPtr<nsIWidget> widget = GetMainWidget();
 
   if (widget) {
     widget->SetSizeMode(nsSizeMode_Normal);
   }
 }
 
-NS_IMETHODIMP
-nsGlobalChromeWindow::GetAttention()
-{
-  FORWARD_TO_INNER_CHROME(GetAttention, (), NS_ERROR_UNEXPECTED);
-
-  ErrorResult rv;
-  GetAttention(rv);
-  return rv.StealNSResult();
-}
-
 void
 nsGlobalWindow::GetAttention(ErrorResult& aResult)
 {
   MOZ_ASSERT(IsInnerWindow());
   return GetAttentionWithCycleCount(-1, aResult);
 }
 
-NS_IMETHODIMP
-nsGlobalChromeWindow::GetAttentionWithCycleCount(int32_t aCycleCount)
-{
-  FORWARD_TO_INNER_CHROME(GetAttentionWithCycleCount, (aCycleCount), NS_ERROR_UNEXPECTED);
-
-  ErrorResult rv;
-  GetAttentionWithCycleCount(aCycleCount, rv);
-  return rv.StealNSResult();
-}
-
 void
 nsGlobalWindow::GetAttentionWithCycleCount(int32_t aCycleCount,
                                            ErrorResult& aError)
 {
   MOZ_ASSERT(IsInnerWindow());
 
   nsCOMPtr<nsIWidget> widget = GetMainWidget();
 
   if (widget) {
     aError = widget->GetAttention(aCycleCount);
   }
 }
 
-NS_IMETHODIMP
-nsGlobalChromeWindow::BeginWindowMove(nsIDOMEvent *aMouseDownEvent, nsIDOMElement* aPanel)
-{
-  FORWARD_TO_INNER_CHROME(BeginWindowMove, (aMouseDownEvent, aPanel), NS_ERROR_UNEXPECTED);
-
-  NS_ENSURE_TRUE(aMouseDownEvent, NS_ERROR_FAILURE);
-  Event* mouseDownEvent = aMouseDownEvent->InternalDOMEvent();
-  NS_ENSURE_TRUE(mouseDownEvent, NS_ERROR_FAILURE);
-
-  nsCOMPtr<Element> panel = do_QueryInterface(aPanel);
-  NS_ENSURE_TRUE(panel || !aPanel, NS_ERROR_FAILURE);
-
-  ErrorResult rv;
-  BeginWindowMove(*mouseDownEvent, panel, rv);
-  return rv.StealNSResult();
-}
-
 void
 nsGlobalWindow::BeginWindowMove(Event& aMouseDownEvent, Element* aPanel,
                                 ErrorResult& aError)
 {
   MOZ_ASSERT(IsInnerWindow());
 
   nsCOMPtr<nsIWidget> widget;
 
@@ -14280,26 +14213,16 @@ nsGlobalWindow::GetWindowRootOuter()
 already_AddRefed<nsWindowRoot>
 nsGlobalWindow::GetWindowRoot(mozilla::ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(GetWindowRootOuter, (), aError, nullptr);
 }
 
 //Note: This call will lock the cursor, it will not change as it moves.
 //To unlock, the cursor must be set back to CURSOR_AUTO.
-NS_IMETHODIMP
-nsGlobalChromeWindow::SetCursor(const nsAString& aCursor)
-{
-  FORWARD_TO_INNER_CHROME(SetCursor, (aCursor), NS_ERROR_UNEXPECTED);
-
-  ErrorResult rv;
-  SetCursor(aCursor, rv);
-  return rv.StealNSResult();
-}
-
 void
 nsGlobalWindow::SetCursorOuter(const nsAString& aCursor, ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   int32_t cursor;
 
   if (aCursor.EqualsLiteral("auto"))
@@ -14374,55 +14297,31 @@ nsGlobalWindow::GetBrowserDOMWindowOuter
 }
 
 nsIBrowserDOMWindow*
 nsGlobalWindow::GetBrowserDOMWindow(ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(GetBrowserDOMWindowOuter, (), aError, nullptr);
 }
 
-NS_IMETHODIMP
-nsGlobalChromeWindow::SetBrowserDOMWindow(nsIBrowserDOMWindow *aBrowserWindow)
-{
-  FORWARD_TO_INNER_CHROME(SetBrowserDOMWindow, (aBrowserWindow), NS_ERROR_UNEXPECTED);
-
-  ErrorResult rv;
-  SetBrowserDOMWindow(aBrowserWindow, rv);
-  return rv.StealNSResult();
-}
-
 void
 nsGlobalWindow::SetBrowserDOMWindowOuter(nsIBrowserDOMWindow* aBrowserWindow)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
   MOZ_ASSERT(IsChromeWindow());
   static_cast<nsGlobalChromeWindow*>(this)->mBrowserDOMWindow = aBrowserWindow;
 }
 
 void
 nsGlobalWindow::SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserWindow,
                                     ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(SetBrowserDOMWindowOuter, (aBrowserWindow), aError, );
 }
 
-NS_IMETHODIMP
-nsGlobalChromeWindow::NotifyDefaultButtonLoaded(nsIDOMElement* aDefaultButton)
-{
-  FORWARD_TO_INNER_CHROME(NotifyDefaultButtonLoaded,
-                          (aDefaultButton), NS_ERROR_UNEXPECTED);
-
-  nsCOMPtr<Element> defaultButton = do_QueryInterface(aDefaultButton);
-  NS_ENSURE_ARG(defaultButton);
-
-  ErrorResult rv;
-  NotifyDefaultButtonLoaded(*defaultButton, rv);
-  return rv.StealNSResult();
-}
-
 void
 nsGlobalWindow::NotifyDefaultButtonLoaded(Element& aDefaultButton,
                                           ErrorResult& aError)
 {
   MOZ_ASSERT(IsInnerWindow());
 #ifdef MOZ_XUL
   // Don't snap to a disabled button.
   nsCOMPtr<nsIDOMXULControlElement> xulControl =
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -441,21 +441,35 @@ public:
   }
 
   void ClearHSTSPrimingLocation(nsIURI* aContentLocation)
   {
     mHSTSPrimingURIList.Remove(aContentLocation);
   }
 
   /**
-   * Set the principal responsible for this document.
+   * Set the principal responsible for this document.  Chances are,
+   * you do not want to be using this.
    */
   virtual void SetPrincipal(nsIPrincipal *aPrincipal) = 0;
 
   /**
+   * Get the list of ancestor principals for a document.  This is the same as
+   * the ancestor list for the document's docshell the last time SetContainer()
+   * was called with a non-null argument. See the documentation for the
+   * corresponding getter in docshell for how this list is determined.  We store
+   * a copy of the list, because we may lose the ability to reach our docshell
+   * before people stop asking us for this information.
+   */
+  const nsTArray<nsCOMPtr<nsIPrincipal>>& AncestorPrincipals() const
+  {
+    return mAncestorPrincipals;
+  }
+
+  /**
    * Return the LoadGroup for the document. May return null.
    */
   already_AddRefed<nsILoadGroup> GetDocumentLoadGroup() const
   {
     nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
     return group.forget();
   }
 
@@ -3590,16 +3604,20 @@ protected:
   // calling NoteScriptTrackingStatus().  Currently we assume that a URL not
   // existing in the set means the corresponding script isn't a tracking script.
   nsTHashtable<nsCStringHashKey> mTrackingScripts;
 
   // CSP violation reports that have been buffered up due to a call to
   // StartBufferingCSPViolations.
   nsTArray<nsCOMPtr<nsIRunnable>> mBufferedCSPViolations;
 
+  // List of ancestor principals.  This is set at the point a document
+  // is connected to a docshell and not mutated thereafter.
+  nsTArray<nsCOMPtr<nsIPrincipal>> mAncestorPrincipals;
+
   // Restyle root for servo's style system.
   //
   // We store this as an nsINode, rather than as an Element, so that we can store
   // the Document node as the restyle root if the entire document (along with all
   // document-level native-anonymous content) needs to be restyled.
   //
   // We also track which "descendant" bits (normal/animation-only/lazy-fc) the
   // root corresponds to.
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -618,16 +618,17 @@ skip-if = toolkit == 'android'
 [test_bug1295852.html]
 [test_bug1307730.html]
 [test_bug1308069.html]
 [test_bug1314032.html]
 [test_bug1318303.html]
 [test_bug1375050.html]
 [test_bug1381710.html]
 [test_bug1384658.html]
+[test_bug1399605.html]
 skip-if = toolkit == 'android'
 [test_caretPositionFromPoint.html]
 [test_change_policy.html]
 [test_clearTimeoutIntervalNoArg.html]
 [test_constructor-assignment.html]
 [test_constructor.html]
 [test_copyimage.html]
 subsuite = clipboard
@@ -809,16 +810,17 @@ skip-if = toolkit == 'android'
 skip-if = toolkit == 'android'
 [test_websocket3.html]
 skip-if = toolkit == 'android'
 [test_websocket4.html]
 skip-if = toolkit == 'android'
 [test_websocket5.html]
 skip-if = toolkit == 'android'
 [test_window_constructor.html]
+[test_window_content.html]
 [test_window_cross_origin_props.html]
 [test_window_define_nonconfigurable.html]
 [test_window_define_symbol.html]
 [test_window_element_enumeration.html]
 [test_window_enumeration.html]
 [test_window_extensible.html]
 [test_window_indexing.html]
 [test_window_keys.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_bug1399605.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1399605</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1399605">Mozilla Bug 1399605</a>
+<p id="display"></p>
+<div id="content">
+  <div id="target"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+  let observer = new IntersectionObserver(function() {
+    ok(true, "we are still observing");
+    SimpleTest.finish();
+  });
+  observer.observe(document.getElementById('target'));
+  observer.unobserve(document.getElementById('content'));
+  SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<div id="log">
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_window_content.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1400139
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1400139</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 1400139 **/
+  var props = [];
+  for (var prop in window) props.push(prop);
+  is(props.indexOf("content"), -1, "Should not have a property named 'content'");
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1400139">Mozilla Bug 1400139</a>
+<p id="display"></p>
+<div id="notcontent" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -539,33 +539,26 @@ class CGDOMProxyJSClass(CGThing):
         # We need one reserved slot (DOM_OBJECT_SLOT).
         flags = ["JSCLASS_IS_DOMJSCLASS",
                  "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount]
         # We don't use an IDL annotation for JSCLASS_EMULATES_UNDEFINED because
         # we don't want people ever adding that to any interface other than
         # HTMLAllCollection.  So just hardcode it here.
         if self.descriptor.interface.identifier.name == "HTMLAllCollection":
             flags.append("JSCLASS_EMULATES_UNDEFINED")
-        objectMovedHook = OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else 'nullptr'
         return fill(
             """
-            static const js::ClassExtension sClassExtension = PROXY_MAKE_EXT(
-                ${objectMoved}
-            );
-
             static const DOMJSClass sClass = {
-              PROXY_CLASS_WITH_EXT("${name}",
-                                   ${flags},
-                                   &sClassExtension),
+              PROXY_CLASS_DEF("${name}",
+                              ${flags}),
               $*{descriptor}
             };
             """,
             name=self.descriptor.interface.identifier.name,
             flags=" | ".join(flags),
-            objectMoved=objectMovedHook,
             descriptor=DOMClass(self.descriptor))
 
 
 class CGXrayExpandoJSClass(CGThing):
     """
     Generate a JSClass for an Xray expando object.  This is only
     needed if we have members in slots (for [Cached] or [StoreInSlot]
     stuff).
@@ -1726,30 +1719,42 @@ class CGClassFinalizeHook(CGAbstractClas
         CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME,
                                      'void', args)
 
     def generate_code(self):
         return finalizeHook(self.descriptor, self.name,
                             self.args[0].name, self.args[1].name).define()
 
 
+def objectMovedHook(descriptor, hookName, obj, old):
+    assert descriptor.wrapperCache
+    return fill("""
+	if (self) {
+	  UpdateWrapper(self, self, ${obj}, ${old});
+	}
+
+	return 0;
+	""",
+	obj=obj,
+	old=old)
+
+
 class CGClassObjectMovedHook(CGAbstractClassHook):
     """
     A hook for objectMovedOp, used to update the wrapper cache when an object it
     is holding moves.
     """
     def __init__(self, descriptor):
-        args = [Argument('JSObject*', 'obj'), Argument('const JSObject*', 'old')]
+        args = [Argument('JSObject*', 'obj'), Argument('JSObject*', 'old')]
         CGAbstractClassHook.__init__(self, descriptor, OBJECT_MOVED_HOOK_NAME,
-                                     'void', args)
+                                     'size_t', args)
 
     def generate_code(self):
-        assert self.descriptor.wrapperCache
-        return CGIfWrapper(CGGeneric("UpdateWrapper(self, self, obj, old);\n"),
-                           "self").define()
+        return objectMovedHook(self.descriptor, self.name,
+                               self.args[0].name, self.args[1].name)
 
 
 def JSNativeArguments():
     return [Argument('JSContext*', 'cx'),
             Argument('unsigned', 'argc'),
             Argument('JS::Value*', 'vp')]
 
 
@@ -2356,16 +2361,22 @@ def MakeClearCachedValueNativeName(membe
 def MakeJSImplClearCachedValueNativeName(member):
     return "_" + MakeClearCachedValueNativeName(member)
 
 
 def IDLToCIdentifier(name):
     return name.replace("-", "_")
 
 
+def EnumerabilityFlags(member):
+    if member.getExtendedAttribute("NonEnumerable"):
+        return "0"
+    return "JSPROP_ENUMERATE"
+
+
 class MethodDefiner(PropertyDefiner):
     """
     A class for defining methods on a prototype object.
     """
     def __init__(self, descriptor, name, static, unforgeable=False):
         assert not (static and unforgeable)
         PropertyDefiner.__init__(self, descriptor, name)
 
@@ -2416,28 +2427,21 @@ class MethodDefiner(PropertyDefiner):
                     "name": 'QueryInterface',
                     "methodInfo": False,
                     "length": 1,
                     "flags": "0",
                     "condition": MemberCondition(func=condition)
                 })
                 continue
 
-            # Iterable methods should be enumerable, maplike/setlike methods
-            # should not.
-            isMaplikeOrSetlikeMethod = (m.isMaplikeOrSetlikeOrIterableMethod() and
-                                        (m.maplikeOrSetlikeOrIterable.isMaplike() or
-                                         m.maplikeOrSetlikeOrIterable.isSetlike()))
             method = {
                 "name": m.identifier.name,
                 "methodInfo": not m.isStatic(),
                 "length": methodLength(m),
-                # Methods generated for a maplike/setlike declaration are not
-                # enumerable.
-                "flags": "JSPROP_ENUMERATE" if not isMaplikeOrSetlikeMethod else "0",
+                "flags": EnumerabilityFlags(m),
                 "condition": PropertyDefiner.getControllingCondition(m, descriptor),
                 "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
                 "returnsPromise": m.returnsPromise(),
                 "hasIteratorAlias": "@@iterator" in m.aliases
             }
 
             if m.isStatic():
                 method["nativeName"] = CppKeywords.checkMethodName(IDLToCIdentifier(m.identifier.name))
@@ -2723,19 +2727,17 @@ class AttrDefiner(PropertyDefiner):
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
 
     def generateArray(self, array, name):
         if len(array) == 0:
             return ""
 
         def flags(attr):
             unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else ""
-            # Attributes generated as part of a maplike/setlike declaration are
-            # not enumerable.
-            enumerable = " | JSPROP_ENUMERATE" if not attr.isMaplikeOrSetlikeAttr() else ""
+            enumerable = " | %s" % EnumerabilityFlags(attr)
             return ("JSPROP_SHARED" + enumerable + unforgeable)
 
         def getter(attr):
             if self.static:
                 if attr.type.isPromise():
                     raise TypeError("Don't know how to handle "
                                     "static Promise-returning "
                                     "attribute %s.%s" %
@@ -6846,18 +6848,17 @@ def getWrapTemplateForType(type, descrip
                 wrapMethod = "WrapNewBindingNonWrapperCachedObject"
                 wrapArgs = "cx, ${obj}, %s, ${jsvalHandle}" % result
             if isConstructorRetval:
                 wrapArgs += ", desiredProto"
             wrap = "%s(%s)" % (wrapMethod, wrapArgs)
             if not descriptor.hasXPConnectImpls:
                 # Can only fail to wrap as a new-binding object
                 # if they already threw an exception.
-                # XXX Assertion disabled for now, see bug 991271.
-                failed = ("MOZ_ASSERT(true || JS_IsExceptionPending(cx));\n" +
+                failed = ("MOZ_ASSERT(JS_IsExceptionPending(cx));\n" +
                           exceptionCode)
             else:
                 if descriptor.notflattened:
                     raise TypeError("%s has XPConnect impls but not flattened; "
                                     "fallback won't work correctly" %
                                     descriptor.interface.identifier.name)
                 # Try old-style wrapping for bindings which might be XPConnect impls.
                 failed = wrapAndSetPtr("HandleNewBindingWrappingFailure(cx, ${obj}, %s, ${jsvalHandle})" % result)
@@ -12403,16 +12404,30 @@ class CGDOMJSProxyHandler_finalize(Class
 
     def getBody(self):
         return (("%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(proxy);\n" %
                  (self.descriptor.nativeType, self.descriptor.nativeType)) +
                 finalizeHook(self.descriptor, FINALIZE_HOOK_NAME,
                              self.args[0].name, self.args[1].name).define())
 
 
+class CGDOMJSProxyHandler_objectMoved(ClassMethod):
+    def __init__(self, descriptor):
+        args = [Argument('JSObject*', 'obj'), Argument('JSObject*', 'old')]
+        ClassMethod.__init__(self, "objectMoved", "size_t", args,
+                             virtual=True, override=True, const=True)
+        self.descriptor = descriptor
+
+    def getBody(self):
+        return (("%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(obj);\n" %
+                 (self.descriptor.nativeType, self.descriptor.nativeType)) +
+                objectMovedHook(self.descriptor, OBJECT_MOVED_HOOK_NAME,
+                                self.args[0].name, self.args[1].name))
+
+
 class CGDOMJSProxyHandler_getElements(ClassMethod):
     def __init__(self, descriptor):
         assert descriptor.supportsIndexedProperties()
 
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
                 Argument('uint32_t', 'begin'),
                 Argument('uint32_t', 'end'),
@@ -12578,16 +12593,18 @@ class CGDOMJSProxyHandler(CGClass):
         if descriptor.operations['LegacyCaller']:
             methods.append(CGDOMJSProxyHandler_call())
             methods.append(CGDOMJSProxyHandler_isCallable())
         if descriptor.interface.hasProbablyShortLivingWrapper():
             if not descriptor.wrapperCache:
                 raise TypeError("Need a wrapper cache to support nursery "
                                 "allocation of DOM objects")
             methods.append(CGDOMJSProxyHandler_canNurseryAllocate())
+        if descriptor.wrapperCache:
+            methods.append(CGDOMJSProxyHandler_objectMoved(descriptor))
 
         if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
             parentClass = 'ShadowingDOMProxyHandler'
         else:
             parentClass = 'mozilla::dom::DOMProxyHandler'
 
         CGClass.__init__(self, 'DOMProxyHandler',
                          bases=[ClassBase(parentClass)],
@@ -12834,17 +12851,17 @@ class CGDescriptor(CGThing):
         if descriptor.concrete and not descriptor.proxy:
             if wantsAddProperty(descriptor):
                 cgThings.append(CGAddPropertyHook(descriptor))
 
             # Always have a finalize hook, regardless of whether the class
             # wants a custom hook.
             cgThings.append(CGClassFinalizeHook(descriptor))
 
-        if descriptor.concrete and descriptor.wrapperCache:
+        if descriptor.concrete and descriptor.wrapperCache and not descriptor.proxy:
             cgThings.append(CGClassObjectMovedHook(descriptor))
 
         # Generate the _ClearCachedFooValue methods before the property arrays that use them.
         if descriptor.interface.isJSImplemented():
             for m in clearableCachedAttrs(descriptor):
                 cgThings.append(CGJSImplClearCachedValueMethod(descriptor, m))
 
         # Need to output our generated hasinstance bits before
@@ -15366,18 +15383,17 @@ class CGJSImplMethod(CGJSImplMember):
             constructorArgs.append("js::GetObjectCompartment(scopeObj)")
             initCall = fill(
                 """
                 // Wrap the object before calling __Init so that __DOM_IMPL__ is available.
                 JS::Rooted<JSObject*> scopeObj(cx, globalHolder->GetGlobalJSObject());
                 MOZ_ASSERT(js::IsObjectInContextCompartment(scopeObj, cx));
                 JS::Rooted<JS::Value> wrappedVal(cx);
                 if (!GetOrCreateDOMReflector(cx, impl, &wrappedVal, aGivenProto)) {
-                  //XXX Assertion disabled for now, see bug 991271.
-                  MOZ_ASSERT(true || JS_IsExceptionPending(cx));
+                  MOZ_ASSERT(JS_IsExceptionPending(cx));
                   aRv.Throw(NS_ERROR_UNEXPECTED);
                   return nullptr;
                 }
                 // Initialize the object with the constructor arguments.
                 impl->mImpl->__Init(${args});
                 if (aRv.Failed()) {
                   return nullptr;
                 }
--- a/dom/bindings/SimpleGlobalObject.cpp
+++ b/dom/bindings/SimpleGlobalObject.cpp
@@ -45,22 +45,23 @@ static void
 SimpleGlobal_finalize(js::FreeOp *fop, JSObject *obj)
 {
   SimpleGlobalObject* globalObject =
     static_cast<SimpleGlobalObject*>(JS_GetPrivate(obj));
   globalObject->ClearWrapper(obj);
   NS_RELEASE(globalObject);
 }
 
-static void
-SimpleGlobal_moved(JSObject *obj, const JSObject *old)
+static size_t
+SimpleGlobal_moved(JSObject *obj, JSObject *old)
 {
   SimpleGlobalObject* globalObject =
     static_cast<SimpleGlobalObject*>(JS_GetPrivate(obj));
   globalObject->UpdateWrapper(obj, old);
+  return 0;
 }
 
 static const js::ClassOps SimpleGlobalClassOps = {
     nullptr,
     nullptr,
     nullptr,
     JS_NewEnumerateStandardClasses,
     JS_ResolveStandardClass,
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -3698,16 +3698,21 @@ class IDLMaplikeOrSetlikeOrIterableBase(
                 [IDLExtendedAttribute(self.location, ("DependsOn", "Everything")),
                  IDLExtendedAttribute(self.location, ("Affects", "Nothing"))])
         if newObject:
             method.addExtendedAttributes(
                 [IDLExtendedAttribute(self.location, ("NewObject",))])
         if isIteratorAlias:
             method.addExtendedAttributes(
                 [IDLExtendedAttribute(self.location, ("Alias", "@@iterator"))])
+        # Methods generated for iterables should be enumerable, but the ones for
+        # maplike/setlike should not be.
+        if not self.isIterable():
+            method.addExtendedAttributes(
+                [IDLExtendedAttribute(self.location, ("NonEnumerable",))])
         members.append(method)
 
     def resolve(self, parentScope):
         if self.keyType:
             self.keyType.resolveType(parentScope)
         if self.valueType:
             self.valueType.resolveType(parentScope)
 
@@ -3821,21 +3826,25 @@ class IDLMaplikeOrSetlike(IDLMaplikeOrSe
 
     def expand(self, members, isJSImplemented):
         """
         In order to take advantage of all of the method machinery in Codegen,
         we generate our functions as if they were part of the interface
         specification during parsing.
         """
         # Both maplike and setlike have a size attribute
-        members.append(IDLAttribute(self.location,
-                                    IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), "size"),
-                                    BuiltinTypes[IDLBuiltinType.Types.unsigned_long],
-                                    True,
-                                    maplikeOrSetlike=self))
+        sizeAttr = IDLAttribute(self.location,
+                                IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), "size"),
+                                BuiltinTypes[IDLBuiltinType.Types.unsigned_long],
+                                True,
+                                maplikeOrSetlike=self)
+        # This should be non-enumerable.
+        sizeAttr.addExtendedAttributes(
+                [IDLExtendedAttribute(self.location, ("NonEnumerable",))])
+        members.append(sizeAttr)
         self.reserved_ro_names = ["size"]
         self.disallowedMemberNames.append("size")
 
         # object entries()
         self.addMethod("entries", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
                        affectsNothing=True, isIteratorAlias=self.isMaplike())
         # object keys()
         self.addMethod("keys", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
@@ -3961,17 +3970,18 @@ class IDLConst(IDLInterfaceMember):
 
     def handleExtendedAttribute(self, attr):
         identifier = attr.identifier()
         if identifier == "Exposed":
             convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
         elif (identifier == "Pref" or
               identifier == "ChromeOnly" or
               identifier == "Func" or
-              identifier == "SecureContext"):
+              identifier == "SecureContext" or
+              identifier == "NonEnumerable"):
             # Known attributes that we don't need to do anything with here
             pass
         else:
             raise WebIDLError("Unknown extended attribute %s on constant" % identifier,
                               [attr.location])
         IDLInterfaceMember.handleExtendedAttribute(self, attr)
 
     def _getDependentObjects(self):
@@ -4339,17 +4349,18 @@ class IDLAttribute(IDLInterfaceMember):
               identifier == "Func" or
               identifier == "SecureContext" or
               identifier == "Frozen" or
               identifier == "NewObject" or
               identifier == "UnsafeInPrerendering" or
               identifier == "NeedsSubjectPrincipal" or
               identifier == "NeedsCallerType" or
               identifier == "ReturnValueNeedsContainsHack" or
-              identifier == "BinaryName"):
+              identifier == "BinaryName" or
+              identifier == "NonEnumerable"):
             # Known attributes that we don't need to do anything with here
             pass
         else:
             raise WebIDLError("Unknown extended attribute %s on attribute" % identifier,
                               [attr.location])
         IDLInterfaceMember.handleExtendedAttribute(self, attr)
 
     def resolve(self, parentScope):
@@ -5076,17 +5087,18 @@ class IDLMethod(IDLInterfaceMember, IDLS
               identifier == "UnsafeInPrerendering" or
               identifier == "Pref" or
               identifier == "Deprecated" or
               identifier == "Func" or
               identifier == "SecureContext" or
               identifier == "BinaryName" or
               identifier == "NeedsSubjectPrincipal" or
               identifier == "NeedsCallerType" or
-              identifier == "StaticClassOverride"):
+              identifier == "StaticClassOverride" or
+              identifier == "NonEnumerable"):
             # Known attributes that we don't need to do anything with here
             pass
         else:
             raise WebIDLError("Unknown extended attribute %s on method" % identifier,
                               [attr.location])
         IDLInterfaceMember.handleExtendedAttribute(self, attr)
 
     def returnsPromise(self):
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -973,16 +973,20 @@ public:
   void PassArgsWithDefaults(JSContext*, const Optional<int32_t>&,
                             TestInterface*, const Dict&, double,
                             const Optional<float>&);
 
   void SetDashed_attribute(int8_t);
   int8_t Dashed_attribute();
   void Dashed_method();
 
+  bool NonEnumerableAttr() const;
+  void SetNonEnumerableAttr(bool);
+  void NonEnumerableMethod();
+
   // Methods and properties imported via "implements"
   bool ImplementedProperty();
   void SetImplementedProperty(bool);
   void ImplementedMethod();
   bool ImplementedParentProperty();
   void SetImplementedParentProperty(bool);
   void ImplementedParentMethod();
   bool IndirectlyImplementedProperty();
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -970,16 +970,24 @@ interface TestInterface {
   attribute any jsonifierShouldSkipThis;
   attribute TestParentInterface jsonifierShouldSkipThis2;
   attribute TestCallbackInterface jsonifierShouldSkipThis3;
   jsonifier;
 
   attribute byte dashed-attribute;
   void dashed-method();
 
+  // [NonEnumerable] tests
+  [NonEnumerable]
+  attribute boolean nonEnumerableAttr;
+  [NonEnumerable]
+  const boolean nonEnumerableConst = true;
+  [NonEnumerable]
+  void nonEnumerableMethod();
+
   // If you add things here, add them to TestExampleGen and TestJSImplGen as well
 };
 
 interface TestParentInterface {
 };
 
 interface TestChildInterface : TestParentInterface {
 };
--- a/dom/bindings/test/TestExampleGen.webidl
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -797,16 +797,24 @@ interface TestExampleInterface {
   attribute any jsonifierShouldSkipThis;
   attribute TestParentInterface jsonifierShouldSkipThis2;
   attribute TestCallbackInterface jsonifierShouldSkipThis3;
   jsonifier;
 
   attribute byte dashed-attribute;
   void dashed-method();
 
+  // [NonEnumerable] tests
+  [NonEnumerable]
+  attribute boolean nonEnumerableAttr;
+  [NonEnumerable]
+  const boolean nonEnumerableConst = true;
+  [NonEnumerable]
+  void nonEnumerableMethod();
+
   // If you add things here, add them to TestCodeGen and TestJSImplGen as well
 };
 
 interface TestExampleProxyInterface {
   getter long longIndexedGetter(unsigned long ix);
   setter creator void longIndexedSetter(unsigned long y, long z);
   readonly attribute unsigned long length;
   stringifier DOMString myStringifier();
--- a/dom/bindings/test/TestJSImplGen.webidl
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -817,16 +817,24 @@ interface TestJSImplInterface {
   attribute any jsonifierShouldSkipThis;
   attribute TestParentInterface jsonifierShouldSkipThis2;
   attribute TestCallbackInterface jsonifierShouldSkipThis3;
   jsonifier;
 
   attribute byte dashed-attribute;
   void dashed-method();
 
+  // [NonEnumerable] tests
+  [NonEnumerable]
+  attribute boolean nonEnumerableAttr;
+  [NonEnumerable]
+  const boolean nonEnumerableConst = true;
+  [NonEnumerable]
+  void nonEnumerableMethod();
+
   // If you add things here, add them to TestCodeGen as well
 };
 
 [NavigatorProperty="TestNavigator", JSImplementation="@mozilla.org/test;1"]
 interface TestNavigator {
 };
 
 [Constructor, NavigatorProperty="TestNavigatorWithConstructor", JSImplementation="@mozilla.org/test;1"]
--- a/dom/file/MultipartBlobImpl.cpp
+++ b/dom/file/MultipartBlobImpl.cpp
@@ -80,17 +80,17 @@ MultipartBlobImpl::GetInternalStream(nsI
     }
 
     aRv = stream->AppendStream(scratchStream);
     if (NS_WARN_IF(aRv.Failed())) {
       return;
     }
   }
 
-  stream.forget(aStream);
+  CallQueryInterface(stream, aStream);
 }
 
 already_AddRefed<BlobImpl>
 MultipartBlobImpl::CreateSlice(uint64_t aStart, uint64_t aLength,
                                const nsAString& aContentType,
                                ErrorResult& aRv)
 {
   // If we clamped to nothing we create an empty blob
--- a/dom/html/HTMLFormSubmission.cpp
+++ b/dom/html/HTMLFormSubmission.cpp
@@ -389,18 +389,23 @@ FSURLEncoded::URLEncode(const nsAString&
 } // anonymous namespace
 
 // --------------------------------------------------------------------------
 
 FSMultipartFormData::FSMultipartFormData(NotNull<const Encoding*> aEncoding,
                                          nsIContent* aOriginatingElement)
   : EncodingFormSubmission(aEncoding, aOriginatingElement)
 {
-  mPostDataStream =
+  mPostData =
     do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+
+  nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(mPostData);
+  MOZ_ASSERT(SameCOMIdentity(mPostData, inputStream));
+  mPostDataStream = inputStream;
+
   mTotalLength = 0;
 
   mBoundary.AssignLiteral("---------------------------");
   mBoundary.AppendInt(rand());
   mBoundary.AppendInt(rand());
   mBoundary.AppendInt(rand());
 }
 
@@ -595,17 +600,17 @@ FSMultipartFormData::AddDataChunk(const 
 
   // We should not try to append an invalid stream. That will happen for example
   // if we try to update a file that actually do not exist.
   if (aInputStream) {
     // We need to dump the data up to this point into the POST data stream
     // here, since we're about to add the file input stream
     AddPostDataStream();
 
-    mPostDataStream->AppendStream(aInputStream);
+    mPostData->AppendStream(aInputStream);
     mTotalLength += aInputStreamSize;
   }
 
   // CRLF after file
   mPostDataChunk.AppendLiteral(CRLF);
 }
 
 nsresult
@@ -635,17 +640,17 @@ FSMultipartFormData::AddPostDataStream()
 {
   nsresult rv = NS_OK;
 
   nsCOMPtr<nsIInputStream> postDataChunkStream;
   rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream),
                                 mPostDataChunk);
   NS_ASSERTION(postDataChunkStream, "Could not open a stream for POST!");
   if (postDataChunkStream) {
-    mPostDataStream->AppendStream(postDataChunkStream);
+    mPostData->AppendStream(postDataChunkStream);
     mTotalLength += mPostDataChunk.Length();
   }
 
   mPostDataChunk.Truncate();
 
   return rv;
 }
 
--- a/dom/html/HTMLFormSubmission.h
+++ b/dom/html/HTMLFormSubmission.h
@@ -188,17 +188,23 @@ private:
                     const nsACString& aContentType,
                     nsIInputStream* aInputStream,
                     uint64_t aInputStreamSize);
   /**
    * The post data stream as it is so far.  This is a collection of smaller
    * chunks--string streams and file streams interleaved to make one big POST
    * stream.
    */
-  nsCOMPtr<nsIMultiplexInputStream> mPostDataStream;
+  nsCOMPtr<nsIMultiplexInputStream> mPostData;
+
+  /**
+   * The same stream, but as an nsIInputStream.
+   * Raw pointers because it is just QI of mInputStream.
+   */
+  nsIInputStream* mPostDataStream;
 
   /**
    * The current string chunk.  When a file is hit, the string chunk gets
    * wrapped up into an input stream and put into mPostDataStream so that the
    * file input stream can then be appended and everything is in the right
    * order.  Then the string chunk gets appended to again as we process more
    * name/value pairs.
    */
--- a/dom/interfaces/base/nsIDOMChromeWindow.idl
+++ b/dom/interfaces/base/nsIDOMChromeWindow.idl
@@ -6,76 +6,46 @@
 #include "domstubs.idl"
 
 interface nsIBrowserDOMWindow;
 interface nsIDOMElement;
 interface nsIDOMEvent;
 interface nsIMessageBroadcaster;
 interface mozIDOMWindowProxy;
 
-[scriptable, uuid(78bdcb41-1efa-409f-aaba-70842213f80f)]
+// Scriptable only so Components.interfaces.nsIDOMChromeWindow works.
+[scriptable, builtinclass, uuid(78bdcb41-1efa-409f-aaba-70842213f80f)]
 interface nsIDOMChromeWindow : nsISupports
 {
-  const unsigned short STATE_MAXIMIZED = 1;
-  const unsigned short STATE_MINIMIZED = 2;
-  const unsigned short STATE_NORMAL = 3;
-  const unsigned short STATE_FULLSCREEN = 4;
-
-  readonly attribute unsigned short              windowState;
-
   /**
    * browserDOMWindow provides access to yet another layer of
    * utility functions implemented by chrome script. It will be null
    * for DOMWindows not corresponding to browsers.
    */
-           attribute nsIBrowserDOMWindow browserDOMWindow;
-
-  void                      getAttention();
-
-  void                      getAttentionWithCycleCount(in long aCycleCount);
-
-  void                      setCursor(in DOMString cursor);
+  [noscript]
+  readonly attribute nsIBrowserDOMWindow browserDOMWindow;
 
-  void                      maximize();
-  void                      minimize();
-  void                      restore();
-
-  /**
-   * Notify a default button is loaded on a dialog or a wizard.
-   * defaultButton is the default button.
-   */
-  void notifyDefaultButtonLoaded(in nsIDOMElement defaultButton);
-
+  [noscript]
   readonly attribute nsIMessageBroadcaster messageManager;
 
   /**
    * Returns the message manager identified by the given group name that
    * manages all frame loaders belonging to that group.
    */
+  [noscript]
   nsIMessageBroadcaster getGroupMessageManager(in AString group);
 
   /**
-   * On some operating systems, we must allow the window manager to
-   * handle window dragging. This function tells the window manager to
-   * start dragging the window. This function will fail unless called
-   * while the left mouse button is held down, callers must check this.
-   *
-   * The optional panel argument should be set when moving a panel.
-   *
-   * Returns NS_ERROR_NOT_IMPLEMENTED (and thus throws in JS) if the OS
-   * doesn't support this.
-   */
-  void beginWindowMove(in nsIDOMEvent mouseDownEvent, [optional] in nsIDOMElement panel);
-
-  /**
    * These methods provide a way to specify the opener value for the content in
    * the window before the content itself is created. This is important in order
    * to set the DocGroup of a document, as the opener must be set before the
    * document is created.
    *
    * SetOpenerForInitialContentBrowser is used to set which opener will be used,
    * and TakeOpenerForInitialContentBrowser is used by nsXULElement in order to
    * take the value set earlier, and null out the value in the
    * nsIDOMChromeWindow.
    */
+  [noscript]
   void setOpenerForInitialContentBrowser(in mozIDOMWindowProxy aOpener);
+  [noscript]
   mozIDOMWindowProxy takeOpenerForInitialContentBrowser();
 };
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -325,16 +325,18 @@ LargeAllocationNonWin32=This page would 
 # LOCALIZATION NOTE: Do not translate URL.createObjectURL(MediaStream).
 URLCreateObjectURL_MediaStreamWarning=URL.createObjectURL(MediaStream) is deprecated and will be removed soon.
 # LOCALIZATION NOTE: Do not translate MozAutoGainControl or autoGainControl.
 MozAutoGainControlWarning=mozAutoGainControl is deprecated. Use autoGainControl instead.
 # LOCALIZATION NOTE: Do not translate mozNoiseSuppression or noiseSuppression.
 MozNoiseSuppressionWarning=mozNoiseSuppression is deprecated. Use noiseSuppression instead.
 # LOCALIZATION NOTE: Do not translate xml:base.
 XMLBaseAttributeWarning=Use of xml:base attribute is deprecated and will be removed soon. Please remove any use of it.
+# LOCALIZATION NOTE: Do not translate "content", "Window", and "window.top"
+WindowContentUntrustedWarning=The ‘content’ attribute of Window objects is deprecated.  Please use ‘window.top’ instead.
 # LOCALIZATION NOTE: %S is the tag name of the element that starts the loop
 SVGReferenceLoopWarning=There is an SVG <%S> reference loop in this document, which will prevent the document rendering correctly.
 # LOCALIZATION NOTE: %S is the tag name of the element that starts the chain
 SVGReferenceChainLengthExceededWarning=There is an SVG <%S> reference chain which is too long in this document, which will prevent the document rendering correctly.
 # LOCALIZATION NOTE: Do not translate "<script>".
 ScriptSourceEmpty=‘%S’ attribute of <script> element is empty.
 # LOCALIZATION NOTE: Do not translate "<script>".
 ScriptSourceInvalidUri=‘%S’ attribute of <script> element is not a valid URI: “%S”
--- a/dom/network/TCPSocket.cpp
+++ b/dom/network/TCPSocket.cpp
@@ -205,25 +205,26 @@ TCPSocket::CreateStream()
     mInputStreamScriptable = do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mInputStreamScriptable->Init(mSocketInputStream);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   mMultiplexStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mMultiplexStream);
 
   mMultiplexStreamCopier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsISocketTransportService> sts =
       do_GetService("@mozilla.org/network/socket-transport-service;1");
 
   nsCOMPtr<nsIEventTarget> target = do_QueryInterface(sts);
-  rv = mMultiplexStreamCopier->Init(mMultiplexStream,
+  rv = mMultiplexStreamCopier->Init(stream,
                                     mSocketOutputStream,
                                     target,
                                     true, /* source buffered */
                                     false, /* sink buffered */
                                     BUFFER_SIZE,
                                     false, /* close source */
                                     false); /* close sink */
   NS_ENSURE_SUCCESS(rv, rv);
@@ -591,17 +592,18 @@ TCPSocket::Ssl()
 uint64_t
 TCPSocket::BufferedAmount()
 {
   if (mSocketBridgeChild) {
     return mBufferedAmount;
   }
   if (mMultiplexStream) {
     uint64_t available = 0;
-    mMultiplexStream->Available(&available);
+    nsCOMPtr<nsIInputStream> stream(do_QueryInterface(mMultiplexStream));
+    stream->Available(&available);
     return available;
   }
   return 0;
 }
 
 void
 TCPSocket::Suspend()
 {
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -227,45 +227,39 @@ public:
   }
   bool construct(JSContext* cx, JS::Handle<JSObject*> proxy,
                  const JS::CallArgs& args) const override;
 
   bool finalizeInBackground(const JS::Value& priv) const override {
     return false;
   }
   void finalize(JSFreeOp* fop, JSObject* proxy) const override;
+
+  size_t objectMoved(JSObject* obj, JSObject* old) const override;
 };
 
 const char NPObjWrapperProxyHandler::family = 0;
 const NPObjWrapperProxyHandler NPObjWrapperProxyHandler::singleton;
 
 static bool
 NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                      bool* resolved, JS::MutableHandle<JSObject*> method);
 
-static void
-NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old);
-
 static bool
 NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
 
 static bool
 CreateNPObjectMember(NPP npp, JSContext *cx,
                      JS::Handle<JSObject*> obj, NPObject* npobj,
                      JS::Handle<jsid> id,  NPVariant* getPropertyResult,
                      JS::MutableHandle<JS::Value> vp);
 
-static const js::ClassExtension sNPObjWrapperProxyClassExtension = PROXY_MAKE_EXT(
-    NPObjWrapper_ObjectMoved
-);
-
-const js::Class sNPObjWrapperProxyClass = PROXY_CLASS_WITH_EXT(
+const js::Class sNPObjWrapperProxyClass = PROXY_CLASS_DEF(
     NPRUNTIME_JSCLASS_NAME,
-    JSCLASS_HAS_RESERVED_SLOTS(1),
-    &sNPObjWrapperProxyClassExtension);
+    JSCLASS_HAS_RESERVED_SLOTS(1));
 
 typedef struct NPObjectMemberPrivate {
     JS::Heap<JSObject *> npobjWrapper;
     JS::Heap<JS::Value> fieldValue;
     JS::Heap<jsid> methodName;
     NPP   npp;
 } NPObjectMemberPrivate;
 
@@ -1782,39 +1776,40 @@ NPObjWrapperProxyHandler::finalize(JSFre
     }
   }
 
   if (!sDelayedReleases)
     sDelayedReleases = new nsTArray<NPObject*>;
   sDelayedReleases->AppendElement(npobj);
 }
 
-static void
-NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old)
+size_t
+NPObjWrapperProxyHandler::objectMoved(JSObject *obj, JSObject *old) const
 {
   // The wrapper JSObject has been moved, so we need to update the entry in the
   // sNPObjWrappers hash table, if present.
 
   if (!sNPObjWrappers) {
-    return;
+    return 0;
   }
 
   NPObject *npobj = (NPObject *)js::GetProxyPrivate(obj).toPrivate();
   if (!npobj) {
-    return;
+    return 0;
   }
 
   // Calling PLDHashTable::Search() will not result in GC.
   JS::AutoSuppressGCAnalysis nogc;
 
   auto entry =
     static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj));
   MOZ_ASSERT(entry && entry->mJSObj);
   MOZ_ASSERT(entry->mJSObj == old);
   entry->mJSObj = obj;
+  return 0;
 }
 
 bool
 NPObjWrapperProxyHandler::call(JSContext* cx, JS::Handle<JSObject*> proxy,
                                const JS::CallArgs& args) const
 {
   return CallNPMethodInternal(cx, proxy, args.length(), args.array(),
                               args.rval().address(), false);
--- a/dom/presentation/PresentationTCPSessionTransport.cpp
+++ b/dom/presentation/PresentationTCPSessionTransport.cpp
@@ -235,30 +235,31 @@ PresentationTCPSessionTransport::CreateS
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   mMultiplexStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
+  nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mMultiplexStream);
 
   mMultiplexStreamCopier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<nsISocketTransportService> sts =
     do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
   if (NS_WARN_IF(!sts)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsCOMPtr<nsIEventTarget> target = do_QueryInterface(sts);
-  rv = mMultiplexStreamCopier->Init(mMultiplexStream,
+  rv = mMultiplexStreamCopier->Init(stream,
                                     mSocketOutputStream,
                                     target,
                                     true, /* source buffered */
                                     false, /* sink buffered */
                                     BUFFER_SIZE,
                                     false, /* close source */
                                     false); /* close sink */
   if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -218,17 +218,17 @@ U2F::Register(const nsAString& aAppId,
 
   RefPtr<U2FManager> mgr = U2FManager::GetOrCreate();
   MOZ_ASSERT(mgr);
   if (!mgr || mRegisterCallback.isSome()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
-  MOZ_ASSERT(!mPromiseHolder.Exists());
+  Cancel();
   MOZ_ASSERT(mRegisterCallback.isNothing());
   mRegisterCallback = Some(nsMainThreadPtrHandle<U2FRegisterCallback>(
                         new nsMainThreadPtrHolder<U2FRegisterCallback>(
                             "U2F::Register::callback", &aCallback)));
 
   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
 
   // Evaluate the AppID
@@ -315,17 +315,17 @@ U2F::Sign(const nsAString& aAppId,
 
   RefPtr<U2FManager> mgr = U2FManager::GetOrCreate();
   MOZ_ASSERT(mgr);
   if (!mgr || mSignCallback.isSome()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
-  MOZ_ASSERT(!mPromiseHolder.Exists());
+  Cancel();
   MOZ_ASSERT(mSignCallback.isNothing());
   mSignCallback = Some(nsMainThreadPtrHandle<U2FSignCallback>(
                     new nsMainThreadPtrHolder<U2FSignCallback>(
                         "U2F::Sign::callback", &aCallback)));
 
   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
 
   // Evaluate the AppID
@@ -380,10 +380,37 @@ U2F::Sign(const nsAString& aAppId,
               response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
 
               ExecuteCallback(response, localCb);
               localReqHolder.Complete();
           })
   ->Track(mPromiseHolder);
 }
 
+void
+U2F::Cancel()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  const ErrorCode errorCode = ErrorCode::OTHER_ERROR;
+
+  if (mRegisterCallback.isSome()) {
+    RegisterResponse response;
+    response.mErrorCode.Construct(static_cast<uint32_t>(errorCode));
+    ExecuteCallback(response, mRegisterCallback);
+  }
+
+  if (mSignCallback.isSome()) {
+    SignResponse response;
+    response.mErrorCode.Construct(static_cast<uint32_t>(errorCode));
+    ExecuteCallback(response, mSignCallback);
+  }
+
+  RefPtr<U2FManager> mgr = U2FManager::Get();
+  if (mgr) {
+    mgr->Cancel(NS_ERROR_DOM_OPERATION_ERR);
+  }
+
+  mPromiseHolder.DisconnectIfExists();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -65,16 +65,19 @@ public:
   Sign(const nsAString& aAppId,
        const nsAString& aChallenge,
        const Sequence<RegisteredKey>& aRegisteredKeys,
        U2FSignCallback& aCallback,
        const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
        ErrorResult& aRv);
 
 private:
+  void
+  Cancel();
+
   nsString mOrigin;
   nsCOMPtr<nsPIDOMWindowInner> mParent;
   nsCOMPtr<nsISerialEventTarget> mEventTarget;
   Maybe<nsMainThreadPtrHandle<U2FRegisterCallback>> mRegisterCallback;
   Maybe<nsMainThreadPtrHandle<U2FSignCallback>> mSignCallback;
   MozPromiseRequestHolder<U2FPromise> mPromiseHolder;
 
   ~U2F();
--- a/dom/u2f/tests/frame_register_sign.html
+++ b/dom/u2f/tests/frame_register_sign.html
@@ -99,27 +99,30 @@ async function doTests() {
   .then(function(params){
     state.appParam = params.appParam;
     state.challengeParam = params.challengeParam;
     return state.attestationCert.getPublicKey();
   }).then(function(attestationPublicKey) {
     var signedData = assembleRegistrationSignedData(state.appParam, state.challengeParam, state.keyHandleBytes, state.publicKeyBytes);
     return verifySignature(attestationPublicKey, signedData, state.attestationSig);
   }).then(function(verified) {
-    local_ok(verified, "Attestation Certificate signature verified")
+    local_ok(verified, "Attestation Certificate signature verified");
      // Import the public key of the U2F token into WebCrypto
-    return importPublicKey(state.publicKeyBytes)
+    return importPublicKey(state.publicKeyBytes);
   }).then(function(key) {
     state.publicKey = key;
-    local_ok(true, "Imported public key")
+    local_ok(true, "Imported public key");
 
     // Ensure the attestation certificate is properly self-signed
-    return state.attestationCert.verify()
+    return state.attestationCert.verify();
   }).then(function(verified) {
-    local_ok(verified, "Register attestation signature verified")
+    if (!verified) {
+      local_ok(verified, "Cert problem: " + bytesToBase64UrlSafe(state.attestation));
+    }
+    local_ok(verified, "Register attestation signature verified");
   });
 
   state.regKey = {
     version: state.version,
     keyHandle: state.keyHandle,
   };
 
   // Test that we don't re-register if we provide regKey as an
--- a/dom/webauthn/U2FHIDTokenManager.cpp
+++ b/dom/webauthn/U2FHIDTokenManager.cpp
@@ -13,34 +13,34 @@ namespace dom {
 static StaticMutex gInstanceMutex;
 static U2FHIDTokenManager* gInstance;
 static nsIThread* gPBackgroundThread;
 
 static void
 u2f_register_callback(uint64_t aTransactionId, rust_u2f_result* aResult)
 {
   StaticMutexAutoLock lock(gInstanceMutex);
-  if (NS_WARN_IF(!gInstance || !gPBackgroundThread)) {
+  if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
     return;
   }
 
   UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
   nsCOMPtr<nsIRunnable> r(NewNonOwningRunnableMethod<UniquePtr<U2FResult>&&>(
       "U2FHIDTokenManager::HandleRegisterResult", gInstance,
       &U2FHIDTokenManager::HandleRegisterResult, Move(rv)));
 
   MOZ_ALWAYS_SUCCEEDS(gPBackgroundThread->Dispatch(r.forget(),
                                                    NS_DISPATCH_NORMAL));
 }
 
 static void
 u2f_sign_callback(uint64_t aTransactionId, rust_u2f_result* aResult)
 {
   StaticMutexAutoLock lock(gInstanceMutex);
-  if (NS_WARN_IF(!gInstance || !gPBackgroundThread)) {
+  if (!gInstance || NS_WARN_IF(!gPBackgroundThread)) {
     return;
   }
 
   UniquePtr<U2FResult> rv = MakeUnique<U2FResult>(aTransactionId, aResult);
   nsCOMPtr<nsIRunnable> r(NewNonOwningRunnableMethod<UniquePtr<U2FResult>&&>(
       "U2FHIDTokenManager::HandleSignResult", gInstance,
       &U2FHIDTokenManager::HandleSignResult, Move(rv)));
 
@@ -58,25 +58,31 @@ U2FHIDTokenManager::U2FHIDTokenManager()
   mU2FManager = rust_u2f_mgr_new();
   gPBackgroundThread = NS_GetCurrentThread();
   MOZ_ASSERT(gPBackgroundThread, "This should never be null!");
   gInstance = this;
 }
 
 U2FHIDTokenManager::~U2FHIDTokenManager()
 {
-  StaticMutexAutoLock lock(gInstanceMutex);
-  MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
+  {
+    StaticMutexAutoLock lock(gInstanceMutex);
+    MOZ_ASSERT(NS_GetCurrentThread() == gPBackgroundThread);
+
+    mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+    mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
 
-  mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
-  mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__);
+    gInstance = nullptr;
+  }
 
+  // Release gInstanceMutex before we call U2FManager::drop(). It will wait
+  // for the work queue thread to join, and that requires the
+  // u2f_{register,sign}_callback to lock and return.
   rust_u2f_mgr_free(mU2FManager);
   mU2FManager = nullptr;
-  gInstance = nullptr;
 }
 
 // A U2F Register operation causes a new key pair to be generated by the token.
 // The token then returns the public key of the key pair, and a handle to the
 // private key, which is a fancy way of saying "key wrapped private key", as
 // well as the generated attestation certificate and a signature using that
 // certificate's private key.
 //
--- a/dom/webauthn/u2f-hid-rs/Cargo.toml
+++ b/dom/webauthn/u2f-hid-rs/Cargo.toml
@@ -1,25 +1,25 @@
 [package]
 name = "u2fhid"
 version = "0.1.0"
 authors = ["Kyle Machulis <kyle@nonpolynomial.com>", "J.C. Jones <jc@mozilla.com>", "Tim Taubert <ttaubert@mozilla.com>"]
-build = "build.rs"
 
 [target.'cfg(target_os = "linux")'.dependencies]
 libudev = "^0.2"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation-sys = "0.3.1"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 winapi = "0.2.8"
 
 [dependencies]
 rand = "0.3"
 log = "0.3"
 env_logger = "0.4.1"
 libc = "^0.2"
 boxfnonce = "0.0.3"
+runloop = "0.1.0"
 
 [dev-dependencies]
 rust-crypto = "^0.2"
 base64 = "^0.4"
deleted file mode 100644
--- a/dom/webauthn/u2f-hid-rs/build.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-/* 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/. */
-
-fn main() {
-    #[cfg(any(target_os = "macos"))]
-    println!("cargo:rustc-link-lib=framework=IOKit");
-}
--- a/dom/webauthn/u2f-hid-rs/src/lib.rs
+++ b/dom/webauthn/u2f-hid-rs/src/lib.rs
@@ -27,19 +27,19 @@ pub mod platform;
 #[path = "stub/mod.rs"]
 pub mod platform;
 
 #[macro_use]
 extern crate log;
 extern crate rand;
 extern crate libc;
 extern crate boxfnonce;
+extern crate runloop;
 
 mod consts;
-mod runloop;
 mod u2ftypes;
 mod u2fprotocol;
 
 mod manager;
 pub use manager::U2FManager;
 
 mod capi;
 pub use capi::*;
--- a/dom/webauthn/u2f-hid-rs/src/linux/mod.rs
+++ b/dom/webauthn/u2f-hid-rs/src/linux/mod.rs
@@ -35,17 +35,17 @@ impl PlatformManager {
         application: Vec<u8>,
         callback: OnceCallback<Vec<u8>>,
     ) {
         // Abort any prior register/sign calls.
         self.cancel();
 
         let cbc = callback.clone();
 
-        let thread = RunLoop::new(
+        let thread = RunLoop::new_with_timeout(
             move |alive| {
                 let mut devices = DeviceMap::new();
                 let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
 
                 while alive() && monitor.alive() {
                     // Add/remove devices.
                     for event in monitor.events() {
                         devices.process_event(event);
@@ -82,17 +82,17 @@ impl PlatformManager {
         key_handles: Vec<Vec<u8>>,
         callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
     ) {
         // Abort any prior register/sign calls.
         self.cancel();
 
         let cbc = callback.clone();
 
-        let thread = RunLoop::new(
+        let thread = RunLoop::new_with_timeout(
             move |alive| {
                 let mut devices = DeviceMap::new();
                 let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
 
                 while alive() && monitor.alive() {
                     // Add/remove devices.
                     for event in monitor.events() {
                         devices.process_event(event);
--- a/dom/webauthn/u2f-hid-rs/src/linux/monitor.rs
+++ b/dom/webauthn/u2f-hid-rs/src/linux/monitor.rs
@@ -56,58 +56,55 @@ pub struct Monitor {
     // Handle to the thread loop.
     thread: RunLoop,
 }
 
 impl Monitor {
     pub fn new() -> io::Result<Self> {
         let (tx, rx) = channel();
 
-        let thread = RunLoop::new(
-            move |alive| -> io::Result<()> {
-                let ctx = libudev::Context::new()?;
-                let mut enumerator = libudev::Enumerator::new(&ctx)?;
-                enumerator.match_subsystem(UDEV_SUBSYSTEM)?;
+        let thread = RunLoop::new(move |alive| -> io::Result<()> {
+            let ctx = libudev::Context::new()?;
+            let mut enumerator = libudev::Enumerator::new(&ctx)?;
+            enumerator.match_subsystem(UDEV_SUBSYSTEM)?;
 
-                // Iterate all existing devices.
-                for dev in enumerator.scan_devices()? {
-                    if let Some(path) = dev.devnode().map(|p| p.to_owned().into_os_string()) {
-                        tx.send(Event::Add(path)).map_err(to_io_err)?;
-                    }
+            // Iterate all existing devices.
+            for dev in enumerator.scan_devices()? {
+                if let Some(path) = dev.devnode().map(|p| p.to_owned().into_os_string()) {
+                    tx.send(Event::Add(path)).map_err(to_io_err)?;
                 }
+            }
 
-                let mut monitor = libudev::Monitor::new(&ctx)?;
-                monitor.match_subsystem(UDEV_SUBSYSTEM)?;
+            let mut monitor = libudev::Monitor::new(&ctx)?;
+            monitor.match_subsystem(UDEV_SUBSYSTEM)?;
 
-                // Start listening for new devices.
-                let mut socket = monitor.listen()?;
-                let mut fds = vec![
-                    ::libc::pollfd {
-                        fd: socket.as_raw_fd(),
-                        events: POLLIN,
-                        revents: 0,
-                    },
-                ];
+            // Start listening for new devices.
+            let mut socket = monitor.listen()?;
+            let mut fds = vec![
+                ::libc::pollfd {
+                    fd: socket.as_raw_fd(),
+                    events: POLLIN,
+                    revents: 0,
+                },
+            ];
 
-                // Loop until we're stopped by the controlling thread, or fail.
-                while alive() {
-                    // Wait for new events, break on failure.
-                    poll(&mut fds)?;
+            // Loop until we're stopped by the controlling thread, or fail.
+            while alive() {
+                // Wait for new events, break on failure.
+                poll(&mut fds)?;
 
-                    // Send the event over.
-                    let udev_event = socket.receive_event();
-                    if let Some(event) = udev_event.and_then(Event::from_udev) {
-                        tx.send(event).map_err(to_io_err)?;
-                    }
+                // Send the event over.
+                let udev_event = socket.receive_event();
+                if let Some(event) = udev_event.and_then(Event::from_udev) {
+                    tx.send(event).map_err(to_io_err)?;
                 }
+            }
 
-                Ok(())
-            },
-            0, /* no timeout */
-        )?;
+            Ok(())
+        })?;
 
         Ok(Self { rx, thread })
     }
 
     pub fn events<'a>(&'a self) -> TryIter<'a, Event> {
         self.rx.try_iter()
     }
 
--- a/dom/webauthn/u2f-hid-rs/src/macos/iokit.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/iokit.rs
@@ -45,16 +45,17 @@ pub struct __IOHIDManager {
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
 pub struct IOHIDDeviceRef(*const c_void);
 
 unsafe impl Send for IOHIDDeviceRef {}
 unsafe impl Sync for IOHIDDeviceRef {}
 
+#[link(name = "IOKit", kind = "framework")]
 extern "C" {
     // IOHIDManager
     pub fn IOHIDManagerCreate(
         allocator: CFAllocatorRef,
         options: IOHIDManagerOptions,
     ) -> IOHIDManagerRef;
     pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef);
     pub fn IOHIDManagerRegisterDeviceMatchingCallback(
--- a/dom/webauthn/u2f-hid-rs/src/macos/mod.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/mod.rs
@@ -40,17 +40,17 @@ impl PlatformManager {
         application: Vec<u8>,
         callback: OnceCallback<Vec<u8>>,
     ) {
         // Abort any prior register/sign calls.
         self.cancel();
 
         let cbc = callback.clone();
 
-        let thread = RunLoop::new(
+        let thread = RunLoop::new_with_timeout(
             move |alive| {
                 let mut devices = DeviceMap::new();
                 let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
 
                 'top: while alive() && monitor.alive() {
                     for event in monitor.events() {
                         devices.process_event(event);
                     }
@@ -93,17 +93,17 @@ impl PlatformManager {
         key_handles: Vec<Vec<u8>>,
         callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
     ) {
         // Abort any prior register/sign calls.
         self.cancel();
 
         let cbc = callback.clone();
 
-        let thread = RunLoop::new(
+        let thread = RunLoop::new_with_timeout(
             move |alive| {
                 let mut devices = DeviceMap::new();
                 let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
 
                 'top: while alive() && monitor.alive() {
                     for event in monitor.events() {
                         devices.process_event(event);
                     }
--- a/dom/webauthn/u2f-hid-rs/src/macos/monitor.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/monitor.rs
@@ -26,63 +26,60 @@ pub struct Monitor {
     // Handle to the thread loop.
     thread: RunLoop,
 }
 
 impl Monitor {
     pub fn new() -> io::Result<Self> {
         let (tx, rx) = channel();
 
-        let thread = RunLoop::new(
-            move |alive| -> io::Result<()> {
-                let tx_box = Box::new(tx);
-                let tx_ptr = Box::into_raw(tx_box) as *mut libc::c_void;
+        let thread = RunLoop::new(move |alive| -> io::Result<()> {
+            let tx_box = Box::new(tx);
+            let tx_ptr = Box::into_raw(tx_box) as *mut libc::c_void;
+
+            // This will keep `tx` alive only for the scope.
+            let _tx = unsafe { Box::from_raw(tx_ptr as *mut Sender<Event>) };
 
-                // This will keep `tx` alive only for the scope.
-                let _tx = unsafe { Box::from_raw(tx_ptr as *mut Sender<Event>) };
+            // Create and initialize a scoped HID manager.
+            let manager = IOHIDManager::new()?;
 
-                // Create and initialize a scoped HID manager.
-                let manager = IOHIDManager::new()?;
-
-                // Match only U2F devices.
-                let dict = IOHIDDeviceMatcher::new();
-                unsafe { IOHIDManagerSetDeviceMatching(manager.get(), dict.get()) };
+            // Match only U2F devices.
+            let dict = IOHIDDeviceMatcher::new();
+            unsafe { IOHIDManagerSetDeviceMatching(manager.get(), dict.get()) };
 
-                // Register callbacks.
-                unsafe {
-                    IOHIDManagerRegisterDeviceMatchingCallback(
-                        manager.get(),
-                        Monitor::device_add_cb,
-                        tx_ptr,
-                    );
-                    IOHIDManagerRegisterDeviceRemovalCallback(
-                        manager.get(),
-                        Monitor::device_remove_cb,
-                        tx_ptr,
-                    );
-                }
+            // Register callbacks.
+            unsafe {
+                IOHIDManagerRegisterDeviceMatchingCallback(
+                    manager.get(),
+                    Monitor::device_add_cb,
+                    tx_ptr,
+                );
+                IOHIDManagerRegisterDeviceRemovalCallback(
+                    manager.get(),
+                    Monitor::device_remove_cb,
+                    tx_ptr,
+                );
+            }
 
-                // Run the Event Loop. CFRunLoopRunInMode() will dispatch HID
-                // input reports into the various callbacks
-                while alive() {
-                    trace!("OSX Runloop running, handle={:?}", thread::current());
+            // Run the Event Loop. CFRunLoopRunInMode() will dispatch HID
+            // input reports into the various callbacks
+            while alive() {
+                trace!("OSX Runloop running, handle={:?}", thread::current());
 
-                    if unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, 0) } ==
-                        kCFRunLoopRunStopped
-                    {
-                        debug!("OSX Runloop device stopped.");
-                        break;
-                    }
+                if unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, 0) } ==
+                    kCFRunLoopRunStopped
+                {
+                    debug!("OSX Runloop device stopped.");
+                    break;
                 }
-                debug!("OSX Runloop completed, handle={:?}", thread::current());
+            }
+            debug!("OSX Runloop completed, handle={:?}", thread::current());
 
-                Ok(())
-            },
-            0, /* no timeout */
-        )?;
+            Ok(())
+        })?;
 
         Ok(Self { rx, thread })
     }
 
     pub fn events(&self) -> TryIter<Event> {
         self.rx.try_iter()
     }
 
--- a/dom/webauthn/u2f-hid-rs/src/manager.rs
+++ b/dom/webauthn/u2f-hid-rs/src/manager.rs
@@ -33,58 +33,55 @@ pub struct U2FManager {
     tx: Sender<QueueAction>,
 }
 
 impl U2FManager {
     pub fn new() -> io::Result<Self> {
         let (tx, rx) = channel();
 
         // Start a new work queue thread.
-        let queue = try!(RunLoop::new(
-            move |alive| {
-                let mut pm = PlatformManager::new();
+        let queue = RunLoop::new(move |alive| {
+            let mut pm = PlatformManager::new();
 
-                while alive() {
-                    match rx.recv_timeout(Duration::from_millis(50)) {
-                        Ok(QueueAction::Register {
-                               timeout,
-                               challenge,
-                               application,
-                               callback,
-                           }) => {
-                            // This must not block, otherwise we can't cancel.
-                            pm.register(timeout, challenge, application, callback);
-                        }
-                        Ok(QueueAction::Sign {
-                               timeout,
-                               challenge,
-                               application,
-                               key_handles,
-                               callback,
-                           }) => {
-                            // This must not block, otherwise we can't cancel.
-                            pm.sign(timeout, challenge, application, key_handles, callback);
-                        }
-                        Ok(QueueAction::Cancel) => {
-                            // Cancelling must block so that we don't start a new
-                            // polling thread before the old one has shut down.
-                            pm.cancel();
-                        }
-                        Err(RecvTimeoutError::Disconnected) => {
-                            break;
-                        }
-                        _ => { /* continue */ }
+            while alive() {
+                match rx.recv_timeout(Duration::from_millis(50)) {
+                    Ok(QueueAction::Register {
+                           timeout,
+                           challenge,
+                           application,
+                           callback,
+                       }) => {
+                        // This must not block, otherwise we can't cancel.
+                        pm.register(timeout, challenge, application, callback);
                     }
+                    Ok(QueueAction::Sign {
+                           timeout,
+                           challenge,
+                           application,
+                           key_handles,
+                           callback,
+                       }) => {
+                        // This must not block, otherwise we can't cancel.
+                        pm.sign(timeout, challenge, application, key_handles, callback);
+                    }
+                    Ok(QueueAction::Cancel) => {
+                        // Cancelling must block so that we don't start a new
+                        // polling thread before the old one has shut down.
+                        pm.cancel();
+                    }
+                    Err(RecvTimeoutError::Disconnected) => {
+                        break;
+                    }
+                    _ => { /* continue */ }
                 }
+            }
 
-                // Cancel any ongoing activity.
-                pm.cancel();
-            },
-            0, /* no timeout */
-        ));
+            // Cancel any ongoing activity.
+            pm.cancel();
+        })?;
 
         Ok(Self {
             queue: queue,
             tx: tx,
         })
     }
 
     pub fn register<F>(
--- a/dom/webauthn/u2f-hid-rs/src/windows/mod.rs
+++ b/dom/webauthn/u2f-hid-rs/src/windows/mod.rs
@@ -35,17 +35,17 @@ impl PlatformManager {
         application: Vec<u8>,
         callback: OnceCallback<Vec<u8>>,
     ) {
         // Abort any prior register/sign calls.
         self.cancel();
 
         let cbc = callback.clone();
 
-        let thread = RunLoop::new(
+        let thread = RunLoop::new_with_timeout(
             move |alive| {
                 let mut devices = DeviceMap::new();
                 let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
 
                 while alive() && monitor.alive() {
                     // Add/remove devices.
                     for event in monitor.events() {
                         devices.process_event(event);
@@ -81,17 +81,17 @@ impl PlatformManager {
         key_handles: Vec<Vec<u8>>,
         callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
     ) {
         // Abort any prior register/sign calls.
         self.cancel();
 
         let cbc = callback.clone();
 
-        let thread = RunLoop::new(
+        let thread = RunLoop::new_with_timeout(
             move |alive| {
                 let mut devices = DeviceMap::new();
                 let monitor = try_or!(Monitor::new(), |e| { callback.call(Err(e)); });
 
                 while alive() && monitor.alive() {
                     // Add/remove devices.
                     for event in monitor.events() {
                         devices.process_event(event);
--- a/dom/webauthn/u2f-hid-rs/src/windows/monitor.rs
+++ b/dom/webauthn/u2f-hid-rs/src/windows/monitor.rs
@@ -32,45 +32,42 @@ pub struct Monitor {
     // Handle to the thread loop.
     thread: RunLoop,
 }
 
 impl Monitor {
     pub fn new() -> io::Result<Self> {
         let (tx, rx) = channel();
 
-        let thread = RunLoop::new(
-            move |alive| -> io::Result<()> {
-                let mut stored = HashSet::new();
-
-                while alive() {
-                    let device_info_set = DeviceInfoSet::new()?;
-                    let devices = HashSet::from_iter(device_info_set.devices());
+        let thread = RunLoop::new(move |alive| -> io::Result<()> {
+            let mut stored = HashSet::new();
 
-                    // Remove devices that are gone.
-                    for path in stored.difference(&devices) {
-                        tx.send(Event::Remove(path.clone())).map_err(to_io_err)?;
-                    }
+            while alive() {
+                let device_info_set = DeviceInfoSet::new()?;
+                let devices = HashSet::from_iter(device_info_set.devices());
 
-                    // Add devices that were plugged in.
-                    for path in devices.difference(&stored) {
-                        tx.send(Event::Add(path.clone())).map_err(to_io_err)?;
-                    }
-
-                    // Remember the new set.
-                    stored = devices;
-
-                    // Wait a little before looking for devices again.
-                    thread::sleep(Duration::from_millis(100));
+                // Remove devices that are gone.
+                for path in stored.difference(&devices) {
+                    tx.send(Event::Remove(path.clone())).map_err(to_io_err)?;
                 }
 
-                Ok(())
-            },
-            0, /* no timeout */
-        )?;
+                // Add devices that were plugged in.
+                for path in devices.difference(&stored) {
+                    tx.send(Event::Add(path.clone())).map_err(to_io_err)?;
+                }
+
+                // Remember the new set.
+                stored = devices;
+
+                // Wait a little before looking for devices again.
+                thread::sleep(Duration::from_millis(100));
+            }
+
+            Ok(())
+        })?;
 
         Ok(Self {
             rx: rx,
             thread: thread,
         })
     }
 
     pub fn events<'a>(&'a self) -> TryIter<'a, Event> {
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -335,17 +335,18 @@ partial interface Window {
                                                                    optional DOMString name = "",
                                                                    optional DOMString options = "",
                                                                    any... extraArguments);
 
   [
 #ifdef NIGHTLY_BUILD
    ChromeOnly,
 #endif
-   Replaceable, Throws, NeedsCallerType] readonly attribute object? content;
+   NonEnumerable, Replaceable, Throws, NeedsCallerType]
+  readonly attribute object? content;
 
   [Throws, ChromeOnly] any getInterface(IID iid);
 
   /**
    * Same as nsIDOMWindow.windowRoot, useful for event listener targeting.
    */
   [ChromeOnly, Throws]
   readonly attribute WindowRoot? windowRoot;
@@ -369,16 +370,17 @@ partial interface Window {
 partial interface Window {
   [Replaceable, Throws, UseCounter]
   readonly attribute (External or WindowProxy) sidebar;
 };
 #endif
 
 [Func="IsChromeOrXBL"]
 interface ChromeWindow {
+  // The STATE_* constants need to match the corresponding enum in nsGlobalWindow.cpp.
   [Func="nsGlobalWindow::IsPrivilegedChromeWindow"]
   const unsigned short STATE_MAXIMIZED = 1;
   [Func="nsGlobalWindow::IsPrivilegedChromeWindow"]
   const unsigned short STATE_MINIMIZED = 2;
   [Func="nsGlobalWindow::IsPrivilegedChromeWindow"]
   const unsigned short STATE_NORMAL = 3;
   [Func="nsGlobalWindow::IsPrivilegedChromeWindow"]
   const unsigned short STATE_FULLSCREEN = 4;
--- a/dom/workers/FileReaderSync.cpp
+++ b/dom/workers/FileReaderSync.cpp
@@ -227,17 +227,19 @@ FileReaderSync::ReadAsText(Blob& aBlob,
     aRv = multiplexStream->AppendStream(syncStream);
     if (NS_WARN_IF(aRv.Failed())) {
       return;
     }
   }
 
   nsAutoCString charset;
   encoding->Name(charset);
-  aRv = ConvertStream(multiplexStream, charset.get(), aResult);
+
+  nsCOMPtr<nsIInputStream> multiplex(do_QueryInterface(multiplexStream));
+  aRv = ConvertStream(multiplex, charset.get(), aResult);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 }
 
 void
 FileReaderSync::ReadAsDataURL(Blob& aBlob, nsAString& aResult,
                               ErrorResult& aRv)
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-52365
+52366
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -50429,16 +50429,17 @@ van/SM
 vanadium/M
 vandal/SM
 vandalism/M
 vandalize/DSG
 vane/MS
 vanguard/MS
 vanilla/SM
 vanish/JDSG
+vanishingly
 vanity/SM
 vanned
 vanning
 vanquish/ZGDRS
 vanquisher/M
 vantage/SM
 vape/GDS
 vapid/YP
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -48,16 +48,17 @@
 #include "gfxCrashReporterUtils.h"
 #include "gfxFailure.h"
 #include "gfxPlatform.h"
 #include "gfxUtils.h"
 #include "GLBlitHelper.h"
 #include "GLContextEGL.h"
 #include "GLContextProvider.h"
 #include "GLLibraryEGL.h"
+#include "LayersLogging.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/widget/CompositorWidget.h"
 #include "nsDebug.h"
 #include "nsIWidget.h"
 #include "nsThreadUtils.h"
@@ -164,44 +165,44 @@ private:
 
 already_AddRefed<GLContext>
 GLContextEGLFactory::Create(EGLNativeWindowType aWindow,
                             bool aWebRender)
 {
     MOZ_ASSERT(aWindow);
     nsCString discardFailureId;
     if (!sEGLLibrary.EnsureInitialized(false, &discardFailureId)) {
-        MOZ_CRASH("GFX: Failed to load EGL library 3!\n");
+        gfxCriticalNote << "Failed to load EGL library 3!";
         return nullptr;
     }
 
     bool doubleBuffered = true;
 
     EGLConfig config;
     if (!CreateConfig(&config, aWebRender)) {
-        MOZ_CRASH("GFX: Failed to create EGLConfig!\n");
+        gfxCriticalNote << "Failed to create EGLConfig!";
         return nullptr;
     }
 
     EGLSurface surface = mozilla::gl::CreateSurfaceFromNativeWindow(aWindow, config);
 
     if (!surface) {
-        MOZ_CRASH("GFX: Failed to create EGLSurface!\n");
+        gfxCriticalNote << "Failed to create EGLSurface!";
         return nullptr;
     }
 
     CreateContextFlags flags = CreateContextFlags::NONE;
     if (aWebRender) {
         flags |= CreateContextFlags::PREFER_ES3;
     }
     SurfaceCaps caps = SurfaceCaps::Any();
     RefPtr<GLContextEGL> gl = GLContextEGL::CreateGLContext(flags, caps, false, config,
                                                             surface, &discardFailureId);
     if (!gl) {
-        MOZ_CRASH("GFX: Failed to create EGLContext!\n");
+        gfxCriticalNote << "Failed to create EGLContext!";
         mozilla::gl::DestroySurface(surface);
         return nullptr;
     }
 
     gl->MakeCurrent();
     gl->SetIsDoubleBuffered(doubleBuffered);
 
     return gl.forget();
@@ -712,17 +713,17 @@ CreateConfig(EGLConfig* aConfig, bool aE
     }
 }
 
 already_AddRefed<GLContext>
 GLContextProviderEGL::CreateWrappingExisting(void* aContext, void* aSurface)
 {
     nsCString discardFailureId;
     if (!sEGLLibrary.EnsureInitialized(false, &discardFailureId)) {
-        MOZ_CRASH("GFX: Failed to load EGL library 2!\n");
+        MOZ_CRASH("GFX: Failed to load EGL library 2!");
         return nullptr;
     }
 
     if (!aContext || !aSurface)
         return nullptr;
 
     SurfaceCaps caps = SurfaceCaps::Any();
     EGLConfig config = EGL_NO_CONFIG;
@@ -753,52 +754,52 @@ GLContextProviderEGL::CreateForWindow(ns
                                        aWebRender);
 }
 
 #if defined(MOZ_WIDGET_ANDROID)
 EGLSurface
 GLContextEGL::CreateCompatibleSurface(void* aWindow)
 {
     if (mConfig == EGL_NO_CONFIG) {
-        MOZ_CRASH("GFX: Failed with invalid EGLConfig 2!\n");
+        MOZ_CRASH("GFX: Failed with invalid EGLConfig 2!");
     }
 
     return GLContextProviderEGL::CreateEGLSurface(aWindow, mConfig);
 }
 
 /* static */ EGLSurface
 GLContextProviderEGL::CreateEGLSurface(void* aWindow, EGLConfig aConfig)
 {
     // NOTE: aWindow is an ANativeWindow
     nsCString discardFailureId;
     if (!sEGLLibrary.EnsureInitialized(false, &discardFailureId)) {
-        MOZ_CRASH("GFX: Failed to load EGL library 4!\n");
+        MOZ_CRASH("GFX: Failed to load EGL library 4!");
     }
     EGLConfig config = aConfig;
     if (!config && !CreateConfig(&config, /* aEnableDepthBuffer */ false)) {
-        MOZ_CRASH("GFX: Failed to create EGLConfig 2!\n");
+        MOZ_CRASH("GFX: Failed to create EGLConfig 2!");
     }
 
     MOZ_ASSERT(aWindow);
 
     EGLSurface surface = sEGLLibrary.fCreateWindowSurface(EGL_DISPLAY(), config, aWindow,
                                                           0);
     if (surface == EGL_NO_SURFACE) {
-        MOZ_CRASH("GFX: Failed to create EGLSurface 2!\n");
+        MOZ_CRASH("GFX: Failed to create EGLSurface 2!");
     }
 
     return surface;
 }
 
 /* static */ void
 GLContextProviderEGL::DestroyEGLSurface(EGLSurface surface)
 {
     nsCString discardFailureId;
     if (!sEGLLibrary.EnsureInitialized(false, &discardFailureId)) {
-        MOZ_CRASH("GFX: Failed to load EGL library 5!\n");
+        MOZ_CRASH("GFX: Failed to load EGL library 5!");
     }
 
     sEGLLibrary.fDestroySurface(EGL_DISPLAY(), surface);
 }
 #endif // defined(ANDROID)
 
 static void
 FillContextAttribs(bool alpha, bool depth, bool stencil, bool bpp16,
--- a/gfx/layers/SourceSurfaceVolatileData.h
+++ b/gfx/layers/SourceSurfaceVolatileData.h
@@ -27,16 +27,17 @@ class SourceSurfaceVolatileData : public
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceVolatileData, override)
 
   SourceSurfaceVolatileData()
     : mMutex("SourceSurfaceVolatileData")
     , mStride(0)
     , mMapCount(0)
     , mFormat(SurfaceFormat::UNKNOWN)
+    , mWasPurged(false)
   {
   }
 
   bool Init(const IntSize &aSize,
             int32_t aStride,
             SurfaceFormat aFormat);
 
   uint8_t *GetData() override { return mVBufPtr; }
@@ -61,32 +62,37 @@ public:
   // we want to allow it for SourceSurfaceVolatileData since it should
   // always be fine (for reading at least).
   //
   // This is the same as the base class implementation except using
   // mMapCount instead of mIsMapped since that breaks for multithread.
   bool Map(MapType, MappedSurface *aMappedSurface) override
   {
     MutexAutoLock lock(mMutex);
+    if (mWasPurged) {
+      return false;
+    }
     if (mMapCount == 0) {
       mVBufPtr = mVBuf;
     }
     if (mVBufPtr.WasBufferPurged()) {
+      mWasPurged = true;
       return false;
     }
     aMappedSurface->mData = mVBufPtr;
     aMappedSurface->mStride = mStride;
     ++mMapCount;
     return true;
   }
 
   void Unmap() override
   {
     MutexAutoLock lock(mMutex);
     MOZ_ASSERT(mMapCount > 0);
+    MOZ_ASSERT(!mWasPurged);
     if (--mMapCount == 0) {
       mVBufPtr = nullptr;
     }
   }
 
 private:
   ~SourceSurfaceVolatileData() override
   {
@@ -95,14 +101,15 @@ private:
 
   Mutex mMutex;
   int32_t mStride;
   int32_t mMapCount;
   IntSize mSize;
   RefPtr<VolatileBuffer> mVBuf;
   VolatileBufferPtr<uint8_t> mVBufPtr;
   SurfaceFormat mFormat;
+  bool mWasPurged;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* MOZILLA_GFX_SOURCESURFACEVOLATILEDATA_H_ */
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -97,16 +97,17 @@ WebRenderLayerManager::DoDestroy(bool aI
   if (IsDestroyed()) {
     return;
   }
 
   LayerManager::Destroy();
 
   if (WrBridge()) {
     // Just clear ImageKeys, they are deleted during WebRenderAPI destruction.
+    mImageKeysToDeleteLater.Clear();
     mImageKeysToDelete.Clear();
     // CompositorAnimations are cleared by WebRenderBridgeParent.
     mDiscardedCompositorAnimationsIds.Clear();
     WrBridge()->Destroy(aIsSync);
   }
 
   mLastCanvasDatas.Clear();
 
@@ -372,17 +373,16 @@ WebRenderLayerManager::CreateWebRenderCo
 }
 
 void
 WebRenderLayerManager::EndTransactionWithoutLayer(nsDisplayList* aDisplayList,
                                                   nsDisplayListBuilder* aDisplayListBuilder)
 {
   MOZ_ASSERT(aDisplayList && aDisplayListBuilder);
   mEndTransactionWithoutLayers = true;
-  DiscardImages();
   WrBridge()->RemoveExpiredFontKeys();
   EndTransactionInternal(nullptr,
                          nullptr,
                          EndTransactionFlags::END_DEFAULT,
                          aDisplayList,
                          aDisplayListBuilder);
 }
 
@@ -682,17 +682,16 @@ WebRenderLayerManager::PushItemAsImage(n
 }
 
 void
 WebRenderLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback,
                                       void* aCallbackData,
                                       EndTransactionFlags aFlags)
 {
   mEndTransactionWithoutLayers = false;
-  DiscardImages();
   WrBridge()->RemoveExpiredFontKeys();
   EndTransactionInternal(aCallback, aCallbackData, aFlags);
 }
 
 bool
 WebRenderLayerManager::EndTransactionInternal(DrawPaintedLayerCallback aCallback,
                                               void* aCallbackData,
                                               EndTransactionFlags aFlags,
@@ -813,16 +812,22 @@ WebRenderLayerManager::EndTransactionInt
     }
     mScrollData.SetPaintSequenceNumber(mPaintSequenceNumber);
   }
 
   bool sync = mTarget != nullptr;
   mLatestTransactionId = mTransactionIdAllocator->GetTransactionId(/*aThrottle*/ true);
   TimeStamp transactionStart = mTransactionIdAllocator->GetTransactionStart();
 
+  for (const auto& key : mImageKeysToDelete) {
+    resourceUpdates.DeleteImage(key);
+  }
+  mImageKeysToDelete.Clear();
+  mImageKeysToDelete.SwapElements(mImageKeysToDeleteLater);
+
   // Skip the synchronization for buffer since we also skip the painting during
   // device-reset status.
   if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
     if (WrBridge()->GetSyncObject() &&
         WrBridge()->GetSyncObject()->IsSyncObjectValid()) {
       WrBridge()->GetSyncObject()->Synchronize();
     }
   }
@@ -918,26 +923,30 @@ WebRenderLayerManager::MakeSnapshotIfReq
   dt->FillRect(dst, pattern);
 
   mTarget = nullptr;
 }
 
 void
 WebRenderLayerManager::AddImageKeyForDiscard(wr::ImageKey key)
 {
-  mImageKeysToDelete.AppendElement(key);
+  mImageKeysToDeleteLater.AppendElement(key);
 }
 
 void
 WebRenderLayerManager::DiscardImages()
 {
   wr::IpcResourceUpdateQueue resources(WrBridge()->GetShmemAllocator());
+  for (const auto& key : mImageKeysToDeleteLater) {
+    resources.DeleteImage(key);
+  }
   for (const auto& key : mImageKeysToDelete) {
     resources.DeleteImage(key);
   }
+  mImageKeysToDeleteLater.Clear();
   mImageKeysToDelete.Clear();
   WrBridge()->UpdateResources(resources);
 }
 
 void
 WebRenderLayerManager::AddCompositorAnimationsIdForDiscard(uint64_t aId)
 {
   mDiscardedCompositorAnimationsIds.AppendElement(aId);
@@ -954,16 +963,17 @@ WebRenderLayerManager::DiscardCompositor
 }
 
 void
 WebRenderLayerManager::DiscardLocalImages()
 {
   // Removes images but doesn't tell the parent side about them
   // This is useful in empty / failed transactions where we created
   // image keys but didn't tell the parent about them yet.
+  mImageKeysToDeleteLater.Clear();
   mImageKeysToDelete.Clear();
 }
 
 void
 WebRenderLayerManager::Mutated(Layer* aLayer)
 {
   LayerManager::Mutated(aLayer);
   AddMutatedLayer(aLayer);
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -245,16 +245,21 @@ private:
                               void* aCallbackData,
                               EndTransactionFlags aFlags,
                               nsDisplayList* aDisplayList = nullptr,
                               nsDisplayListBuilder* aDisplayListBuilder = nullptr);
 
 private:
   nsIWidget* MOZ_NON_OWNING_REF mWidget;
   nsTArray<wr::ImageKey> mImageKeysToDelete;
+  // TODO - This is needed because we have some code that creates image keys
+  // and enqueues them for deletion right away which is bad not only because
+  // of poor texture cache usage, but also because images end up deleted before
+  // they are used. This should hopfully be temporary.
+  nsTArray<wr::ImageKey> mImageKeysToDeleteLater;
   nsTArray<uint64_t> mDiscardedCompositorAnimationsIds;
 
   /* PaintedLayer callbacks; valid at the end of a transaciton,
    * while rendering */
   DrawPaintedLayerCallback mPaintedLayerCallback;
   void *mPaintedLayerCallbackData;
 
   RefPtr<WebRenderBridgeChild> mWrChild;
--- a/image/SurfaceCache.cpp
+++ b/image/SurfaceCache.cpp
@@ -609,22 +609,39 @@ public:
     if (idealArea <= currentArea && currentArea < bestMatchArea) {
       return true;
     }
 
     // This surface isn't an improvement over the current best match.
     return false;
   }
 
+  template<typename Function>
   void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
-                             MallocSizeOf                    aMallocSizeOf)
+                             MallocSizeOf                    aMallocSizeOf,
+                             Function&&                      aRemoveCallback)
   {
     CachedSurface::SurfaceMemoryReport report(aCounters, aMallocSizeOf);
-    for (auto iter = ConstIter(); !iter.Done(); iter.Next()) {
+    for (auto iter = mSurfaces.Iter(); !iter.Done(); iter.Next()) {
       NotNull<CachedSurface*> surface = WrapNotNull(iter.UserData());
+
+      // We don't need the drawable surface for ourselves, but adding a surface
+      // to the report will trigger this indirectly. If the surface was
+      // discarded by the OS because it was in volatile memory, we should remove
+      // it from the cache immediately rather than include it in the report.
+      DrawableSurface drawableSurface;
+      if (!surface->IsPlaceholder()) {
+        drawableSurface = surface->GetDrawableSurface();
+        if (!drawableSurface) {
+          aRemoveCallback(surface);
+          iter.Remove();
+          continue;
+        }
+      }
+
       const IntSize& size = surface->GetSurfaceKey().Size();
       bool factor2Size = false;
       if (mFactor2Mode) {
         factor2Size = (size == SuggestedSize(size));
       }
       report.Add(surface, factor2Size);
     }
   }
@@ -1068,16 +1085,18 @@ public:
   {
     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
       return;  // No cached surfaces for this image, so nothing to do.
     }
 
     cache->Prune([this, &aAutoLock](NotNull<CachedSurface*> aSurface) -> void {
       StopTracking(aSurface, /* aIsTracked */ true, aAutoLock);
+      // Individual surfaces must be freed outside the lock.
+      mCachedSurfacesDiscard.AppendElement(aSurface);
     });
   }
 
   void DiscardAll(const StaticMutexAutoLock& aAutoLock)
   {
     // Remove in order of cost because mCosts is an array and the other data
     // structures are all hash tables. Note that locked surfaces are not
     // removed, since they aren't present in mCosts.
@@ -1160,25 +1179,31 @@ public:
 "Count of how many times the surface cache has hit its capacity and been "
 "unable to insert a new surface.");
 
     return NS_OK;
   }
 
   void CollectSizeOfSurfaces(const ImageKey                  aImageKey,
                              nsTArray<SurfaceMemoryCounter>& aCounters,
-                             MallocSizeOf                    aMallocSizeOf)
+                             MallocSizeOf                    aMallocSizeOf,
+                             const StaticMutexAutoLock&      aAutoLock)
   {
     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
       return;  // No surfaces for this image.
     }
 
     // Report all surfaces in the per-image cache.
-    cache->CollectSizeOfSurfaces(aCounters, aMallocSizeOf);
+    cache->CollectSizeOfSurfaces(aCounters, aMallocSizeOf,
+      [this, &aAutoLock](NotNull<CachedSurface*> aSurface) -> void {
+      StopTracking(aSurface, /* aIsTracked */ true, aAutoLock);
+      // Individual surfaces must be freed outside the lock.
+      mCachedSurfacesDiscard.AppendElement(aSurface);
+    });
   }
 
 private:
   already_AddRefed<ImageSurfaceCache> GetImageCache(const ImageKey aImageKey)
   {
     RefPtr<ImageSurfaceCache> imageCache;
     mImageCaches.Get(aImageKey, getter_AddRefs(imageCache));
     return imageCache.forget();
@@ -1539,19 +1564,23 @@ SurfaceCache::RemoveImage(const ImageKey
       discard = sInstance->RemoveImage(aImageKey, lock);
     }
   }
 }
 
 /* static */ void
 SurfaceCache::PruneImage(const ImageKey aImageKey)
 {
-  StaticMutexAutoLock lock(sInstanceMutex);
-  if (sInstance) {
-    sInstance->PruneImage(aImageKey, lock);
+  nsTArray<RefPtr<CachedSurface>> discard;
+  {
+    StaticMutexAutoLock lock(sInstanceMutex);
+    if (sInstance) {
+      sInstance->PruneImage(aImageKey, lock);
+      sInstance->TakeDiscard(discard, lock);
+    }
   }
 }
 
 /* static */ void
 SurfaceCache::DiscardAll()
 {
   nsTArray<RefPtr<CachedSurface>> discard;
   {
@@ -1563,22 +1592,26 @@ SurfaceCache::DiscardAll()
   }
 }
 
 /* static */ void
 SurfaceCache::CollectSizeOfSurfaces(const ImageKey                  aImageKey,
                                     nsTArray<SurfaceMemoryCounter>& aCounters,
                                     MallocSizeOf                    aMallocSizeOf)
 {
-  StaticMutexAutoLock lock(sInstanceMutex);
-  if (!sInstance) {
-    return;
+  nsTArray<RefPtr<CachedSurface>> discard;
+  {
+    StaticMutexAutoLock lock(sInstanceMutex);
+    if (!sInstance) {
+      return;
+    }
+
+    sInstance->CollectSizeOfSurfaces(aImageKey, aCounters, aMallocSizeOf, lock);
+    sInstance->TakeDiscard(discard, lock);
   }
-
-  return sInstance->CollectSizeOfSurfaces(aImageKey, aCounters, aMallocSizeOf);
 }
 
 /* static */ size_t
 SurfaceCache::MaximumCapacity()
 {
   StaticMutexAutoLock lock(sInstanceMutex);
   if (!sInstance) {
     return 0;
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -126,17 +126,17 @@ class CPOWProxyHandler : public BaseProx
     virtual bool hasInstance(JSContext* cx, HandleObject proxy,
                              MutableHandleValue v, bool* bp) const override;
     virtual bool getBuiltinClass(JSContext* cx, HandleObject obj, js::ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject obj,
                          IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const override;
     virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override;
-    virtual void objectMoved(JSObject* proxy, const JSObject* old) const override;
+    virtual size_t objectMoved(JSObject* proxy, JSObject* old) const override;
     virtual bool isCallable(JSObject* obj) const override;
     virtual bool isConstructor(JSObject* obj) const override;
     virtual bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const override;
     virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
                                         MutableHandleObject protop) const override;
 
     static const char family;
     static const CPOWProxyHandler singleton;
@@ -890,20 +890,21 @@ CPOWProxyHandler::finalize(JSFreeOp* fop
     AuxCPOWData* aux = AuxCPOWDataOf(proxy);
 
     OwnerOf(proxy)->drop(proxy);
 
     if (aux)
         delete aux;
 }
 
-void
-CPOWProxyHandler::objectMoved(JSObject* proxy, const JSObject* old) const
+size_t
+CPOWProxyHandler::objectMoved(JSObject* proxy, JSObject* old) const
 {
     OwnerOf(proxy)->updatePointer(proxy, old);
+    return 0;
 }
 
 bool
 CPOWProxyHandler::isCallable(JSObject* proxy) const
 {
     AuxCPOWData* aux = AuxCPOWDataOf(proxy);
     return aux->isCallable;
 }
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -505,18 +505,18 @@ typedef bool
  * marking its native structures.
  */
 typedef void
 (* JSTraceOp)(JSTracer* trc, JSObject* obj);
 
 typedef JSObject*
 (* JSWeakmapKeyDelegateOp)(JSObject* obj);
 
-typedef void
-(* JSObjectMovedOp)(JSObject* obj, const JSObject* old);
+typedef size_t
+(* JSObjectMovedOp)(JSObject* obj, JSObject* old);
 
 /* js::Class operation signatures. */
 
 namespace js {
 
 typedef bool
 (* LookupPropertyOp)(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                      JS::MutableHandleObject objp, JS::MutableHandle<JS::PropertyResult> propp);
@@ -706,25 +706,32 @@ struct JS_STATIC_CLASS ClassExtension
      * that case, the wrapped object is returned by the wrapper's
      * weakmapKeyDelegateOp hook. As long as the wrapper is used as a weakmap
      * key, it will not be collected (and remain in the weakmap) until the
      * wrapped object is collected.
      */
     JSWeakmapKeyDelegateOp weakmapKeyDelegateOp;
 
     /**
-     * Optional hook called when an object is moved by a compacting GC.
+     * Optional hook called when an object is moved by generational or
+     * compacting GC.
      *
      * There may exist weak pointers to an object that are not traced through
      * when the normal trace APIs are used, for example objects in the wrapper
      * cache. This hook allows these pointers to be updated.
      *
      * Note that this hook can be called before JS_NewObject() returns if a GC
      * is triggered during construction of the object. This can happen for
      * global objects for example.
+     *
+     * The function should return the difference between nursery bytes used and
+     * tenured bytes used, which may be nonzero e.g. if some nursery-allocated
+     * data beyond the actual GC thing is moved into malloced memory.
+     *
+     * This is used to compute the nursery promotion rate.
      */
     JSObjectMovedOp objectMovedOp;
 };
 
 #define JS_NULL_CLASS_SPEC  nullptr
 #define JS_NULL_CLASS_EXT   nullptr
 
 struct JS_STATIC_CLASS ObjectOps
--- a/js/public/GCPolicyAPI.h
+++ b/js/public/GCPolicyAPI.h
@@ -89,30 +89,35 @@ struct StructGCPolicy
 
     static void sweep(T* tp) {
         return tp->sweep();
     }
 
     static bool needsSweep(T* tp) {
         return tp->needsSweep();
     }
+
+    static bool isValid(const T& tp) {
+        return true;
+    }
 };
 
 // The default GC policy attempts to defer to methods on the underlying type.
 // Most C++ structures that contain a default constructor, a trace function and
 // a sweep function will work out of the box with Rooted, Handle, GCVector,
 // and GCHash{Set,Map}.
 template <typename T> struct GCPolicy : public StructGCPolicy<T> {};
 
 // This policy ignores any GC interaction, e.g. for non-GC types.
 template <typename T>
 struct IgnoreGCPolicy {
     static T initial() { return T(); }
     static void trace(JSTracer* trc, T* t, const char* name) {}
     static bool needsSweep(T* v) { return false; }
+    static bool isValid(const T& v) { return true; }
 };
 template <> struct GCPolicy<uint32_t> : public IgnoreGCPolicy<uint32_t> {};
 template <> struct GCPolicy<uint64_t> : public IgnoreGCPolicy<uint64_t> {};
 
 template <typename T>
 struct GCPointerPolicy
 {
     static T initial() { return nullptr; }
@@ -120,25 +125,46 @@ struct GCPointerPolicy
         if (*vp)
             js::UnsafeTraceManuallyBarrieredEdge(trc, vp, name);
     }
     static bool needsSweep(T* vp) {
         if (*vp)
             return js::gc::IsAboutToBeFinalizedUnbarriered(vp);
         return false;
     }
+    static bool isValid(T v) {
+        return js::gc::IsCellPointerValid(v);
+    }
 };
 template <> struct GCPolicy<JS::Symbol*> : public GCPointerPolicy<JS::Symbol*> {};
 template <> struct GCPolicy<JSAtom*> : public GCPointerPolicy<JSAtom*> {};
 template <> struct GCPolicy<JSFunction*> : public GCPointerPolicy<JSFunction*> {};
 template <> struct GCPolicy<JSObject*> : public GCPointerPolicy<JSObject*> {};
 template <> struct GCPolicy<JSScript*> : public GCPointerPolicy<JSScript*> {};
 template <> struct GCPolicy<JSString*> : public GCPointerPolicy<JSString*> {};
 
 template <typename T>
+struct NonGCPointerPolicy
+{
+    static T initial() { return nullptr; }
+    static void trace(JSTracer* trc, T* vp, const char* name) {
+        if (*vp)
+            (*vp)->trace(trc);
+    }
+    static bool needsSweep(T* vp) {
+        if (*vp)
+            return (*vp)->needsSweep();
+        return false;
+    }
+    static bool isValid(T v) {
+        return true;
+    }
+};
+
+template <typename T>
 struct GCPolicy<JS::Heap<T>>
 {
     static void trace(JSTracer* trc, JS::Heap<T>* thingp, const char* name) {
         TraceEdge(trc, thingp, name);
     }
     static bool needsSweep(JS::Heap<T>* thingp) {
         return *thingp && js::gc::EdgeNeedsSweep(thingp);
     }
@@ -153,16 +179,21 @@ struct GCPolicy<mozilla::UniquePtr<T, D>
         if (tp->get())
             GCPolicy<T>::trace(trc, tp->get(), name);
     }
     static bool needsSweep(mozilla::UniquePtr<T,D>* tp) {
         if (tp->get())
             return GCPolicy<T>::needsSweep(tp->get());
         return false;
     }
+    static bool isValid(const mozilla::UniquePtr<T,D>& t) {
+        if (t.get())
+            return GCPolicy<T>::isValid(*t.get());
+        return true;
+    }
 };
 
 // GCPolicy<Maybe<T>> forwards tracing/sweeping to GCPolicy<T*> if
 // when the Maybe<T> is full.
 template <typename T>
 struct GCPolicy<mozilla::Maybe<T>>
 {
     static mozilla::Maybe<T> initial() { return mozilla::Maybe<T>(); }
@@ -170,15 +201,20 @@ struct GCPolicy<mozilla::Maybe<T>>
         if (tp->isSome())
             GCPolicy<T>::trace(trc, tp->ptr(), name);
     }
     static bool needsSweep(mozilla::Maybe<T>* tp) {
         if (tp->isSome())
             return GCPolicy<T>::needsSweep(tp->ptr());
         return false;
     }
+    static bool isValid(const mozilla::Maybe<T>& t) {
+        if (t.isSome())
+            return GCPolicy<T>::isValid(t.ref());
+        return true;
+    }
 };
 
 template <> struct GCPolicy<JS::Realm*>;  // see Realm.h
 
 } // namespace JS
 
 #endif // GCPolicyAPI_h
--- a/js/public/GCVariant.h
+++ b/js/public/GCVariant.h
@@ -113,16 +113,29 @@ struct GCPolicy<mozilla::Variant<Ts...>>
     using Impl = detail::GCVariantImplementation<Ts...>;
 
     // Variants do not provide initial(). They do not have a default initial
     // value and one must be provided.
 
     static void trace(JSTracer* trc, mozilla::Variant<Ts...>* v, const char* name) {
         Impl::trace(trc, v, name);
     }
+
+    static bool isValid(const mozilla::Variant<Ts...>& v) {
+        return v.match(IsValidMatcher());
+    }
+
+  private:
+    struct IsValidMatcher
+    {
+        template<typename T>
+        bool match(T& v) {
+            return GCPolicy<T>::isValid(v);
+        };
+    };
 };
 
 } // namespace JS
 
 namespace js {
 
 template <typename Wrapper, typename... Ts>
 class WrappedPtrOperations<mozilla::Variant<Ts...>, Wrapper>
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -48,16 +48,17 @@ const size_t ChunkMarkBitmapOffset = 258
 const size_t ChunkMarkBitmapBits = 31744;
 #else
 const size_t ChunkMarkBitmapOffset = 1032352;
 const size_t ChunkMarkBitmapBits = 129024;
 #endif
 const size_t ChunkRuntimeOffset = ChunkSize - sizeof(void*);
 const size_t ChunkTrailerSize = 2 * sizeof(uintptr_t) + sizeof(uint64_t);
 const size_t ChunkLocationOffset = ChunkSize - ChunkTrailerSize;
+const size_t ChunkStoreBufferOffset = ChunkLocationOffset + sizeof(uint64_t);
 const size_t ArenaZoneOffset = sizeof(size_t);
 const size_t ArenaHeaderSize = sizeof(size_t) + 2 * sizeof(uintptr_t) +
                                sizeof(size_t) + sizeof(uintptr_t);
 
 /*
  * Live objects are marked black or gray. Everything reachable from a JS root is
  * marked black. Objects marked gray are eligible for cycle collection.
  *
@@ -367,31 +368,62 @@ CellIsMarkedGray(const Cell* cell)
 extern JS_PUBLIC_API(bool)
 CellIsMarkedGrayIfKnown(const Cell* cell);
 
 #ifdef DEBUG
 extern JS_PUBLIC_API(bool)
 CellIsNotGray(const Cell* cell);
 #endif
 
+MOZ_ALWAYS_INLINE ChunkLocation
+GetCellLocation(const void* cell)
+{
+    uintptr_t addr = uintptr_t(cell);
+    addr &= ~js::gc::ChunkMask;
+    addr |= js::gc::ChunkLocationOffset;
+    return *reinterpret_cast<ChunkLocation*>(addr);
+}
+
+MOZ_ALWAYS_INLINE bool
+NurseryCellHasStoreBuffer(const void* cell)
+{
+    uintptr_t addr = uintptr_t(cell);
+    addr &= ~js::gc::ChunkMask;
+    addr |= js::gc::ChunkStoreBufferOffset;
+    return *reinterpret_cast<void**>(addr) != nullptr;
+}
+
 } /* namespace detail */
 
 MOZ_ALWAYS_INLINE bool
 IsInsideNursery(const js::gc::Cell* cell)
 {
     if (!cell)
         return false;
-    uintptr_t addr = uintptr_t(cell);
-    addr &= ~js::gc::ChunkMask;
-    addr |= js::gc::ChunkLocationOffset;
-    auto location = *reinterpret_cast<ChunkLocation*>(addr);
+    auto location = detail::GetCellLocation(cell);
     MOZ_ASSERT(location == ChunkLocation::Nursery || location == ChunkLocation::TenuredHeap);
     return location == ChunkLocation::Nursery;
 }
 
+MOZ_ALWAYS_INLINE bool
+IsCellPointerValid(const void* cell)
+{
+    if (!cell)
+        return true;
+    auto addr = uintptr_t(cell);
+    if (addr < ChunkSize || addr % CellAlignBytes != 0)
+        return false;
+    auto location = detail::GetCellLocation(cell);
+    if (location == ChunkLocation::TenuredHeap)
+        return !!detail::GetGCThingZone(addr);
+    if (location == ChunkLocation::Nursery)
+        return detail::NurseryCellHasStoreBuffer(cell);
+    return false;
+}
+
 } /* namespace gc */
 } /* namespace js */
 
 namespace JS {
 
 static MOZ_ALWAYS_INLINE Zone*
 GetTenuredGCThingZone(GCCellPtr thing)
 {
--- a/js/public/Id.h
+++ b/js/public/Id.h
@@ -166,16 +166,19 @@ namespace JS {
 
 template <>
 struct GCPolicy<jsid>
 {
     static jsid initial() { return JSID_VOID; }
     static void trace(JSTracer* trc, jsid* idp, const char* name) {
         js::UnsafeTraceManuallyBarrieredEdge(trc, idp, name);
     }
+    static bool isValid(jsid id) {
+        return !JSID_IS_GCTHING(id) || js::gc::IsCellPointerValid(JSID_TO_GCTHING(id).asCell());
+    }
 };
 
 } // namespace JS
 
 namespace js {
 
 template <>
 struct BarrierMethods<jsid>
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -330,17 +330,17 @@ class JS_FRIEND_API(BaseProxyHandler)
                                  ESClass* cls) const;
     virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const;
     virtual const char* className(JSContext* cx, HandleObject proxy) const;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const;
     virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const;
     virtual void trace(JSTracer* trc, JSObject* proxy) const;
     virtual void finalize(JSFreeOp* fop, JSObject* proxy) const;
-    virtual void objectMoved(JSObject* proxy, const JSObject* old) const;
+    virtual size_t objectMoved(JSObject* proxy, JSObject* old) const;
 
     // Allow proxies, wrappers in particular, to specify callability at runtime.
     // Note: These do not take const JSObject*, but they do in spirit.
     //       We are not prepared to do this, as there's little const correctness
     //       in the external APIs that handle proxies.
     virtual bool isCallable(JSObject* obj) const;
     virtual bool isConstructor(JSObject* obj) const;
 
@@ -679,28 +679,16 @@ inline void assertEnteredPolicy(JSContex
 
 extern JS_FRIEND_API(JSObject*)
 InitProxyClass(JSContext* cx, JS::HandleObject obj);
 
 extern JS_FRIEND_DATA(const js::ClassOps) ProxyClassOps;
 extern JS_FRIEND_DATA(const js::ClassExtension) ProxyClassExtension;
 extern JS_FRIEND_DATA(const js::ObjectOps) ProxyObjectOps;
 
-/*
- * Helper Macros for creating JSClasses that function as proxies.
- *
- * NB: The macro invocation must be surrounded by braces, so as to
- *     allow for potential JSClass extensions.
- */
-#define PROXY_MAKE_EXT(objectMoved)                                     \
-    {                                                                   \
-        js::proxy_WeakmapKeyDelegate,                                   \
-        objectMoved                                                     \
-    }
-
 template <unsigned Flags>
 constexpr unsigned
 CheckProxyFlags()
 {
     // For now assert each Proxy Class has at least 1 reserved slot. This is
     // not a hard requirement, but helps catch Classes that need an explicit
     // JSCLASS_HAS_RESERVED_SLOTS since bug 1360523.
     static_assert(((Flags >> JSCLASS_RESERVED_SLOTS_SHIFT) & JSCLASS_RESERVED_SLOTS_MASK) > 0,
@@ -717,27 +705,24 @@ CheckProxyFlags()
     // always have finalizers, and whether they can be nursery allocated is
     // controlled by the canNurseryAllocate() method on the proxy handler.
     static_assert(!(Flags & JSCLASS_SKIP_NURSERY_FINALIZE),
                   "Proxies must not use JSCLASS_SKIP_NURSERY_FINALIZE; use "
                   "the canNurseryAllocate() proxy handler method instead.");
     return Flags;
 }
 
-#define PROXY_CLASS_WITH_EXT(name, flags, extPtr)                                       \
+#define PROXY_CLASS_DEF(name, flags)                                                    \
     {                                                                                   \
         name,                                                                           \
         js::Class::NON_NATIVE |                                                         \
             JSCLASS_IS_PROXY |                                                          \
             JSCLASS_DELAY_METADATA_BUILDER |                                            \
             js::CheckProxyFlags<flags>(),                                               \
         &js::ProxyClassOps,                                                             \
         JS_NULL_CLASS_SPEC,                                                             \
-        extPtr,                                                                         \
+        &js::ProxyClassExtension,                                                       \
         &js::ProxyObjectOps                                                             \
     }
 
-#define PROXY_CLASS_DEF(name, flags) \
-  PROXY_CLASS_WITH_EXT(name, flags, &js::ProxyClassExtension)
-
 } /* namespace js */
 
 #endif /* js_Proxy_h */
--- a/js/public/Realm.h
+++ b/js/public/Realm.h
@@ -23,19 +23,18 @@ JS_PUBLIC_API(void) TraceRealm(JSTracer*
 JS_PUBLIC_API(bool) RealmNeedsSweep(JS::Realm* realm);
 }
 }
 
 namespace JS {
 
 // Each Realm holds a strong reference to its GlobalObject, and vice versa.
 template <>
-struct GCPolicy<Realm*>
+struct GCPolicy<Realm*> : public NonGCPointerPolicy<Realm*>
 {
-    static Realm* initial() { return nullptr; }
     static void trace(JSTracer* trc, Realm** vp, const char* name) {
         if (*vp)
             ::js::gc::TraceRealm(trc, *vp, name);
     }
     static bool needsSweep(Realm** vp) {
         return *vp && ::js::gc::RealmNeedsSweep(*vp);
     }
 };
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -391,16 +391,17 @@ class TenuredHeap : public js::HeapBase<
         static_assert(sizeof(T) == sizeof(TenuredHeap<T>),
                       "TenuredHeap<T> must be binary compatible with T.");
     }
     explicit TenuredHeap(T p) : bits(0) { setPtr(p); }
     explicit TenuredHeap(const TenuredHeap<T>& p) : bits(0) { setPtr(p.getPtr()); }
 
     void setPtr(T newPtr) {
         MOZ_ASSERT((reinterpret_cast<uintptr_t>(newPtr) & flagsMask) == 0);
+        MOZ_ASSERT(js::gc::IsCellPointerValid(newPtr));
         if (newPtr)
             AssertGCThingMustBeTenured(newPtr);
         bits = (bits & flagsMask) | reinterpret_cast<uintptr_t>(newPtr);
     }
 
     void setFlags(uintptr_t flagsToSet) {
         MOZ_ASSERT((flagsToSet & ~flagsMask) == 0);
         bits |= flagsToSet;
@@ -566,19 +567,21 @@ class MOZ_STACK_CLASS MutableHandle : pu
 
   private:
     // Disallow nullptr for overloading purposes.
     MutableHandle(decltype(nullptr)) = delete;
 
   public:
     void set(const T& v) {
         *ptr = v;
+        MOZ_ASSERT(GCPolicy<T>::isValid(*ptr));
     }
     void set(T&& v) {
         *ptr = mozilla::Move(v);
+        MOZ_ASSERT(GCPolicy<T>::isValid(*ptr));
     }
 
     /*
      * This may be called only if the location of the T is guaranteed
      * to be marked (for some reason other than being a Rooted),
      * e.g., if it is guaranteed to be reachable from an implicit root.
      *
      * Create a MutableHandle from a raw location of a T.
@@ -813,35 +816,38 @@ class MOZ_RAII Rooted : public js::Roote
     {
         registerWithRootLists(rootLists(cx));
     }
 
     template <typename RootingContext, typename S>
     Rooted(const RootingContext& cx, S&& initial)
       : ptr(mozilla::Forward<S>(initial))
     {
+        MOZ_ASSERT(GCPolicy<T>::isValid(ptr));
         registerWithRootLists(rootLists(cx));
     }
 
     ~Rooted() {
         MOZ_ASSERT(*stack == reinterpret_cast<Rooted<void*>*>(this));
         *stack = prev;
     }
 
     Rooted<T>* previous() { return reinterpret_cast<Rooted<T>*>(prev); }
 
     /*
      * This method is public for Rooted so that Codegen.py can use a Rooted
      * interchangeably with a MutableHandleValue.
      */
     void set(const T& value) {
         ptr = value;
+        MOZ_ASSERT(GCPolicy<T>::isValid(ptr));
     }
     void set(T&& value) {
         ptr = mozilla::Move(value);
+        MOZ_ASSERT(GCPolicy<T>::isValid(ptr));
     }
 
     DECLARE_POINTER_CONSTREF_OPS(T);
     DECLARE_POINTER_ASSIGN_OPS(Rooted, T);
     DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr);
     DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr);
 
   private:
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -349,27 +349,27 @@ class MOZ_NON_PARAM alignas(8) Value
     }
 
     double& getDoubleRef() {
         MOZ_ASSERT(isDouble());
         return data.asDouble;
     }
 
     void setString(JSString* str) {
-        MOZ_ASSERT(uintptr_t(str) > 0x1000);
+        MOZ_ASSERT(js::gc::IsCellPointerValid(str));
         data.asBits = bitsFromTagAndPayload(JSVAL_TAG_STRING, PayloadType(str));
     }
 
     void setSymbol(JS::Symbol* sym) {
-        MOZ_ASSERT(uintptr_t(sym) > 0x1000);
+        MOZ_ASSERT(js::gc::IsCellPointerValid(sym));
         data.asBits = bitsFromTagAndPayload(JSVAL_TAG_SYMBOL, PayloadType(sym));
     }
 
     void setObject(JSObject& obj) {
-        MOZ_ASSERT(uintptr_t(&obj) >= 0x1000);
+        MOZ_ASSERT(js::gc::IsCellPointerValid(&obj));
 #if defined(JS_PUNBOX64)
         // VisualStudio cannot contain parenthesized C++ style cast and shift
         // inside decltype in template parameter:
         //   AssertionConditionType<decltype((uintptr_t(x) >> 1))>
         // It throws syntax error.
         MOZ_ASSERT((((uintptr_t)&obj) >> JSVAL_TAG_SHIFT) == 0);
 #endif
         setObjectNoCheck(&obj);
@@ -751,17 +751,17 @@ class MOZ_NON_PARAM alignas(8) Value
     void setPrivateGCThing(js::gc::Cell* cell) {
         MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::String,
                    "Private GC thing Values must not be strings. Make a StringValue instead.");
         MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::Symbol,
                    "Private GC thing Values must not be symbols. Make a SymbolValue instead.");
         MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::Object,
                    "Private GC thing Values must not be objects. Make an ObjectValue instead.");
 
-        MOZ_ASSERT(uintptr_t(cell) > 0x1000);
+        MOZ_ASSERT(js::gc::IsCellPointerValid(cell));
 #if defined(JS_PUNBOX64)
         // VisualStudio cannot contain parenthesized C++ style cast and shift
         // inside decltype in template parameter:
         //   AssertionConditionType<decltype((uintptr_t(x) >> 1))>
         // It throws syntax error.
         MOZ_ASSERT((((uintptr_t)cell) >> JSVAL_TAG_SHIFT) == 0);
 #endif
         data.asBits = bitsFromTagAndPayload(JSVAL_TAG_PRIVATE_GCTHING, PayloadType(cell));
@@ -1254,16 +1254,19 @@ struct GCPolicy<JS::Value>
 {
     static Value initial() { return UndefinedValue(); }
     static void trace(JSTracer* trc, Value* v, const char* name) {
         js::UnsafeTraceManuallyBarrieredEdge(trc, v, name);
     }
     static bool isTenured(const Value& thing) {
         return !thing.isGCThing() || !IsInsideNursery(thing.toGCThing());
     }
+    static bool isValid(const Value& value) {
+        return !value.isGCThing() || js::gc::IsCellPointerValid(value.toGCThing());
+    }
 };
 
 } // namespace JS
 
 namespace js {
 
 template <>
 struct BarrierMethods<JS::Value>
--- a/js/rust/src/glue.rs
+++ b/js/rust/src/glue.rs
@@ -128,17 +128,18 @@ pub struct ProxyTraps {
                                                                  hint: JSType,
                                                                  vp: JS::MutableHandleValue)
                                                                  -> bool>,
     pub trace:
         ::std::option::Option<unsafe extern "C" fn(trc: *mut JSTracer, proxy: *mut JSObject)>,
     pub finalize:
         ::std::option::Option<unsafe extern "C" fn(fop: *mut JSFreeOp, proxy: *mut JSObject)>,
     pub objectMoved:
-        ::std::option::Option<unsafe extern "C" fn(proxy: *mut JSObject, old: *const JSObject)>,
+        ::std::option::Option<unsafe extern "C" fn(proxy: *mut JSObject,
+                                                   old: *mut JSObject) -> usize>,
     pub isCallable: ::std::option::Option<unsafe extern "C" fn(obj: *mut JSObject) -> bool>,
     pub isConstructor: ::std::option::Option<unsafe extern "C" fn(obj: *mut JSObject) -> bool>,
 }
 impl ::std::default::Default for ProxyTraps {
     fn default() -> ProxyTraps {
         unsafe { ::std::mem::zeroed() }
     }
 }
--- a/js/rust/src/jsglue.cpp
+++ b/js/rust/src/jsglue.cpp
@@ -80,17 +80,17 @@ struct ProxyTraps {
     JSString* (*fun_toString)(JSContext *cx, JS::HandleObject proxy,
                               bool isToString);
     //bool (*regexp_toShared)(JSContext *cx, JS::HandleObject proxy, RegExpGuard *g);
     bool (*boxedValue_unbox)(JSContext *cx, JS::HandleObject proxy,
                              JS::MutableHandleValue vp);
     bool (*defaultValue)(JSContext *cx, JS::HandleObject obj, JSType hint, JS::MutableHandleValue vp);
     void (*trace)(JSTracer *trc, JSObject *proxy);
     void (*finalize)(JSFreeOp *fop, JSObject *proxy);
-    void (*objectMoved)(JSObject *proxy, const JSObject *old);
+    size_t (*objectMoved)(JSObject *proxy, JSObject *old);
 
     bool (*isCallable)(JSObject *obj);
     bool (*isConstructor)(JSObject *obj);
 
     // watch
     // unwatch
     // getElements
 
@@ -221,22 +221,21 @@ static int HandlerFamily;
                                                                                 \
     virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override        \
     {                                                                           \
         mTraps.finalize                                                         \
         ? mTraps.finalize(fop, proxy)                                           \
         : _base::finalize(fop, proxy);                                          \
     }                                                                           \
                                                                                 \
-    virtual void objectMoved(JSObject* proxy,                                   \
-                             const JSObject *old) const override                \
+    virtual size_t objectMoved(JSObject* proxy, JSObject *old) const override   \
     {                                                                           \
-        mTraps.objectMoved                                                      \
-        ? mTraps.objectMoved(proxy, old)                                        \
-        : _base::objectMoved(proxy, old);                                       \
+        return mTraps.objectMoved                                               \
+               ? mTraps.objectMoved(proxy, old)                                 \
+               : _base::objectMoved(proxy, old);                                \
     }                                                                           \
                                                                                 \
     virtual bool isCallable(JSObject* obj) const override                       \
     {                                                                           \
         return mTraps.isCallable                                                \
                ? mTraps.isCallable(obj)                                         \
                : _base::isCallable(obj);                                        \
     }                                                                           \
@@ -563,29 +562,19 @@ WrapperNew(JSContext* aCx, JS::HandleObj
     js::WrapperOptions options;
     if (aClass) {
         options.setClass(js::Valueify(aClass));
     }
     options.setSingleton(aSingleton);
     return js::Wrapper::New(aCx, aObj, (const js::Wrapper*)aHandler, options);
 }
 
-void WindowProxyObjectMoved(JSObject*, const JSObject*)
-{
-    abort();
-}
-
-static const js::ClassExtension WindowProxyClassExtension = PROXY_MAKE_EXT(
-    WindowProxyObjectMoved
-);
-
-const js::Class WindowProxyClass = PROXY_CLASS_WITH_EXT(
+const js::Class WindowProxyClass = PROXY_CLASS_DEF(
     "Proxy",
-    JSCLASS_HAS_RESERVED_SLOTS(1), /* additional class flags */
-    &WindowProxyClassExtension);
+    JSCLASS_HAS_RESERVED_SLOTS(1)); /* additional class flags */
 
 const js::Class*
 GetWindowProxyClass()
 {
     return &WindowProxyClass;
 }
 
 JS::Value
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -270,41 +270,42 @@ MapIteratorObject::finalize(FreeOp* fop,
     MOZ_ASSERT(!IsInsideNursery(obj));
 
     auto range = MapIteratorObjectRange(&obj->as<NativeObject>());
     MOZ_ASSERT(!obj->zone()->group()->nursery().isInside(range));
 
     fop->delete_(range);
 }
 
-void
-MapIteratorObject::objectMoved(JSObject* obj, const JSObject* old)
+size_t
+MapIteratorObject::objectMoved(JSObject* obj, JSObject* old)
 {
     if (!IsInsideNursery(old))
-        return;
+        return 0;
 
     MapIteratorObject* iter = &obj->as<MapIteratorObject>();
     ValueMap::Range* range = MapIteratorObjectRange(iter);
     if (!range)
-        return;
+        return 0;
 
     Nursery& nursery = iter->zone()->group()->nursery();
     if (!nursery.isInside(range)) {
         nursery.removeMallocedBuffer(range);
-        return;
+        return 0;
     }
 
     AutoEnterOOMUnsafeRegion oomUnsafe;
     auto newRange = iter->zone()->pod_malloc<ValueMap::Range>();
     if (!newRange)
         oomUnsafe.crash("MapIteratorObject failed to allocate Range data while tenuring.");
 
     new (newRange) ValueMap::Range(*range);
     range->~Range();
     iter->setReservedSlot(MapIteratorObject::RangeSlot, PrivateValue(newRange));
+    return sizeof(ValueMap::Range);
 }
 
 template <typename Range>
 static void
 DestroyRange(JSObject* iterator, Range* range)
 {
     range->~Range();
     if (!IsInsideNursery(iterator))
@@ -1119,41 +1120,42 @@ SetIteratorObject::finalize(FreeOp* fop,
     MOZ_ASSERT(!IsInsideNursery(obj));
 
     auto range = SetIteratorObjectRange(&obj->as<NativeObject>());
     MOZ_ASSERT(!obj->zone()->group()->nursery().isInside(range));
 
     fop->delete_(range);
 }
 
-void
-SetIteratorObject::objectMoved(JSObject* obj, const JSObject* old)
+size_t
+SetIteratorObject::objectMoved(JSObject* obj, JSObject* old)
 {
     if (!IsInsideNursery(old))
-        return;
+        return 0;
 
     SetIteratorObject* iter = &obj->as<SetIteratorObject>();
     ValueSet::Range* range = SetIteratorObjectRange(iter);
     if (!range)
-        return;
+        return 0;
 
     Nursery& nursery = iter->zone()->group()->nursery();
     if (!nursery.isInside(range)) {
         nursery.removeMallocedBuffer(range);
-        return;
+        return 0;
     }
 
     AutoEnterOOMUnsafeRegion oomUnsafe;
     auto newRange = iter->zone()->pod_malloc<ValueSet::Range>();
     if (!newRange)
         oomUnsafe.crash("SetIteratorObject failed to allocate Range data while tenuring.");
 
     new (newRange) ValueSet::Range(*range);
     range->~Range();
     iter->setReservedSlot(SetIteratorObject::RangeSlot, PrivateValue(newRange));
+    return sizeof(ValueSet::Range);
 }
 
 bool
 SetIteratorObject::next(Handle<SetIteratorObject*> setIterator, HandleArrayObject resultObj,
                         JSContext* cx)
 {
     // Check invariants for inlined _GetNextSetEntryForIterator.
 
--- a/js/src/builtin/MapObject.h
+++ b/js/src/builtin/MapObject.h
@@ -185,17 +185,17 @@ class MapIteratorObject : public NativeO
                   "RangeSlot must match self-hosting define for range or index slot.");
     static_assert(KindSlot == ITERATOR_SLOT_ITEM_KIND,
                   "KindSlot must match self-hosting define for item kind slot.");
 
     static const JSFunctionSpec methods[];
     static MapIteratorObject* create(JSContext* cx, HandleObject mapobj, ValueMap* data,
                                      MapObject::IteratorKind kind);
     static void finalize(FreeOp* fop, JSObject* obj);
-    static void objectMoved(JSObject* obj, const JSObject* old);
+    static size_t objectMoved(JSObject* obj, JSObject* old);
 
     static MOZ_MUST_USE bool next(Handle<MapIteratorObject*> mapIterator,
                                   HandleArrayObject resultPairObj, JSContext* cx);
 
     static JSObject* createResultPair(JSContext* cx);
 
   private:
     inline MapObject::IteratorKind kind() const;
@@ -288,17 +288,17 @@ class SetIteratorObject : public NativeO
                   "RangeSlot must match self-hosting define for range or index slot.");
     static_assert(KindSlot == ITERATOR_SLOT_ITEM_KIND,
                   "KindSlot must match self-hosting define for item kind slot.");
 
     static const JSFunctionSpec methods[];
     static SetIteratorObject* create(JSContext* cx, HandleObject setobj, ValueSet* data,
                                      SetObject::IteratorKind kind);
     static void finalize(FreeOp* fop, JSObject* obj);
-    static void objectMoved(JSObject* obj, const JSObject* old);
+    static size_t objectMoved(JSObject* obj, JSObject* old);
 
     static MOZ_MUST_USE bool next(Handle<SetIteratorObject*> setIterator,
                                   HandleArrayObject resultObj, JSContext* cx);
 
     static JSObject* createResult(JSContext* cx);
 
   private:
     inline SetObject::IteratorKind kind() const;
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -1568,17 +1568,17 @@ js::RegExpPrototypeOptimizable(JSContext
 
     args.rval().setBoolean(RegExpPrototypeOptimizableRaw(cx, &args[0].toObject()));
     return true;
 }
 
 bool
 js::RegExpPrototypeOptimizableRaw(JSContext* cx, JSObject* proto)
 {
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
     AutoAssertNoPendingException aanpe(cx);
     if (!proto->isNative())
         return false;
 
     NativeObject* nproto = static_cast<NativeObject*>(proto);
 
     Shape* shape = cx->compartment()->regExps.getOptimizableRegExpPrototypeShape();
     if (shape == nproto->lastProperty())
@@ -1661,17 +1661,17 @@ js::RegExpInstanceOptimizable(JSContext*
     args.rval().setBoolean(RegExpInstanceOptimizableRaw(cx, &args[0].toObject(),
                                                         &args[1].toObject()));
     return true;
 }
 
 bool
 js::RegExpInstanceOptimizableRaw(JSContext* cx, JSObject* obj, JSObject* proto)
 {
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
     AutoAssertNoPendingException aanpe(cx);
 
     RegExpObject* rx = &obj->as<RegExpObject>();
 
     Shape* shape = cx->compartment()->regExps.getOptimizableRegExpInstanceShape();
     if (shape == rx->lastProperty())
         return true;
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2353,17 +2353,17 @@ SetIonCheckGraphCoherency(JSContext* cx,
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     jit::JitOptions.checkGraphConsistency = ToBoolean(args.get(0));
     args.rval().setUndefined();
     return true;
 }
 
 class CloneBufferObject : public NativeObject {
-    static const JSPropertySpec props_[2];
+    static const JSPropertySpec props_[3];
     static const size_t DATA_SLOT   = 0;
     static const size_t LENGTH_SLOT = 1;
     static const size_t NUM_SLOTS   = 2;
 
   public:
     static const Class class_;
 
     static CloneBufferObject* Create(JSContext* cx) {
@@ -2408,46 +2408,54 @@ class CloneBufferObject : public NativeO
             JSAutoStructuredCloneBuffer clonebuf(JS::StructuredCloneScope::SameProcessSameThread, nullptr, nullptr);
             clonebuf.adopt(Move(*data()));
         }
         setReservedSlot(DATA_SLOT, PrivateValue(nullptr));
     }
 
     static bool
     setCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
-        if (args.length() != 1) {
-            JS_ReportErrorASCII(cx, "clonebuffer setter requires a single string argument");
-            return false;
-        }
-        if (!args[0].isString()) {
-            JS_ReportErrorASCII(cx, "clonebuffer value must be a string");
-            return false;
-        }
-
         if (fuzzingSafe) {
             // A manually-created clonebuffer could easily trigger a crash
             args.rval().setUndefined();
             return true;
         }
 
         Rooted<CloneBufferObject*> obj(cx, &args.thisv().toObject().as<CloneBufferObject>());
-        obj->discard();
-
-        char* str = JS_EncodeString(cx, args[0].toString());
-        if (!str)
-            return false;
-        size_t nbytes = JS_GetStringLength(args[0].toString());
-        MOZ_ASSERT(nbytes % sizeof(uint64_t) == 0);
-        auto buf = js::MakeUnique<JSStructuredCloneData>(0, 0, nbytes);
-        if (!buf->Init(nbytes, nbytes)) {
-            JS_free(cx, str);
+
+        uint8_t* data = nullptr;
+        UniquePtr<uint8_t[], JS::FreePolicy> dataOwner;
+        uint32_t nbytes;
+
+        if (args.get(0).isObject() && args[0].toObject().is<ArrayBufferObject>()) {
+            ArrayBufferObject* buffer = &args[0].toObject().as<ArrayBufferObject>();
+            bool isSharedMemory;
+            js::GetArrayBufferLengthAndData(buffer, &nbytes, &isSharedMemory, &data);
+            MOZ_ASSERT(!isSharedMemory);
+        } else {
+            JSString* str = JS::ToString(cx, args.get(0));
+            if (!str)
+                return false;
+            data = reinterpret_cast<uint8_t*>(JS_EncodeString(cx, str));
+            if (!data)
+                return false;
+            dataOwner.reset(data);
+            nbytes = JS_GetStringLength(str);
+        }
+
+        if (nbytes % sizeof(uint64_t) != 0) {
+            JS_ReportErrorASCII(cx, "Invalid length for clonebuffer data");
             return false;
         }
-        js_memcpy(buf->Start(), str, nbytes);
-        JS_free(cx, str);
+
+        auto buf = js::MakeUnique<JSStructuredCloneData>(0, 0, nbytes);
+        if (!buf->Init(nbytes, nbytes))
+            return false;
+        js_memcpy(buf->Start(), data, nbytes);
+        obj->discard();
         obj->setData(buf.release());
 
         args.rval().setUndefined();
         return true;
     }
 
     static bool
     is(HandleValue v) {
@@ -2456,55 +2464,95 @@ class CloneBufferObject : public NativeO
 
     static bool
     setCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) {
         CallArgs args = CallArgsFromVp(argc, vp);
         return CallNonGenericMethod<is, setCloneBuffer_impl>(cx, args);
     }
 
     static bool
-    getCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
-        Rooted<CloneBufferObject*> obj(cx, &args.thisv().toObject().as<CloneBufferObject>());
-        MOZ_ASSERT(args.length() == 0);
-
+    getData(JSContext* cx, Handle<CloneBufferObject*> obj, JSStructuredCloneData** data) {
         if (!obj->data()) {
-            args.rval().setUndefined();
+            *data = nullptr;
             return true;
         }
 
         bool hasTransferable;
         if (!JS_StructuredCloneHasTransferables(*obj->data(), &hasTransferable))
             return false;
 
         if (hasTransferable) {
             JS_ReportErrorASCII(cx, "cannot retrieve structured clone buffer with transferables");
             return false;
         }
 
-        size_t size = obj->data()->Size();
+        *data = obj->data();
+        return true;
+    }
+
+    static bool
+    getCloneBuffer_impl(JSContext* cx, const CallArgs& args) {
+        Rooted<CloneBufferObject*> obj(cx, &args.thisv().toObject().as<CloneBufferObject>());
+        MOZ_ASSERT(args.length() == 0);
+
+        JSStructuredCloneData* data;
+        if (!getData(cx, obj, &data))
+            return false;
+
+        size_t size = data->Size();
         UniqueChars buffer(static_cast<char*>(js_malloc(size)));
         if (!buffer) {
             ReportOutOfMemory(cx);
             return false;
         }
-        auto iter = obj->data()->Iter();
-        obj->data()->ReadBytes(iter, buffer.get(), size);
+        auto iter = data->Iter();
+        data->ReadBytes(iter, buffer.get(), size);
         JSString* str = JS_NewStringCopyN(cx, buffer.get(), size);
         if (!str)
             return false;
         args.rval().setString(str);
         return true;
     }
 
     static bool
     getCloneBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) {
         CallArgs args = CallArgsFromVp(argc, vp);
         return CallNonGenericMethod<is, getCloneBuffer_impl>(cx, args);
     }
 
+    static bool
+    getCloneBufferAsArrayBuffer_impl(JSContext* cx, const CallArgs& args) {
+        Rooted<CloneBufferObject*> obj(cx, &args.thisv().toObject().as<CloneBufferObject>());
+        MOZ_ASSERT(args.length() == 0);
+
+        JSStructuredCloneData* data;
+        if (!getData(cx, obj, &data))
+            return false;
+
+        size_t size = data->Size();
+        UniqueChars buffer(static_cast<char*>(js_malloc(size)));
+        if (!buffer) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+        auto iter = data->Iter();
+        data->ReadBytes(iter, buffer.get(), size);
+        JSObject* arrayBuffer = JS_NewArrayBufferWithContents(cx, size, buffer.release());
+        if (!arrayBuffer)
+            return false;
+        args.rval().setObject(*arrayBuffer);
+        return true;
+    }
+
+    static bool
+    getCloneBufferAsArrayBuffer(JSContext* cx, unsigned int argc, JS::Value* vp) {
+        CallArgs args = CallArgsFromVp(argc, vp);
+        return CallNonGenericMethod<is, getCloneBufferAsArrayBuffer_impl>(cx, args);
+    }
+
     static void Finalize(FreeOp* fop, JSObject* obj) {
         obj->as<CloneBufferObject>().discard();
     }
 };
 
 static const ClassOps CloneBufferObjectClassOps = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
@@ -2519,16 +2567,17 @@ const Class CloneBufferObject::class_ = 
     "CloneBuffer",
     JSCLASS_HAS_RESERVED_SLOTS(CloneBufferObject::NUM_SLOTS) |
     JSCLASS_FOREGROUND_FINALIZE,
     &CloneBufferObjectClassOps
 };
 
 const JSPropertySpec CloneBufferObject::props_[] = {
     JS_PSGS("clonebuffer", getCloneBuffer, setCloneBuffer, 0),
+    JS_PSG("arraybuffer", getCloneBufferAsArrayBuffer, 0),
     JS_PS_END
 };
 
 static mozilla::Maybe<JS::StructuredCloneScope>
 ParseCloneScope(JSContext* cx, HandleString str)
 {
     mozilla::Maybe<JS::StructuredCloneScope> scope;
 
@@ -4540,17 +4589,17 @@ static const JSFunctionSpecWithHelp Test
 "representativeStringArray()",
 "  Returns an array of strings that represent the various internal string\n"
 "  types and character encodings."),
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
     JS_FN_HELP("oomThreadTypes", OOMThreadTypes, 0, 0,
 "oomThreadTypes()",
 "  Get the number of thread types that can be used as an argument for\n"
-"oomAfterAllocations() and oomAtAllocation()."),
+"  oomAfterAllocations() and oomAtAllocation()."),
 
     JS_FN_HELP("oomAfterAllocations", OOMAfterAllocations, 2, 0,
 "oomAfterAllocations(count [,threadType])",
 "  After 'count' js_malloc memory allocations, fail every following allocation\n"
 "  (return nullptr). The optional thread type limits the effect to the\n"
 "  specified type of helper thread."),
 
     JS_FN_HELP("oomAtAllocation", OOMAtAllocation, 2, 0,
@@ -4694,18 +4743,18 @@ gc::ZealModeHelpText),
 "  memory or been terminated by the slow script dialog."),
 
     JS_FN_HELP("readGeckoProfilingStack", ReadGeckoProfilingStack, 0, 0,
 "readGeckoProfilingStack()",
 "  Reads the jit stack using ProfilingFrameIterator."),
 
     JS_FN_HELP("enableOsiPointRegisterChecks", EnableOsiPointRegisterChecks, 0, 0,
 "enableOsiPointRegisterChecks()",
-"Emit extra code to verify live regs at the start of a VM call are not\n"
-"modified before its OsiPoint."),
+"  Emit extra code to verify live regs at the start of a VM call are not\n"
+"  modified before its OsiPoint."),
 
     JS_FN_HELP("displayName", DisplayName, 1, 0,
 "displayName(fn)",
 "  Gets the display name for a function, which can possibly be a guessed or\n"
 "  inferred name based on where the function was defined. This can be\n"
 "  different from the 'name' property on the function."),
 
     JS_FN_HELP("isAsmJSCompilationAvailable", IsAsmJSCompilationAvailable, 0, 0,
@@ -4714,17 +4763,17 @@ gc::ZealModeHelpText),
 "  (e.g., by the debugger)."),
 
     JS_FN_HELP("isSimdAvailable", IsSimdAvailable, 0, 0,
 "isSimdAvailable",
 "  Returns true if SIMD extensions are supported on this platform."),
 
     JS_FN_HELP("getJitCompilerOptions", GetJitCompilerOptions, 0, 0,
 "getCompilerOptions()",
-"Return an object describing some of the JIT compiler options.\n"),
+"  Return an object describing some of the JIT compiler options.\n"),
 
     JS_FN_HELP("isAsmJSModule", IsAsmJSModule, 1, 0,
 "isAsmJSModule(fn)",
 "  Returns whether the given value is a function containing \"use asm\" that has been\n"
 "  validated according to the asm.js spec."),
 
     JS_FN_HELP("isAsmJSModuleLoadedFromCache", IsAsmJSModuleLoadedFromCache, 1, 0,
 "isAsmJSModuleLoadedFromCache(fn)",
@@ -4767,17 +4816,17 @@ gc::ZealModeHelpText),
 "  until background compilation is complete."),
 
     JS_FN_HELP("isLazyFunction", IsLazyFunction, 1, 0,
 "isLazyFunction(fun)",
 "  True if fun is a lazy JSFunction."),
 
     JS_FN_HELP("isRelazifiableFunction", IsRelazifiableFunction, 1, 0,
 "isRelazifiableFunction(fun)",
-"  Ture if fun is a JSFunction with a relazifiable JSScript."),
+"  True if fun is a JSFunction with a relazifiable JSScript."),
 
     JS_FN_HELP("enableShellAllocationMetadataBuilder", EnableShellAllocationMetadataBuilder, 0, 0,
 "enableShellAllocationMetadataBuilder()",
 "  Use ShellAllocationMetadataBuilder to supply metadata for all newly created objects."),
 
     JS_FN_HELP("getAllocationMetadata", GetAllocationMetadata, 1, 0,
 "getAllocationMetadata(obj)",
 "  Get the metadata for an object."),
@@ -4820,21 +4869,20 @@ gc::ZealModeHelpText),
 "  are valuable and should be generally enabled, however they can be very expensive for large\n"
 "  (wasm) programs."),
 
     JS_FN_HELP("serialize", Serialize, 1, 0,
 "serialize(data, [transferables, [policy]])",
 "  Serialize 'data' using JS_WriteStructuredClone. Returns a structured\n"
 "  clone buffer object. 'policy' may be an options hash. Valid keys:\n"
 "    'SharedArrayBuffer' - either 'allow' (the default) or 'deny'\n"
-"    to specify whether SharedArrayBuffers may be serialized.\n"
-"\n"
+"      to specify whether SharedArrayBuffers may be serialized.\n"
 "    'scope' - SameProcessSameThread, SameProcessDifferentThread, or\n"
-"    DifferentProcess. Determines how some values will be serialized.\n"
-"    Clone buffers may only be deserialized with a compatible scope."),
+"      DifferentProcess. Determines how some values will be serialized.\n"
+"      Clone buffers may only be deserialized with a compatible scope."),
 
     JS_FN_HELP("deserialize", Deserialize, 1, 0,
 "deserialize(clonebuffer[, opts])",
 "  Deserialize data generated by serialize. 'opts' is an options hash with one\n"
 "  recognized key 'scope', which limits the clone buffers that are considered\n"
 "  valid. Allowed values: 'SameProcessSameThread', 'SameProcessDifferentThread',\n"
 "  and 'DifferentProcess'. So for example, a DifferentProcess clone buffer\n"
 "  may be deserialized in any scope, but a SameProcessSameThread clone buffer\n"
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -2121,33 +2121,39 @@ InlineTypedObject::obj_trace(JSTracer* t
     // tracing. If there is an entry in the compartment's LazyArrayBufferTable,
     // tracing that reference will be taken care of by the table itself.
     if (typedObj.is<InlineTransparentTypedObject>())
         return;
 
     typedObj.typeDescr().traceInstances(trc, typedObj.inlineTypedMem(), 1);
 }
 
-/* static */ void
-InlineTypedObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src)
+/* static */ size_t
+InlineTypedObject::obj_moved(JSObject* dst, JSObject* src)
 {
+    if (!IsInsideNursery(src))
+        return 0;
+
     // Inline typed object element arrays can be preserved on the stack by Ion
     // and need forwarding pointers created during a minor GC. We can't do this
     // in the trace hook because we don't have any stale data to determine
     // whether this object moved and where it was moved from.
     TypeDescr& descr = dst->as<InlineTypedObject>().typeDescr();
     if (descr.kind() == type::Array) {
         // The forwarding pointer can be direct as long as there is enough
         // space for it. Other objects might point into the object's buffer,
         // but they will not set any direct forwarding pointers.
         uint8_t* oldData = reinterpret_cast<uint8_t*>(src) + offsetOfDataStart();
         uint8_t* newData = dst->as<InlineTypedObject>().inlineTypedMem();
-        dst->zone()->group()->nursery().maybeSetForwardingPointer(trc, oldData, newData,
-                                                                  descr.size() >= sizeof(uintptr_t));
+        auto& nursery = dst->zone()->group()->nursery();
+        bool direct = descr.size() >= sizeof(uintptr_t);
+        nursery.setForwardingPointerWhileTenuring(oldData, newData, direct);
     }
+
+    return 0;
 }
 
 ArrayBufferObject*
 InlineTransparentTypedObject::getOrCreateBuffer(JSContext* cx)
 {
     ObjectWeakMap*& table = cx->compartment()->lazyArrayBuffers;
     if (!table) {
         table = cx->new_<ObjectWeakMap>(cx);
@@ -2214,44 +2220,58 @@ const ObjectOps TypedObject::objectOps_ 
     TypedObject::obj_setProperty,
     TypedObject::obj_getOwnPropertyDescriptor,
     TypedObject::obj_deleteProperty,
     nullptr, nullptr, /* watch/unwatch */
     nullptr,   /* getElements */
     nullptr, /* thisValue */
 };
 
-#define DEFINE_TYPEDOBJ_CLASS(Name, Trace, flag)         \
+#define DEFINE_TYPEDOBJ_CLASS(Name, Trace, Moved, flag)   \
     static const ClassOps Name##ClassOps = {             \
         nullptr,        /* addProperty */                \
         nullptr,        /* delProperty */                \
         nullptr,        /* enumerate   */                \
         TypedObject::obj_newEnumerate,                   \
         nullptr,        /* resolve     */                \
         nullptr,        /* mayResolve  */                \
         nullptr,        /* finalize    */                \
         nullptr,        /* call        */                \
         nullptr,        /* hasInstance */                \
         nullptr,        /* construct   */                \
         Trace,                                           \
     };                                                   \
+    static const ClassExtension Name##ClassExt = {       \
+        nullptr,        /* weakmapKeyDelegateOp */       \
+        Moved           /* objectMovedOp */              \
+    };                                                   \
     const Class Name::class_ = {                         \
         # Name,                                          \
         Class::NON_NATIVE | flag,                        \
         &Name##ClassOps,                                 \
         JS_NULL_CLASS_SPEC,                              \
-        JS_NULL_CLASS_EXT,                               \
+        &Name##ClassExt,                                 \
         &TypedObject::objectOps_                         \
     }
 
-DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject, OutlineTypedObject::obj_trace, 0);
-DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject,      OutlineTypedObject::obj_trace, 0);
-DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject,  InlineTypedObject::obj_trace,
+DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject,
+                      OutlineTypedObject::obj_trace,
+                      nullptr,
+                      0);
+DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject,
+                      OutlineTypedObject::obj_trace,
+                      nullptr,
+                      0);
+DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject,
+                      InlineTypedObject::obj_trace,
+                      InlineTypedObject::obj_moved,
                       JSCLASS_DELAY_METADATA_BUILDER);
-DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject,       InlineTypedObject::obj_trace,
+DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject,
+                      InlineTypedObject::obj_trace,
+                      InlineTypedObject::obj_moved,
                       JSCLASS_DELAY_METADATA_BUILDER);
 
 static int32_t
 LengthForType(TypeDescr& descr)
 {
     switch (descr.kind()) {
       case type::Scalar:
       case type::Reference:
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -718,17 +718,17 @@ class InlineTypedObject : public TypedOb
         return inlineTypedMem();
     }
 
     uint8_t* inlineTypedMemForGC() const {
         return inlineTypedMem();
     }
 
     static void obj_trace(JSTracer* trace, JSObject* object);
-    static void objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src);
+    static size_t obj_moved(JSObject* dst, JSObject* src);
 
     static size_t offsetOfDataStart() {
         return offsetof(InlineTypedObject, data_);
     }
 
     static InlineTypedObject* create(JSContext* cx, HandleTypeDescr descr,
                                      gc::InitialHeap heap = gc::DefaultHeap);
     static InlineTypedObject* createCopy(JSContext* cx, Handle<InlineTypedObject*> templateObject,
--- a/js/src/devtools/rootAnalysis/analyze.py
+++ b/js/src/devtools/rootAnalysis/analyze.py
@@ -226,20 +226,23 @@ parser.add_argument('--jobs', '-j', defa
 parser.add_argument('--list', const=True, nargs='?', type=bool,
                     help='display available steps')
 parser.add_argument('--buildcommand', '--build', '-b', type=str, nargs='?',
                     help='command to build the tree being analyzed')
 parser.add_argument('--tag', '-t', type=str, nargs='?',
                     help='name of job, also sets build command to "build.<tag>"')
 parser.add_argument('--expect-file', type=str, nargs='?',
                     help='deprecated option, temporarily still present for backwards compatibility')
-parser.add_argument('--verbose', '-v', action='store_true',
+parser.add_argument('--verbose', '-v', action='count', default=1,
                     help='Display cut & paste commands to run individual steps')
+parser.add_argument('--quiet', '-q', action='count', default=0,
+                    help='Suppress output')
 
 args = parser.parse_args()
+args.verbose = max(0, args.verbose - args.quiet)
 
 for default in defaults:
     try:
         execfile(default, config)
         if args.verbose:
             print("Loaded %s" % default)
     except:
         pass
@@ -251,17 +254,17 @@ for k,v in vars(args).items():
         data[k] = v
 
 if args.tag and not args.buildcommand:
     args.buildcommand="build.%s" % args.tag
 
 if args.jobs is not None:
     data['jobs'] = args.jobs
 if not data.get('jobs'):
-    data['jobs'] = subprocess.check_output(['nproc', '--ignore=1']).strip()
+    data['jobs'] = int(subprocess.check_output(['nproc', '--ignore=1']).strip())
 
 if args.buildcommand:
     data['buildcommand'] = args.buildcommand
 elif 'BUILD' in os.environ:
     data['buildcommand'] = os.environ['BUILD']
 else:
     data['buildcommand'] = 'make -j4 -s'
 
--- a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+++ b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
@@ -1,15 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 
 "use strict";
 
 loadRelativeToScript('utility.js');
 loadRelativeToScript('annotations.js');
 loadRelativeToScript('callgraph.js');
+loadRelativeToScript('dumpCFG.js');
 
 ///////////////////////////////////////////////////////////////////////////////
 // Annotations
 ///////////////////////////////////////////////////////////////////////////////
 
 function checkExternalFunction(entry)
 {
     var whitelist = [
@@ -19,19 +20,22 @@ function checkExternalFunction(entry)
         "ceilf",
         "floorf",
         /^rusturl/,
         "memcmp",
         "strcmp",
         "fmod",
         "floor",
         "ceil",
+        "atof",
         /memchr/,
         "strlen",
+        "Servo_ComputedValues_EqualCustomProperties",
         /Servo_DeclarationBlock_GetCssText/,
+        "Servo_GetArcStringData",
         /nsIFrame::AppendOwnedAnonBoxes/,
         // Assume that atomic accesses are threadsafe.
         /^__atomic_fetch_/,
         /^__atomic_load_/,
         /^__atomic_thread_fence/,
     ];
     if (entry.matches(whitelist))
         return;
@@ -216,55 +220,101 @@ function treatAsSafeArgument(entry, varN
         ["Gecko_StyleShapeSource_SetURLValue", "aShape", null],
         ["Gecko_nsFont_InitSystem", "aDest", null],
         ["Gecko_nsFont_SetFontFeatureValuesLookup", "aFont", null],
         ["Gecko_nsFont_ResetFontFeatureValuesLookup", "aFont", null],
         ["Gecko_nsStyleFont_FixupNoneGeneric", "aFont", null],
         ["Gecko_StyleTransition_SetUnsupportedProperty", "aTransition", null],
         ["Gecko_AddPropertyToSet", "aPropertySet", null],
         ["Gecko_CalcStyleDifference", "aAnyStyleChanged", null],
+        ["Gecko_CalcStyleDifference", "aOnlyResetStructsChanged", null],
         ["Gecko_nsStyleSVG_CopyContextProperties", "aDst", null],
         ["Gecko_nsStyleFont_PrefillDefaultForGeneric", "aFont", null],
         ["Gecko_nsStyleSVG_SetContextPropertiesLength", "aSvg", null],
         ["Gecko_ClearAlternateValues", "aFont", null],
         ["Gecko_AppendAlternateValues", "aFont", null],
         ["Gecko_CopyAlternateValuesFrom", "aDest", null],
         ["Gecko_CounterStyle_GetName", "aResult", null],
         ["Gecko_CounterStyle_GetSingleString", "aResult", null],
+        ["Gecko_EnsureMozBorderColors", "aBorder", null],
+        ["Gecko_ClearMozBorderColors", "aBorder", null],
+        ["Gecko_AppendMozBorderColors", "aBorder", null],
+        ["Gecko_CopyMozBorderColors", "aDest", null],
     ];
     for (var [entryMatch, varMatch, csuMatch] of whitelist) {
         assert(entryMatch || varMatch || csuMatch);
         if (entryMatch && !nameMatches(entry.name, entryMatch))
             continue;
         if (varMatch && !nameMatches(varName, varMatch))
             continue;
         if (csuMatch && (!csuName || !nameMatches(csuName, csuMatch)))
             continue;
         return true;
     }
     return false;
 }
 
+function isSafeAssignment(entry, edge, variable)
+{
+    if (edge.Kind != 'Assign')
+        return false;
+
+    var [mangled, unmangled] = splitFunction(entry.name);
+
+    // The assignment
+    //
+    //   nsFont* font = fontTypes[eType];
+    //
+    // ends up with 'font' pointing to a member of 'this', so it should inherit
+    // the safety of 'this'.
+    if (unmangled.includes("mozilla::LangGroupFontPrefs::Initialize") &&
+        variable == 'font')
+    {
+        const [lhs, rhs] = edge.Exp;
+        const {Kind, Exp: [{Kind: indexKind, Exp: [collection, index]}]} = rhs;
+        if (Kind == 'Drf' &&
+            indexKind == 'Index' &&
+            collection.Kind == 'Var' &&
+            collection.Variable.Name[0] == 'fontTypes')
+        {
+            return entry.isSafeArgument(0); // 'this'
+        }
+    }
+
+    return false;
+}
+
 function checkFieldWrite(entry, location, fields)
 {
     var name = entry.name;
     for (var field of fields) {
         // The analysis is having some trouble keeping track of whether
         // already_AddRefed and nsCOMPtr structures are safe to access.
         // Hopefully these will be thread local, but it would be better to
         // improve the analysis to handle these.
         if (/already_AddRefed.*?.mRawPtr/.test(field))
             return;
         if (/nsCOMPtr<.*?>.mRawPtr/.test(field))
             return;
+
+        if (/\bThreadLocal<\b/.test(field))
+            return;
     }
 
     var str = "";
     for (var field of fields)
         str += " " + field;
+
+    // Bug 1400435
+    if (entry.stack[entry.stack.length - 1].callee.match(/^Gecko_CSSValue_Set/) &&
+        str == " nsAutoRefCnt.mValue")
+    {
+        return;
+    }
+
     dumpError(entry, location, "Field write" + str);
 }
 
 function checkDereferenceWrite(entry, location, variable)
 {
     var name = entry.name;
 
     // Maybe<T> uses placement new on local storage in a way we don't understand.
@@ -276,16 +326,21 @@ function checkDereferenceWrite(entry, lo
     // Allow this if the UniquePtr<> is threadsafe.
     if (/UniquePtr.*?::reset/.test(name) && entry.isSafeArgument(0))
         return;
 
     // Operations on nsISupports reference counts.
     if (hasThreadsafeReferenceCounts(entry, /nsCOMPtr<T>::swap\(.*?\[with T = (.*?)\]/))
         return;
 
+    // ConvertToLowerCase::write writes through a local pointer into the first
+    // argument.
+    if (/ConvertToLowerCase::write/.test(name) && entry.isSafeArgument(0))
+        return;
+
     dumpError(entry, location, "Dereference write " + (variable ? variable : "<unknown>"));
 }
 
 function ignoreCallEdge(entry, callee)
 {
     var name = entry.name;
 
     // nsPropertyTable::GetPropertyInternal has the option of removing data
@@ -362,103 +417,104 @@ function ignoreCallEdge(entry, callee)
 
 function ignoreContents(entry)
 {
     var whitelist = [
         // We don't care what happens when we're about to crash.
         "abort",
         /MOZ_ReportAssertionFailure/,
         /MOZ_ReportCrash/,
+        /MOZ_CrashPrintf/,
+        /MOZ_CrashOOL/,
         /AnnotateMozCrashReason/,
         /InvalidArrayIndex_CRASH/,
         /NS_ABORT_OOM/,
 
         // These ought to be threadsafe.
         "NS_DebugBreak",
         /mozalloc_handle_oom/,
         /^NS_Log/, /log_print/, /LazyLogModule::operator/,
         /SprintfLiteral/, "PR_smprintf", "PR_smprintf_free",
         /NS_DispatchToMainThread/, /NS_ReleaseOnMainThreadSystemGroup/,
         /NS_NewRunnableFunction/, /NS_Atomize/,
         /nsCSSValue::BufferFromString/,
         /NS_strdup/,
         /Assert_NoQueryNeeded/,
+        /AssertCurrentThreadOwnsMe/,
+        /PlatformThread::CurrentId/,
         /imgRequestProxy::GetProgressTracker/, // Uses an AutoLock
         /Smprintf/,
         "malloc",
         "free",
         "realloc",
         "jemalloc_thread_local_arena",
-        /profiler_register_thread/,
-        /profiler_unregister_thread/,
 
         // These all create static strings in local storage, which is threadsafe
         // to do but not understood by the analysis yet.
         / EmptyString\(\)/,
         /nsCSSProps::LookupPropertyValue/,
         /nsCSSProps::ValueToKeyword/,
         /nsCSSKeywords::GetStringValue/,
 
-        // The analysis can't cope with the indirection used for the objects
-        // being initialized here.
-        "Gecko_GetOrCreateKeyframeAtStart",
-        "Gecko_GetOrCreateInitialKeyframe",
-        "Gecko_GetOrCreateFinalKeyframe",
-        "Gecko_NewStyleQuoteValues",
-        "Gecko_NewCSSValueSharedList",
-        "Gecko_NewNoneTransform",
-        "Gecko_NewGridTemplateAreasValue",
-        /nsCSSValue::SetCalcValue/,
-        /CSSValueSerializeCalcOps::Append/,
-        "Gecko_CSSValue_SetFunction",
-        "Gecko_CSSValue_SetArray",
-        "Gecko_CSSValue_InitSharedList",
-        "Gecko_EnsureMozBorderColors",
-        "Gecko_ClearMozBorderColors",
-        "Gecko_AppendMozBorderColors",
-        "Gecko_CopyMozBorderColors",
-        "Gecko_SetNullImageValue",
+        // These could probably be handled by treating the scope of PSAutoLock
+        // aka BaseAutoLock<PSMutex> as threadsafe.
+        /profiler_register_thread/,
+        /profiler_unregister_thread/,
 
         // The analysis thinks we'll write to mBits in the DoGetStyleFoo<false>
         // call.  Maybe the template parameter confuses it?
         /nsStyleContext::PeekStyle/,
 
-        // Needs main thread assertions or other fixes.
-        /UndisplayedMap::GetEntryFor/,
+        // The analysis can't cope with the indirection used for the objects
+        // being initialized here, from nsCSSValue::Array::Create to the return
+        // value of the Item(i) getter.
+        /nsCSSValue::SetCalcValue/,
+
+        // Unable to analyze safety of linked list initialization.
+        "Gecko_NewCSSValueSharedList",
+        "Gecko_CSSValue_InitSharedList",
+
+        // Unable to trace through dataflow, but straightforward if inspected.
+        "Gecko_NewNoneTransform",
+
+        // Bug 1368922
+        "Gecko_UnsetDirtyStyleAttr",
+
+        // Bug 1400438
+        "Gecko_AppendMozBorderColors",
+
+        // Need main thread assertions or other fixes.
         /EffectCompositor::GetServoAnimationRule/,
         /LookAndFeel::GetColor/,
-        "Gecko_CopyStyleContentsFrom",
-        "Gecko_CSSValue_SetPixelValue",
-        "Gecko_UnsetDirtyStyleAttr",
-        /nsCSSPropertyIDSet::AddProperty/,
     ];
     if (entry.matches(whitelist))
         return true;
 
     if (entry.isSafeArgument(0)) {
         var heapWhitelist = [
             // Operations on heap structures pointed to by arrays and strings are
             // threadsafe as long as the array/string itself is threadsafe.
             /nsTArray_Impl.*?::AppendElement/,
             /nsTArray_Impl.*?::RemoveElementsAt/,
             /nsTArray_Impl.*?::ReplaceElementsAt/,
-            /nsTArray_Impl.*?::InsertElementsAt/,
+            /nsTArray_Impl.*?::InsertElementAt/,
             /nsTArray_Impl.*?::SetCapacity/,
+            /nsTArray_Impl.*?::SetLength/,
             /nsTArray_base.*?::EnsureCapacity/,
             /nsTArray_base.*?::ShiftData/,
             /AutoTArray.*?::Init/,
-            /nsTSubstring<T>::SetCapacity/,
-            /nsTSubstring<T>::SetLength/,
-            /nsTSubstring<T>::Assign/,
-            /nsTSubstring<T>::Append/,
-            /nsTSubstring<T>::Replace/,
-            /nsTSubstring<T>::Trim/,
-            /nsTSubstring<T>::Truncate/,
-            /nsTSubstring<T>::StripTaggedASCII/,
-            /nsTSubstring<T>::operator=/,
+            /(nsTSubstring<T>|nsAC?String)::SetCapacity/,
+            /(nsTSubstring<T>|nsAC?String)::SetLength/,
+            /(nsTSubstring<T>|nsAC?String)::Assign/,
+            /(nsTSubstring<T>|nsAC?String)::Append/,
+            /(nsTSubstring<T>|nsAC?String)::Replace/,
+            /(nsTSubstring<T>|nsAC?String)::Trim/,
+            /(nsTSubstring<T>|nsAC?String)::Truncate/,
+            /(nsTSubstring<T>|nsAC?String)::StripTaggedASCII/,
+            /(nsTSubstring<T>|nsAC?String)::operator=/,
             /nsTAutoStringN<T, N>::nsTAutoStringN/,
             /nsTFixedString<T>::nsTFixedString/,
 
             // Similar for some other data structures
             /nsCOMArray_base::SetCapacity/,
             /nsCOMArray_base::Clear/,
             /nsCOMArray_base::AppendElement/,
 
@@ -486,18 +542,18 @@ function ignoreContents(entry)
         ];
         if (entry.matches(firstArgWhitelist))
             return true;
     }
 
     if (entry.isSafeArgument(2)) {
         var secondArgWhitelist = [
             /nsStringBuffer::ToString/,
-            /AppendUTF8toUTF16/,
-            /AppendASCIItoUTF16/,
+            /AppendUTF\d+toUTF\d+/,
+            /AppendASCIItoUTF\d+/,
         ];
         if (entry.matches(secondArgWhitelist))
             return true;
     }
 
     return false;
 }
 
@@ -566,22 +622,22 @@ function isZero(exp)
 // pointer fields etc.) without concerns about thread safety. This includes
 // pointers to stack data, null pointers, and other data we know is thread
 // local, such as certain arguments to the root functions.
 //
 // Entries in the worklist keep track of the pointer arguments to the function
 // which are safe using a sorted array, so that this can be propagated down the
 // stack. Zero is |this|, and arguments are indexed starting at one.
 
-function WorklistEntry(name, safeArguments, stack)
+function WorklistEntry(name, safeArguments, stack, parameterNames)
 {
     this.name = name;
     this.safeArguments = safeArguments;
     this.stack = stack;
-    this.parameterNames = {};
+    this.parameterNames = parameterNames;
 }
 
 WorklistEntry.prototype.readable = function()
 {
     const [ mangled, readable ] = splitFunction(this.name);
     return readable;
 }
 
@@ -679,16 +735,22 @@ CallSite.prototype.safeString = function
 
 ///////////////////////////////////////////////////////////////////////////////
 // Analysis Core
 ///////////////////////////////////////////////////////////////////////////////
 
 var errorCount = 0;
 var errorLimit = 100;
 
+// We want to suppress output for functions that ended up not having any
+// hazards, for brevity of the final output. So each new toplevel function will
+// initialize this to a string, which should be printed only if an error is
+// seen.
+var errorHeader;
+
 var startTime = new Date;
 function elapsedTime()
 {
     var seconds = (new Date - startTime) / 1000;
     return "[" + seconds.toFixed(2) + "s] ";
 }
 
 var options = parse_options([
@@ -718,44 +780,50 @@ var removePrefix = add_trailing_slash(op
 var addPrefix = add_trailing_slash(options.add_prefix);
 
 if (options.verbose) {
     printErr(`Removing prefix ${removePrefix} from paths`);
     printErr(`Prepending ${addPrefix} to paths`);
 }
 
 print(elapsedTime() + "Loading types...");
-loadTypes('src_comp.xdb');
+if (os.getenv("TYPECACHE"))
+    loadTypesWithCache('src_comp.xdb', os.getenv("TYPECACHE"));
+else
+    loadTypes('src_comp.xdb');
 print(elapsedTime() + "Starting analysis...");
 
-var reachable = {};
-
 var xdb = xdbLibrary();
 xdb.open("src_body.xdb");
 
 var minStream = xdb.min_data_stream();
 var maxStream = xdb.max_data_stream();
+var roots = [];
 
-var roots = [];
-for (var bodyIndex = minStream; bodyIndex <= maxStream; bodyIndex++) {
-    var key = xdb.read_key(bodyIndex);
-    var name = key.readString();
-    if (/^Gecko_/.test(name)) {
-        var data = xdb.read_entry(key);
-        if (/ServoBindings.cpp/.test(data.readString()))
-            roots.push(name);
-        xdb.free_string(data);
+var [flag, arg] = scriptArgs;
+if (flag && (flag == '-f' || flag == '--function')) {
+    roots = [arg];
+} else {
+    for (var bodyIndex = minStream; bodyIndex <= maxStream; bodyIndex++) {
+        var key = xdb.read_key(bodyIndex);
+        var name = key.readString();
+        if (/^Gecko_/.test(name)) {
+            var data = xdb.read_entry(key);
+            if (/ServoBindings.cpp/.test(data.readString()))
+                roots.push(name);
+            xdb.free_string(data);
+        }
+        xdb.free_string(key);
     }
-    xdb.free_string(key);
 }
 
 print(elapsedTime() + "Found " + roots.length + " roots.");
 for (var i = 0; i < roots.length; i++) {
     var root = roots[i];
-    print(elapsedTime() + "#" + (i + 1) + " Analyzing " + root + " ...");
+    errorHeader = elapsedTime() + "#" + (i + 1) + " Analyzing " + root + " ...";
     try {
         processRoot(root);
     } catch (e) {
         if (e != "Error!")
             throw e;
     }
 }
 
@@ -766,18 +834,26 @@ var currentBody;
 // All local variable assignments we have seen in either the outer or inner
 // function. This crosses loop boundaries, and currently has an unsoundness
 // where later assignments in a loop are not taken into account.
 var assignments;
 
 // All loops in the current function which are reachable off main thread.
 var reachableLoops;
 
+// Functions that are reachable from the current root.
+var reachable = {};
+
 function dumpError(entry, location, text)
 {
+    if (errorHeader) {
+        print(errorHeader);
+        errorHeader = undefined;
+    }
+
     var stack = entry.stack;
     print("Error: " + text);
     print("Location: " + entry.name + (location ? " @ " + location : "") + stack[0].safeString());
     print("Stack Trace:");
     // Include the callers in the stack trace instead of the callees. Make sure
     // the dummy stack entry we added for the original roots is in place.
     assert(stack[stack.length - 1].location == null);
     for (var i = 0; i < stack.length - 1; i++)
@@ -798,51 +874,69 @@ function variableAssignRhs(edge)
     if (edge.Kind == "Assign" && edge.Exp[1].Kind == "Drf" && edge.Exp[1].Exp[0].Kind == "Var") {
         var variable = edge.Exp[1].Exp[0].Variable;
         if (isLocalVariable(variable))
             return variable;
     }
     return null;
 }
 
-function processAssign(entry, location, lhs, edge)
+function processAssign(body, entry, location, lhs, edge)
 {
     var fields;
     [lhs, fields] = stripFields(lhs);
 
     switch (lhs.Kind) {
       case "Var":
         var name = variableName(lhs.Variable);
         if (isLocalVariable(lhs.Variable)) {
             // Remember any assignments to local variables in this function.
             // Note that we ignore any points where the variable's address is
             // taken and indirect assignments might occur. This is an
             // unsoundness in the analysis.
 
+            let assign = [body, edge];
+
             // Chain assignments if the RHS has only been assigned once.
             var rhsVariable = variableAssignRhs(edge);
             if (rhsVariable) {
-                var rhsEdge = singleAssignment(variableName(rhsVariable));
-                if (rhsEdge)
-                    edge = rhsEdge;
+                var rhsAssign = singleAssignment(variableName(rhsVariable));
+                if (rhsAssign)
+                    assign = rhsAssign;
             }
 
             if (!(name in assignments))
                 assignments[name] = [];
-            assignments[name].push(edge);
+            assignments[name].push(assign);
         } else {
             checkVariableAssignment(entry, location, name);
         }
         return;
       case "Drf":
         var variable = null;
         if (lhs.Exp[0].Kind == "Var") {
             variable = lhs.Exp[0].Variable;
             if (isSafeVariable(entry, variable))
                 return;
+        } else if (lhs.Exp[0].Kind == "Fld") {
+            const {
+                Type: {Kind, Type: fieldType},
+                FieldCSU: {Type: {Kind: containerTypeKind,
+                                  Name: containerTypeName}}
+            } = lhs.Exp[0].Field;
+            const [containerExpr] = lhs.Exp[0].Exp;
+
+            if (containerTypeKind == 'CSU' &&
+                Kind == 'Pointer' &&
+                isEdgeSafeArgument(entry, containerExpr) &&
+                isSafeMemberPointer(containerTypeName, fieldType))
+            {
+                return;
+            }
+
         }
         if (fields.length)
             checkFieldWrite(entry, location, fields);
         else
             checkDereferenceWrite(entry, location, variableName(variable));
         return;
       case "Int":
         if (isZero(lhs)) {
@@ -892,17 +986,17 @@ function process(entry, body, addCallee)
 
         var location = get_location(body.PPoint[edge.Index[0] - 1].Location);
 
         var callees = getCallees(edge);
         for (var callee of callees) {
             switch (callee.kind) {
             case "direct":
                 var safeArguments = getEdgeSafeArguments(entry, edge, callee.name);
-                addCallee(new CallSite(callee.name, safeArguments, location, entry.parameterNames));
+                addCallee(new CallSite(callee.name, safeArguments, location, {}));
                 break;
               case "resolved-field":
                 break;
               case "field":
                 var field = callee.csu + "." + callee.field;
                 if (callee.isVirtual)
                     checkOverridableVirtualCall(entry, location, field);
                 else
@@ -916,21 +1010,21 @@ function process(entry, body, addCallee)
                 break;
             }
         }
 
         var fallthrough = true;
 
         if (edge.Kind == "Assign") {
             assert(edge.Exp.length == 2);
-            processAssign(entry, location, edge.Exp[0], edge);
+            processAssign(body, entry, location, edge.Exp[0], edge);
         } else if (edge.Kind == "Call") {
             assert(edge.Exp.length <= 2);
             if (edge.Exp.length == 2)
-                processAssign(entry, location, edge.Exp[1], edge);
+                processAssign(body, entry, location, edge.Exp[1], edge);
 
             // Treat assertion failures as if they don't return, so that
             // asserting NS_IsMainThread() is sufficient to prevent the
             // analysis from considering a block of code.
             if (isDirectCall(edge, /MOZ_ReportAssertionFailure/))
                 fallthrough = false;
         } else if (edge.Kind == "Loop") {
             reachableLoops[edge.BlockId.Loop] = true;
@@ -988,26 +1082,34 @@ function maybeProcessMissingFunction(ent
     }
 
     return false;
 }
 
 function processRoot(name)
 {
     var safeArguments = [];
-    var worklist = [new WorklistEntry(name, safeArguments, [new CallSite(name, safeArguments, null, {})])];
+    var parameterNames = {};
+    var worklist = [new WorklistEntry(name, safeArguments, [new CallSite(name, safeArguments, null, parameterNames)], parameterNames)];
+
+    reachable = {};
+
+    var armed = false;
+    if (name.includes("Gecko_CSSValue_Set"))
+        armed = true;
 
     while (worklist.length > 0) {
         var entry = worklist.pop();
 
         // In principle we would be better off doing a meet-over-paths here to get
         // the common subset of arguments which are safe to write through. However,
         // analyzing functions separately for each subset if simpler, ensures that
         // the stack traces we produce accurately characterize the stack arguments,
         // and should be fast enough for now.
+
         if (entry.mangledName() in reachable)
             continue;
         reachable[entry.mangledName()] = true;
 
         if (ignoreContents(entry))
             continue;
 
         var data = xdb.read_entry(entry.name);
@@ -1029,17 +1131,17 @@ function processRoot(name)
             if (!maybeProcessMissingFunction(entry, Array.prototype.push.bind(callees)))
                 checkExternalFunction(entry);
         }
         xdb.free_string(data);
 
         for (var callee of callees) {
             if (!ignoreCallEdge(entry, callee.callee)) {
                 var nstack = [callee, ...entry.stack];
-                worklist.push(new WorklistEntry(callee.callee, callee.safeArguments, nstack));
+                worklist.push(new WorklistEntry(callee.callee, callee.safeArguments, nstack, callee.parameterNames));
             }
         }
     }
 }
 
 function isEdgeSafeArgument(entry, exp)
 {
     var fields;
@@ -1080,35 +1182,53 @@ function singleAssignment(name)
         var edges = assignments[name];
         if (edges.length == 1)
             return edges[0];
     }
     return null;
 }
 
 function expressionValueEdge(exp) {
-    if (exp.Kind == "Var" && exp.Variable.Kind == "Temp")
-        return singleAssignment(variableName(exp.Variable));
-    return null;
+    if (!(exp.Kind == "Var" && exp.Variable.Kind == "Temp"))
+        return null;
+    const assign = singleAssignment(variableName(exp.Variable));
+    if (!assign)
+        return null;
+    const [body, edge] = assign;
+    return edge;
 }
 
 function isSafeVariable(entry, variable)
 {
     var index = safeArgumentIndex(variable);
     if (index >= 0)
         return entry.isSafeArgument(index);
 
     if (variable.Kind != "Temp" && variable.Kind != "Local")
         return false;
     var name = variableName(variable);
 
+    if (!entry.safeLocals)
+        entry.safeLocals = new Map;
+    if (entry.safeLocals.has(name))
+        return entry.safeLocals.get(name);
+
+    const safe = isSafeLocalVariable(entry, name);
+    entry.safeLocals.set(name, safe);
+    return safe;
+}
+
+function isSafeLocalVariable(entry, name)
+{
     // If there is a single place where this variable has been assigned on
     // edges we are considering, look at that edge.
-    var edge = singleAssignment(name);
-    if (edge) {
+    var assign = singleAssignment(name);
+    if (assign) {
+        const [body, edge] = assign;
+
         // Treat temporary pointers to DebugOnly contents as thread local.
         if (isDirectCall(edge, /DebugOnly.*?::operator/))
             return true;
 
         // Treat heap allocated pointers as thread local during construction.
         // Hopefully the construction code doesn't leak pointers to the object
         // to places where other threads might access it.
         if (isDirectCall(edge, /operator new/) ||
@@ -1116,33 +1236,42 @@ function isSafeVariable(entry, variable)
         {
             return true;
         }
 
         if ("PEdgeCallInstance" in edge) {
             // References to the contents of an array are threadsafe if the array
             // itself is threadsafe.
             if ((isDirectCall(edge, /operator\[\]/) ||
+                 isDirectCall(edge, /nsTArray.*?::InsertElementAt\b/) ||
                  isDirectCall(edge, /nsStyleContent::ContentAt/)) &&
                 isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
             {
                 return true;
             }
 
-            // Watch for the coerced result of a getter_AddRefs call.
+            // Watch for the coerced result of a getter_AddRefs or getter_Copies call.
             if (isDirectCall(edge, /operator /)) {
                 var otherEdge = expressionValueEdge(edge.PEdgeCallInstance.Exp);
                 if (otherEdge &&
-                    isDirectCall(otherEdge, /getter_AddRefs/) &&
+                    isDirectCall(otherEdge, /getter_(?:AddRefs|Copies)/) &&
                     isEdgeSafeArgument(entry, otherEdge.PEdgeCallArguments.Exp[0]))
                 {
                     return true;
                 }
             }
 
+            // RefPtr::operator->() and operator* transmit the safety of the
+            // RefPtr to the return value.
+            if (isDirectCall(edge, /RefPtr<.*?>::operator(->|\*)\(\)/) &&
+                isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
+            {
+                return true;
+            }
+
             // Placement-new returns a pointer that is as safe as the pointer
             // passed to it. Exp[0] is the size, Exp[1] is the pointer/address.
             // Note that the invocation of the constructor is a separate call,
             // and so need not be considered here.
             if (isDirectCall(edge, /operator new/) &&
                 edge.PEdgeCallInstance.Exp.length == 2 &&
                 isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp[1]))
             {
@@ -1150,18 +1279,41 @@ function isSafeVariable(entry, variable)
             }
 
             // Coercion via AsAString preserves safety.
             if (isDirectCall(edge, /AsAString/) &&
                 isEdgeSafeArgument(entry, edge.PEdgeCallInstance.Exp))
             {
                 return true;
             }
+
+            // Special case:
+            //
+            //   keyframe->mTimingFunction.emplace()
+            //   keyframe->mTimingFunction->Init()
+            //
+            // The object calling Init should be considered safe here because
+            // we just emplaced it, though in general keyframe::operator->
+            // could do something crazy.
+            if (isDirectCall(edge, /operator->/)) do {
+                const predges = getPredecessors(body)[edge.Index[0]];
+                if (!predges || predges.length != 1)
+                    break;
+                const predge = predges[0];
+                if (!isDirectCall(predge, /\bemplace\b/))
+                    break;
+                const instance = predge.PEdgeCallInstance;
+                if (JSON.stringify(instance) == JSON.stringify(edge.PEdgeCallInstance))
+                    return true;
+            } while (false);
         }
 
+        if (isSafeAssignment(entry, edge, name))
+            return true;
+
         // Watch out for variables which were assigned arguments.
         var rhsVariable = variableAssignRhs(edge);
         if (rhsVariable)
             return isSafeVariable(entry, rhsVariable);
     }
 
     // When temporary stack structures are created (either to return or to call
     // methods on without assigning them a name), the generated sixgill JSON is
@@ -1184,16 +1336,36 @@ function isSafeVariable(entry, variable)
     // If it is initialized at this point we should have seen *some* write
     // already, since the CFG edges are visited in reverse post order.
     if (name in assignments)
         return false;
 
     return true;
 }
 
+function isSafeMemberPointer(containerType, memberType)
+{
+    if (memberType.Kind != 'Pointer')
+        return false;
+
+    const {Type: {Kind: pointeeKind, Name: pointeeTypeName}} = memberType;
+
+    // nsStyleBorder has a member mBorderColors of type nsBorderColors**. It is
+    // lazily initialized to an array of 4 nsBorderColors, and should inherit
+    // the safety of its container.
+    if (containerType == 'nsStyleBorder' &&
+        pointeeKind == 'CSU' &&
+        pointeeTypeName == 'nsBorderColors')
+    {
+        return true;
+    }
+
+    return false;
+}
+
 // Return whether 'exp == value' holds only when execution is on the main thread.
 function testFailsOffMainThread(exp, value) {
     switch (exp.Kind) {
       case "Drf":
         var edge = expressionValueEdge(exp.Exp[0]);
         if (edge) {
             if (isDirectCall(edge, /NS_IsMainThread/) && value)
                 return true;
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -62,16 +62,18 @@ var ignoreClasses = {
     "_MD_IOVector" : true,
     "malloc_table_t": true, // replace_malloc
     "malloc_hook_table_t": true, // replace_malloc
 };
 
 // Ignore calls through TYPE.FIELD, where TYPE is the class or struct name containing
 // a function pointer field named FIELD.
 var ignoreCallees = {
+    "js::Class.trace" : true,
+    "js::Class.finalize" : true,
     "js::ClassOps.trace" : true,
     "js::ClassOps.finalize" : true,
     "JSRuntime.destroyPrincipals" : true,
     "icu_50::UObject.__deleting_dtor" : true, // destructors in ICU code can't cause GC
     "mozilla::CycleCollectedJSRuntime.DescribeCustomObjects" : true, // During tracing, cannot GC.
     "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
     "PLDHashTableOps.hashKey" : true,
     "z_stream_s.zfree" : true,
--- a/js/src/devtools/rootAnalysis/callgraph.js
+++ b/js/src/devtools/rootAnalysis/callgraph.js
@@ -211,8 +211,25 @@ function loadTypes(type_xdb_filename) {
         const data = xdb.read_entry(csu);
         const json = JSON.parse(data.readString());
         processCSU(csu.readString(), json[0]);
 
         xdb.free_string(csu);
         xdb.free_string(data);
     }
 }
+
+function loadTypesWithCache(type_xdb_filename, cache_filename) {
+    try {
+        const cacheAB = os.file.readFile(cache_filename, "binary");
+        const cb = serialize();
+        cb.clonebuffer = cacheAB.buffer;
+        const cacheData = deserialize(cb);
+        subclasses = cacheData.subclasses;
+        superclasses = cacheData.superclasses;
+        classFunctions = cacheData.classFunctions;
+    } catch (e) {
+        loadTypes(type_xdb_filename);
+        const cb = serialize({subclasses, superclasses, classFunctions});
+        os.file.writeTypedArrayToFile(cache_filename,
+                                      new Uint8Array(cb.arraybuffer));
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/dumpCFG.js
@@ -0,0 +1,259 @@
+// const cfg = loadCFG(scriptArgs[0]);
+// dump_CFG(cfg);
+
+function loadCFG(filename) {
+  const data = os.file.readFile(filename);
+  return JSON.parse(data);
+}
+
+function dump_CFG(cfg) {
+  for (const body of cfg)
+    dump_body(body);
+}
+
+function dump_body(body, src, dst) {
+  const {BlockId,Command,DefineVariable,Index,Location,PEdge,PPoint,Version} = body;
+
+  const [mangled, unmangled] = splitFunction(BlockId.Variable.Name[0]);
+  print(`${unmangled} at ${Location[0].CacheString}:${Location[0].Line}`);
+
+  if (src === undefined) {
+    for (const def of DefineVariable)
+      print(str_definition(def));
+    print("");
+  }
+
+  for (const edge of PEdge) {
+    if (src === undefined || edge.Index[0] == src) {
+      if (dst == undefined || edge.Index[1] == dst)
+        print(str_edge(edge, body));
+    }
+  }
+}
+
+function str_definition(def) {
+  const {Type, Variable} = def;
+  return `define ${str_Variable(Variable)} : ${str_Type(Type)}`;
+}
+
+function badFormat(what, val) {
+  printErr("Bad format of " + what + ": " + JSON.stringify(val, null, 4));
+  printErr((new Error).stack);
+}
+
+function str_Variable(variable) {
+  if (variable.Kind == 'Return')
+    return '<returnval>';
+  else if (variable.Kind == 'This')
+    return 'this';
+
+  try {
+    return variable.Name[1];
+  } catch(e) {
+    badFormat("variable", variable);
+  }
+}
+
+function str_Type(type) {
+  try {
+    const {Kind, Type, Name, TypeFunctionArguments} = type;
+    if (Kind == 'Pointer')
+      return str_Type(Type) + "*";
+    else if (Kind == 'CSU') {
+      return Name;
+    }
+
+    return Kind;
+  } catch(e) {
+    badFormat("type", type);
+  }
+}
+
+var OpCodeNames = {
+  'LessEqual': ['<=', '>'],
+  'LessThan': ['<', '>='],
+  'GreaterEqual': ['>=', '<'],
+  'Greater': ['>', '<='],
+  'Plus': '+',
+  'Minus': '-',
+};
+
+function opcode_name(opcode, invert) {
+  if (opcode in OpCodeNames) {
+    const name = OpCodeNames[opcode];
+    if (invert === undefined)
+      return name;
+    return name[invert ? 1 : 0];
+  } else {
+    if (invert === undefined)
+      return opcode;
+    return (invert ? '!' : '') + opcode;
+  }
+}
+
+function str_value(val, env, options) {
+  const {Kind, Variable, String, Exp} = val;
+  if (Kind == 'Var')
+    return str_Variable(Variable);
+  else if (Kind == 'Drf') {
+    // Suppress the vtable lookup dereference
+    if (Exp[0].Kind == 'Fld' && "FieldInstanceFunction" in Exp[0].Field)
+      return str_value(Exp[0], env);
+    const exp = str_value(Exp[0], env);
+    if (options && options.noderef)
+      return exp;
+    return "*" + exp;
+  } else if (Kind == 'Fld') {
+    const {Exp, Field} = val;
+    const name = Field.Name[0];
+    if ("FieldInstanceFunction" in Field) {
+      return Field.FieldCSU.Type.Name + "::" + name;
+    }
+    const container = str_value(Exp[0]);
+    if (container.startsWith("*"))
+      return container.substring(1) + "->" + name;
+    return container + "." + name;
+  } else if (Kind == 'Empty') {
+    return '<unknown>';
+  } else if (Kind == 'Binop') {
+    const {OpCode} = val;
+    const op = opcode_name(OpCode);
+    return `${str_value(Exp[0], env)} ${op} ${str_value(Exp[1], env)}`;
+  } else if (Kind == 'Unop') {
+    const exp = str_value(Exp[0], env);
+    const {OpCode} = val;
+    if (OpCode == 'LogicalNot')
+      return `not ${exp}`;
+    return `${OpCode}(${exp})`;
+  } else if (Kind == 'Index') {
+    const index = str_value(Exp[1], env);
+    if (Exp[0].Kind == 'Drf')
+      return `${str_value(Exp[0], env, {noderef:true})}[${index}]`;
+    else
+      return `&${str_value(Exp[0], env)}[${index}]`;
+  } else if (Kind == 'NullTest') {
+    return `nullptr == ${str_value(Exp[0], env)}`;
+  } else if (Kind == "String") {
+    return '"' + String + '"';
+  } else if (String !== undefined) {
+    return String;
+  }
+  badFormat("value", val);
+}
+
+function str_thiscall_Exp(exp) {
+  return exp.Kind == 'Drf' ? str_value(exp.Exp[0]) + "->" : str_value(exp) + ".";
+}
+
+function stripcsu(s) {
+    return s.replace("class ", "").replace("struct ", "").replace("union ");
+}
+
+function str_call(prefix, edge, env) {
+  const {Exp, Type, PEdgeCallArguments, PEdgeCallInstance} = edge;
+  const {Kind, Type:cType, TypeFunctionArguments, TypeFunctionCSU} = Type;
+
+  if (Kind == 'Function') {
+    const params = PEdgeCallArguments ? PEdgeCallArguments.Exp : [];
+    const strParams = params.map(str_value);
+
+    let func;
+    let comment = "";
+    let assign_exp;
+    if (PEdgeCallInstance) {
+      const csu = TypeFunctionCSU.Type.Name;
+      const method = str_value(Exp[0], env);
+
+      // Heuristic to only display the csu for constructors
+      if (csu.includes(method)) {
+        func = stripcsu(csu) + "::" + method;
+      } else {
+        func = method;
+        comment = "# " + csu + "::" + method + "\n";
+      }
+
+      const {Exp: thisExp} = PEdgeCallInstance;
+      func = str_thiscall_Exp(thisExp) + func;
+    } else {
+      func = str_value(Exp[0]);
+    }
+    assign_exp = Exp[1];
+
+    let assign = "";
+    if (assign_exp) {
+      assign = str_value(assign_exp) + " := ";
+    }
+    return `${comment}${prefix} Call ${assign}${func}(${strParams.join(", ")})`;
+  }
+
+  print(JSON.stringify(edge, null, 4));
+  throw "unhandled format error";
+}
+
+function str_assign(prefix, edge) {
+  const {Exp} = edge;
+  const [lhs, rhs] = Exp;
+  return `${prefix} Assign ${str_value(lhs)} := ${str_value(rhs)}`;
+}
+
+function str_loop(prefix, edge) {
+  const {BlockId: {Loop}} = edge;
+  return `${prefix} Loop ${Loop}`;
+}
+
+function str_assume(prefix, edge) {
+  const {Exp, PEdgeAssumeNonZero} = edge;
+  const cmp = PEdgeAssumeNonZero ? "" : "!";
+
+  const {Exp: aExp, Kind, OpCode} = Exp[0];
+  if (Kind == 'Binop') {
+    const [lhs, rhs] = aExp;
+    const op = opcode_name(OpCode, !PEdgeAssumeNonZero);
+    return `${prefix} Assume ${str_value(lhs)} ${op} ${str_value(rhs)}`;
+  } else if (Kind == 'Unop') {
+    return `${prefix} Assume ${cmp}${OpCode} ${str_value(aExp[0])}`;
+  } else if (Kind == 'NullTest') {
+    return `${prefix} Assume nullptr ${cmp}== ${str_value(aExp[0])}`;
+  } else if (Kind == 'Drf') {
+    return `${prefix} Assume ${cmp}${str_value(Exp[0])}`;
+  }
+
+  print(JSON.stringify(edge, null, 4));
+  throw "unhandled format error";
+}
+
+function str_edge(edge, env) {
+  const {Index, Kind} = edge;
+  const [src, dst] = Index;
+  const prefix = `[${src},${dst}]`;
+
+  if (Kind == "Call")
+    return str_call(prefix, edge, env);
+  if (Kind == 'Assign')
+    return str_assign(prefix, edge);
+  if (Kind == 'Assume')
+    return str_assume(prefix, edge);
+  if (Kind == 'Loop')
+    return str_loop(prefix, edge);
+
+  print(JSON.stringify(edge, null, 4));
+  throw "unhandled edge type";
+}
+
+function str(unknown) {
+  if ("Index" in unknown) {
+    return str_edge(unknown);
+  } else if ("Kind" in unknown) {
+    if ("BlockId" in unknown)
+      return str_Variable(unknown);
+    return str_value(unknown);
+  } else if ("Type" in unknown) {
+    return str_Type(unknown);
+  }
+  return "unknown";
+}
+
+function jdump(x) {
+  print(JSON.stringify(x, null, 4));
+  quit(0);
+}
--- a/js/src/devtools/rootAnalysis/run-test.py
+++ b/js/src/devtools/rootAnalysis/run-test.py
@@ -1,9 +1,9 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # 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/.
 
 import os
 import site
 import subprocess
 import argparse
@@ -76,14 +76,19 @@ for name in cfg.tests:
     name = os.path.basename(name)
     indir = os.path.join(testdir, name)
     outdir = os.path.join(testdir, 'out', name)
     try:
         os.mkdir(outdir)
     except OSError:
         pass
 
-    test = Test(indir, outdir, cfg)
+    test = Test(indir, outdir, cfg, verbose=cfg.verbose)
 
     os.chdir(outdir)
     subprocess.call(["sh", "-c", "rm *.xdb"])
-    execfile(os.path.join(indir, "test.py"), {'test': test, 'equal': equal})
+    if cfg.verbose:
+        print("Running test %s" % name)
+    testpath = os.path.join(indir, "test.py")
+    testscript = open(testpath).read()
+    testcode = compile(testscript, testpath, 'exec')
+    exec(testcode, {'test': test, 'equal': equal})
     print("TEST-PASSED: %s" % name)
--- a/js/src/devtools/rootAnalysis/t/suppression/test.py
+++ b/js/src/devtools/rootAnalysis/t/suppression/test.py
@@ -4,19 +4,19 @@ test.run_analysis_script('gcTypes', upto
 # The suppressions file uses only mangled names since it's for internal use,
 # though I may change that soon given (1) the unfortunate non-uniqueness of
 # mangled constructor names, and (2) the usefulness of this file for
 # mrgiggles's reporting.
 suppressed = test.load_suppressed_functions()
 
 # Only one of these is fully suppressed (ie, *always* called within the scope
 # of an AutoSuppressGC).
-assert(len(filter(lambda f: 'suppressedFunction' in f, suppressed)) == 1)
-assert(len(filter(lambda f: 'halfSuppressedFunction' in f, suppressed)) == 0)
-assert(len(filter(lambda f: 'unsuppressedFunction' in f, suppressed)) == 0)
+assert(len(list(filter(lambda f: 'suppressedFunction' in f, suppressed))) == 1)
+assert(len(list(filter(lambda f: 'halfSuppressedFunction' in f, suppressed))) == 0)
+assert(len(list(filter(lambda f: 'unsuppressedFunction' in f, suppressed))) == 0)
 
 # gcFunctions should be the inverse, but we get to rely on unmangled names here.
 gcFunctions = test.load_gcFunctions()
 print(gcFunctions)
 assert('void GC()' in gcFunctions)
 assert('void suppressedFunction()' not in gcFunctions)
 assert('void halfSuppressedFunction()' in gcFunctions)
 assert('void unsuppressedFunction()' in gcFunctions)
--- a/js/src/devtools/rootAnalysis/t/testlib.py
+++ b/js/src/devtools/rootAnalysis/t/testlib.py
@@ -5,30 +5,30 @@ import subprocess
 
 from sixgill import Body
 from collections import defaultdict, namedtuple
 
 scriptdir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
 
 HazardSummary = namedtuple('HazardSummary', ['function', 'variable', 'type', 'GCFunction', 'location'])
 
-
 def equal(got, expected):
     if got != expected:
         print("Got '%s', expected '%s'" % (got, expected))
 
 def extract_unmangled(func):
     return func.split('$')[-1]
 
 
 class Test(object):
-    def __init__(self, indir, outdir, cfg):
+    def __init__(self, indir, outdir, cfg, verbose=0):
         self.indir = indir
         self.outdir = outdir
         self.cfg = cfg
+        self.verbose = verbose
 
     def infile(self, path):
         return os.path.join(self.indir, path)
 
     def binpath(self, prog):
         return os.path.join(self.cfg.sixgill_bin, prog)
 
     def compile(self, source, options = ''):
@@ -39,34 +39,36 @@ class Test(object):
         if self.cfg.verbose:
             print("Running %s" % cmd)
         subprocess.check_call(["sh", "-c", cmd])
 
     def load_db_entry(self, dbname, pattern):
         '''Look up an entry from an XDB database file, 'pattern' may be an exact
         matching string, or an re pattern object matching a single entry.'''
 
-        if not isinstance(pattern, basestring):
-            output = subprocess.check_output([self.binpath("xdbkeys"), dbname + ".xdb"])
-            matches = filter(lambda _: re.search(pattern, _), output.splitlines())
+        if hasattr(pattern, 'match'):
+            output = subprocess.check_output([self.binpath("xdbkeys"), dbname + ".xdb"],
+                                             universal_newlines=True)
+            matches = list(filter(lambda _: re.search(pattern, _), output.splitlines()))
             if len(matches) == 0:
                 raise Exception("entry not found")
             if len(matches) > 1:
                 raise Exception("multiple entries found")
             pattern = matches[0]
 
-        output = subprocess.check_output([self.binpath("xdbfind"), "-json", dbname + ".xdb", pattern])
+        output = subprocess.check_output([self.binpath("xdbfind"), "-json", dbname + ".xdb", pattern],
+                                         universal_newlines=True)
         return json.loads(output)
 
     def run_analysis_script(self, phase, upto=None):
-        file("defaults.py", "w").write('''\
+        open("defaults.py", "w").write('''\
 analysis_scriptdir = '{scriptdir}'
 sixgill_bin = '{bindir}'
 '''.format(scriptdir=scriptdir, bindir=self.cfg.sixgill_bin))
-        cmd = [os.path.join(scriptdir, "analyze.py"), phase]
+        cmd = [os.path.join(scriptdir, "analyze.py"), '-v' if self.verbose else '-q', phase]
         if upto:
             cmd += ["--upto", upto]
         cmd.append("--source=%s" % self.indir)
         cmd.append("--objdir=%s" % self.outdir)
         cmd.append("--js=%s" % self.cfg.js)
         if self.cfg.verbose:
             cmd.append("--verbose")
             print("Running " + " ".join(cmd))
@@ -75,18 +77,18 @@ sixgill_bin = '{bindir}'
     def computeGCTypes(self):
         self.run_analysis_script("gcTypes", upto="gcTypes")
 
     def computeHazards(self):
         self.run_analysis_script("gcTypes")
 
     def load_text_file(self, filename, extract=lambda l: l):
         fullpath = os.path.join(self.outdir, filename)
-        values = (extract(line.strip()) for line in file(fullpath))
-        return filter(lambda _: _ is not None, values)
+        values = (extract(line.strip()) for line in open(fullpath, "r"))
+        return list(filter(lambda _: _ is not None, values))
 
     def load_suppressed_functions(self):
         return set(self.load_text_file("suppressedFunctions.lst"))
 
     def load_gcTypes(self):
         def grab_type(line):
             m = re.match(r'^(GC\w+): (.*)', line)
             if m:
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -1102,16 +1102,19 @@ static_assert(sizeof(Chunk) == ChunkSize
 static_assert(js::gc::ChunkMarkBitmapOffset == offsetof(Chunk, bitmap),
               "The hardcoded API bitmap offset must match the actual offset.");
 static_assert(js::gc::ChunkRuntimeOffset == offsetof(Chunk, trailer) +
                                             offsetof(ChunkTrailer, runtime),
               "The hardcoded API runtime offset must match the actual offset.");
 static_assert(js::gc::ChunkLocationOffset == offsetof(Chunk, trailer) +
                                              offsetof(ChunkTrailer, location),
               "The hardcoded API location offset must match the actual offset.");
+static_assert(js::gc::ChunkStoreBufferOffset == offsetof(Chunk, trailer) +
+                                                offsetof(ChunkTrailer, storeBuffer),
+              "The hardcoded API storeBuffer offset must match the actual offset.");
 
 /*
  * Tracks the used sizes for owned heap data and automatically maintains the
  * memory usage relationship between GCRuntime and Zones.
  */
 class HeapUsage
 {
     /*
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2971,33 +2971,20 @@ js::TenuringTracer::moveObjectToTenured(
         NativeObject* nsrc = &src->as<NativeObject>();
         tenuredSize += moveSlotsToTenured(ndst, nsrc, dstKind);
         tenuredSize += moveElementsToTenured(ndst, nsrc, dstKind);
 
         // There is a pointer into a dictionary mode object from the head of its
         // shape list. This is updated in Nursery::sweepDictionaryModeObjects().
     }
 
-    if (src->is<InlineTypedObject>()) {
-        InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
-    } else if (src->is<TypedArrayObject>()) {
-        tenuredSize += TypedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
-    } else if (src->is<UnboxedArrayObject>()) {
-        tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
-    } else if (src->is<ArgumentsObject>()) {
-        tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src);
-    } else if (src->is<ProxyObject>()) {
-        // Objects in the nursery are never swapped so the proxy must have an
-        // inline ProxyValueArray.
-        MOZ_ASSERT(src->as<ProxyObject>().usingInlineValueArray());
-        dst->as<ProxyObject>().setInlineValueArray();
-        if (JSObjectMovedOp op = dst->getClass()->extObjectMovedOp())
-            op(dst, src);
-    } else if (JSObjectMovedOp op = dst->getClass()->extObjectMovedOp()) {
-        op(dst, src);
+    JSObjectMovedOp op = dst->getClass()->extObjectMovedOp();
+    MOZ_ASSERT_IF(src->is<ProxyObject>(), op == proxy_ObjectMoved);
+    if (op) {
+        tenuredSize += op(dst, src);
     } else {
         MOZ_ASSERT_IF(src->getClass()->hasFinalize(),
                       CanNurseryAllocateFinalizedClass(src->getClass()));
     }
 
     return tenuredSize;
 }
 
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -212,17 +212,22 @@ class Nursery
      * returns false and leaves |*ref| unset.
      */
     MOZ_ALWAYS_INLINE MOZ_MUST_USE static bool getForwardedPointer(JSObject** ref);
 
     /* Forward a slots/elements pointer stored in an Ion frame. */
     void forwardBufferPointer(HeapSlot** pSlotsElems);
 
     void maybeSetForwardingPointer(JSTracer* trc, void* oldData, void* newData, bool direct) {
-        if (trc->isTenuringTracer() && isInside(oldData))
+        if (trc->isTenuringTracer())
+            setForwardingPointerWhileTenuring(oldData, newData, direct);
+    }
+
+    void setForwardingPointerWhileTenuring(void* oldData, void* newData, bool direct) {
+        if (isInside(oldData))
             setForwardingPointer(oldData, newData, direct);
     }
 
     /* Mark a malloced buffer as no longer needing to be freed. */
     void removeMallocedBuffer(void* buffer) {
         mallocedBuffers.remove(buffer);
     }
 
--- a/js/src/gc/Policy.h
+++ b/js/src/gc/Policy.h
@@ -121,16 +121,19 @@ struct InternalGCPointerPolicy {
     using Type = typename mozilla::RemovePointer<T>::Type;
     static T initial() { return nullptr; }
     static void preBarrier(T v) { Type::writeBarrierPre(v); }
     static void postBarrier(T* vp, T prev, T next) { Type::writeBarrierPost(vp, prev, next); }
     static void readBarrier(T v) { Type::readBarrier(v); }
     static void trace(JSTracer* trc, T* vp, const char* name) {
         TraceManuallyBarrieredEdge(trc, vp, name);
     }
+    static bool isValid(T v) {
+        return gc::IsCellPointerValid(v);
+    }
 };
 
 } // namespace js
 
 namespace JS {
 
 #define DEFINE_INTERNAL_GC_POLICY(type) \
     template <> struct GCPolicy<type> : public js::InternalGCPointerPolicy<type> {};
--- a/js/src/irregexp/RegExpMacroAssembler.cpp
+++ b/js/src/irregexp/RegExpMacroAssembler.cpp
@@ -35,16 +35,18 @@
 using namespace js;
 using namespace js::irregexp;
 
 template <typename CharT>
 int
 irregexp::CaseInsensitiveCompareStrings(const CharT* substring1, const CharT* substring2,
 					size_t byteLength)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     MOZ_ASSERT(byteLength % sizeof(CharT) == 0);
     size_t length = byteLength / sizeof(CharT);
 
     for (size_t i = 0; i < length; i++) {
         char16_t c1 = substring1[i];
         char16_t c2 = substring2[i];
         if (c1 != c2) {
             c1 = unicode::ToLowerCase(c1);
@@ -65,16 +67,18 @@ template int
 irregexp::CaseInsensitiveCompareStrings(const char16_t* substring1, const char16_t* substring2,
 					size_t byteLength);
 
 template <typename CharT>
 int
 irregexp::CaseInsensitiveCompareUCStrings(const CharT* substring1, const CharT* substring2,
                                           size_t byteLength)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     MOZ_ASSERT(byteLength % sizeof(CharT) == 0);
     size_t length = byteLength / sizeof(CharT);
 
     for (size_t i = 0; i < length; i++) {
         char16_t c1 = substring1[i];
         char16_t c2 = substring2[i];
         if (c1 != c2) {
             c1 = unicode::FoldCase(c1);
--- a/js/src/irregexp/RegExpStack.cpp
+++ b/js/src/irregexp/RegExpStack.cpp
@@ -42,16 +42,17 @@ RegExpStackScope::RegExpStackScope(JSCon
 RegExpStackScope::~RegExpStackScope()
 {
     regexp_stack->reset();
 }
 
 int
 irregexp::GrowBacktrackStack(JSRuntime* rt)
 {
+    AutoUnsafeCallWithABI unsafe;
     return TlsContext.get()->regexpStack.ref().grow();
 }
 
 RegExpStack::RegExpStack()
   : base_(nullptr), size(0), limit_(nullptr)
 {}
 
 RegExpStack::~RegExpStack()
--- a/js/src/jit-test/tests/wasm/integer.js
+++ b/js/src/jit-test/tests/wasm/integer.js
@@ -148,16 +148,21 @@ testComparison32('lt_s', 40, 40, 0);
 testComparison32('lt_u', 40, 40, 0);
 testComparison32('le_s', 40, 40, 1);
 testComparison32('le_u', 40, 40, 1);
 testComparison32('gt_s', 40, 40, 0);
 testComparison32('gt_u', 40, 40, 0);
 testComparison32('ge_s', 40, 40, 1);
 testComparison32('ge_u', 40, 40, 1);
 
+// On 32-bit debug builds, with --ion-eager, this test can run into our
+// per-process JIT code limits and OOM. Trigger a GC to discard code.
+if (getJitCompilerOptions()["ion.warmup.trigger"] === 0)
+    gc();
+
 // Test MTest's GVN branch inversion.
 var testTrunc = wasmEvalText(`(module (func (param f32) (result i32) (if i32 (i32.eqz (i32.trunc_s/f32 (get_local 0))) (i32.const 0) (i32.const 1))) (export "" 0))`).exports[""];
 assertEq(testTrunc(0), 0);
 assertEq(testTrunc(13.37), 1);
 
 {
     setJitCompilerOption('wasm.test-mode', 1);
 
--- a/js/src/jit/Bailouts.cpp
+++ b/js/src/jit/Bailouts.cpp
@@ -24,16 +24,18 @@
 using namespace js;
 using namespace js::jit;
 
 using mozilla::IsInRange;
 
 uint32_t
 jit::Bailout(BailoutStack* sp, BaselineBailoutInfo** bailoutInfo)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     JSContext* cx = TlsContext.get();
     MOZ_ASSERT(bailoutInfo);
 
     // We don't have an exit frame.
     MOZ_ASSERT(IsInRange(FAKE_EXITFP_FOR_BAILOUT, 0, 0x1000) &&
                IsInRange(FAKE_EXITFP_FOR_BAILOUT + sizeof(CommonFrameLayout), 0, 0x1000),
                "Fake exitfp pointer should be within the first page.");
 
@@ -99,16 +101,18 @@ jit::Bailout(BailoutStack* sp, BaselineB
 
     return retval;
 }
 
 uint32_t
 jit::InvalidationBailout(InvalidationBailoutStack* sp, size_t* frameSizeOut,
                          BaselineBailoutInfo** bailoutInfo)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     sp->checkInvariants();
 
     JSContext* cx = TlsContext.get();
 
     // We don't have an exit frame.
     cx->activation()->asJit()->setExitFP(FAKE_EXITFP_FOR_BAILOUT);
 
     JitActivationIterator jitActivations(cx);
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -983,16 +983,17 @@ EmitBranchIsReturningFromCallVM(MacroAss
     EmitBranchICEntryKind(masm, entry, ICEntry::Kind_WarmupCounter, label);
     EmitBranchICEntryKind(masm, entry, ICEntry::Kind_StackCheck, label);
     EmitBranchICEntryKind(masm, entry, ICEntry::Kind_EarlyStackCheck, label);
 }
 
 static void
 SyncBaselineDebugModeOSRInfo(BaselineFrame* frame, Value* vp, bool rv)
 {
+    AutoUnsafeCallWithABI unsafe;
     BaselineDebugModeOSRInfo* info = frame->debugModeOSRInfo();
     MOZ_ASSERT(info);
     MOZ_ASSERT(frame->script()->baselineScript()->containsCodeAddress(info->resumeAddr));
 
     if (HasForcedReturn(info, rv)) {
         // Load the frame's rval and overwrite the resume address to go to the
         // epilogue.
         MOZ_ASSERT(R0 == JSReturnOperand);
@@ -1017,16 +1018,17 @@ SyncBaselineDebugModeOSRInfo(BaselineFra
 
     // Scale stackAdjust.
     info->stackAdjust *= sizeof(Value);
 }
 
 static void
 FinishBaselineDebugModeOSR(BaselineFrame* frame)
 {
+    AutoUnsafeCallWithABI unsafe;
     frame->deleteDebugModeOSRInfo();
 
     // We will return to JIT code now so we have to clear the override pc.
     frame->clearOverridePc();
 }
 
 void
 BaselineFrame::deleteDebugModeOSRInfo()
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -1586,22 +1586,24 @@ CreateDependentString::generate(MacroAss
     }
 
     masm.bind(&done);
 }
 
 static void*
 AllocateString(JSContext* cx)
 {
+    AutoUnsafeCallWithABI unsafe;
     return js::Allocate<JSString, NoGC>(cx);
 }
 
 static void*
 AllocateFatInlineString(JSContext* cx)
 {
+    AutoUnsafeCallWithABI unsafe;
     return js::Allocate<JSFatInlineString, NoGC>(cx);
 }
 
 void
 CreateDependentString::generateFallback(MacroAssembler& masm, LiveRegisterSet regsToSave)
 {
     regsToSave.take(string_);
     regsToSave.take(temp_);
@@ -1624,16 +1626,17 @@ CreateDependentString::generateFallback(
 
         masm.jump(&joins_[kind]);
     }
 }
 
 static void*
 CreateMatchResultFallbackFunc(JSContext* cx, gc::AllocKind kind, size_t nDynamicSlots)
 {
+    AutoUnsafeCallWithABI unsafe;
     return js::Allocate<JSObject, NoGC>(cx, kind, nDynamicSlots, gc::DefaultHeap,
                                         &ArrayObject::class_);
 }
 
 static void
 CreateMatchResultFallback(MacroAssembler& masm, LiveRegisterSet regsToSave,
                           Register object, Register temp2, Register temp5,
                           ArrayObject* templateObj, Label* fail)
@@ -3995,17 +3998,18 @@ CodeGenerator::visitCallNative(LCallNati
     masm.passABIArg(argUintNReg);
     masm.passABIArg(argVpReg);
     JSNative native = target->native();
     if (call->ignoresReturnValue()) {
         const JSJitInfo* jitInfo = target->jitInfo();
         if (jitInfo && jitInfo->type() == JSJitInfo::IgnoresReturnValueNative)
             native = jitInfo->ignoresReturnValueMethod;
     }
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, native));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, native), MoveOp::GENERAL,
+                     CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     emitTracelogStopEvent(TraceLogger_Call);
 
     // Test for failure.
     masm.branchIfFalseBool(ReturnReg, masm.failureLabel());
 
     // Load the outparam vp[0] into output register(s).
     masm.loadValue(Address(masm.getStackPointer(), NativeExitFrameLayout::offsetOfResult()), JSReturnOperand);
@@ -4118,17 +4122,18 @@ CodeGenerator::visitCallDOMNative(LCallD
 
     // Construct and execute call.
     masm.setupUnalignedABICall(argJSContext);
     masm.loadJSContext(argJSContext);
     masm.passABIArg(argJSContext);
     masm.passABIArg(argObj);
     masm.passABIArg(argPrivate);
     masm.passABIArg(argArgs);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->jitInfo()->method));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->jitInfo()->method), MoveOp::GENERAL,
+                     CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     if (target->jitInfo()->isInfallible) {
         masm.loadValue(Address(masm.getStackPointer(), IonDOMMethodExitFrameLayout::offsetOfResult()),
                        JSReturnOperand);
     } else {
         // Test for failure.
         masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
@@ -7054,25 +7059,34 @@ CodeGenerator::visitMathFunctionF(LMathF
     Register temp = ToRegister(ins->temp());
     FloatRegister input = ToFloatRegister(ins->input());
     MOZ_ASSERT(ToFloatRegister(ins->output()) == ReturnFloat32Reg);
 
     masm.setupUnalignedABICall(temp);
     masm.passABIArg(input, MoveOp::FLOAT32);
 
     void* funptr = nullptr;
+    CheckUnsafeCallWithABI check = CheckUnsafeCallWithABI::Check;
     switch (ins->mir()->function()) {
-      case MMathFunction::Floor: funptr = JS_FUNC_TO_DATA_PTR(void*, floorf);           break;
-      case MMathFunction::Round: funptr = JS_FUNC_TO_DATA_PTR(void*, math_roundf_impl); break;
-      case MMathFunction::Ceil:  funptr = JS_FUNC_TO_DATA_PTR(void*, ceilf);            break;
+      case MMathFunction::Floor:
+        funptr = JS_FUNC_TO_DATA_PTR(void*, floorf);
+        check = CheckUnsafeCallWithABI::DontCheckOther;
+        break;
+      case MMathFunction::Round:
+        funptr = JS_FUNC_TO_DATA_PTR(void*, math_roundf_impl);
+        break;
+      case MMathFunction::Ceil:
+        funptr = JS_FUNC_TO_DATA_PTR(void*, ceilf);
+        check = CheckUnsafeCallWithABI::DontCheckOther;
+        break;
       default:
         MOZ_CRASH("Unknown or unsupported float32 math function");
     }
 
-    masm.callWithABI(funptr, MoveOp::FLOAT32);
+    masm.callWithABI(funptr, MoveOp::FLOAT32, check);
 }
 
 void
 CodeGenerator::visitModD(LModD* ins)
 {
     FloatRegister lhs = ToFloatRegister(ins->lhs());
     FloatRegister rhs = ToFloatRegister(ins->rhs());
 
@@ -7955,17 +7969,18 @@ JitRuntime::generateFreeStub(JSContext* 
     LiveRegisterSet save(regs.asLiveSet());
     masm.PushRegsInMask(save);
 
     const Register regTemp = regs.takeAnyGeneral();
     MOZ_ASSERT(regTemp != regSlots);
 
     masm.setupUnalignedABICall(regTemp);
     masm.passABIArg(regSlots);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, js_free));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, js_free), MoveOp::GENERAL,
+                     CheckUnsafeCallWithABI::DontCheckOther);
 
     masm.PopRegsInMask(save);
 
     masm.ret();
 
     Linker linker(masm);
     AutoFlushICache afc("FreeStub");
     JitCode* code = linker.newCode<NoGC>(cx, OTHER_CODE);
@@ -7993,17 +8008,18 @@ JitRuntime::generateLazyLinkStub(JSConte
     Register temp0 = regs.takeAny();
 
     masm.loadJSContext(temp0);
     masm.enterFakeExitFrame(temp0, temp0, ExitFrameToken::LazyLink);
     masm.PushStubCode();
 
     masm.setupUnalignedABICall(temp0);
     masm.passABIArg(temp0);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, LazyLinkTopActivation));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, LazyLinkTopActivation), MoveOp::GENERAL,
+                     CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     masm.leaveExitFrame(/* stub code */ sizeof(JitCode*));
 
 #ifdef JS_USE_LINK_REGISTER
     // Restore the return address such that the emitPrologue function of the
     // CodeGenerator can push it back on the stack with pushReturnAddress.
     masm.popReturnAddress();
 #endif
@@ -11794,17 +11810,18 @@ CodeGenerator::visitGetDOMProperty(LGetD
     markSafepointAt(safepointOffset, ins);
 
     masm.setupUnalignedABICall(JSContextReg);
     masm.loadJSContext(JSContextReg);
     masm.passABIArg(JSContextReg);
     masm.passABIArg(ObjectReg);
     masm.passABIArg(PrivateReg);
     masm.passABIArg(ValueReg);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ins->mir()->fun()));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ins->mir()->fun()), MoveOp::GENERAL,
+                     CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     if (ins->mir()->isInfallible()) {
         masm.loadValue(Address(masm.getStackPointer(), IonDOMExitFrameLayout::offsetOfResult()),
                        JSReturnOperand);
     } else {
         masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
         masm.loadValue(Address(masm.getStackPointer(), IonDOMExitFrameLayout::offsetOfResult()),
@@ -11892,17 +11909,18 @@ CodeGenerator::visitSetDOMProperty(LSetD
     markSafepointAt(safepointOffset, ins);
 
     masm.setupUnalignedABICall(JSContextReg);
     masm.loadJSContext(JSContextReg);
     masm.passABIArg(JSContextReg);
     masm.passABIArg(ObjectReg);
     masm.passABIArg(PrivateReg);
     masm.passABIArg(ValueReg);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ins->mir()->fun()));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ins->mir()->fun()), MoveOp::GENERAL,
+                     CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
     masm.adjustStack(IonDOMExitFrameLayout::Size());
 
     MOZ_ASSERT(masm.framePushed() == initialStack);
 }
 
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -1073,17 +1073,18 @@ IonCacheIRCompiler::emitCallNativeGetter
         return false;
     masm.enterFakeExitFrame(argJSContext, scratch, ExitFrameToken::IonOOLNative);
 
     // Construct and execute call.
     masm.setupUnalignedABICall(scratch);
     masm.passABIArg(argJSContext);
     masm.passABIArg(argUintN);
     masm.passABIArg(argVp);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()), MoveOp::GENERAL,
+                     CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     // Test for failure.
     masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
     // Load the outparam vp[0] into output register(s).
     Address outparam(masm.getStackPointer(), IonOOLNativeExitFrameLayout::offsetOfResult());
     masm.loadValue(outparam, output.valueReg());
 
@@ -1131,17 +1132,18 @@ IonCacheIRCompiler::emitCallProxyGetResu
     masm.enterFakeExitFrame(argJSContext, scratch, ExitFrameToken::IonOOLProxy);
 
     // Make the call.
     masm.setupUnalignedABICall(scratch);
     masm.passABIArg(argJSContext);
     masm.passABIArg(argProxy);
     masm.passABIArg(argId);
     masm.passABIArg(argVp);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ProxyGetProperty));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ProxyGetProperty), MoveOp::GENERAL,
+                     CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     // Test for failure.
     masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
     // Load the outparam vp[0] into output register(s).
     Address outparam(masm.getStackPointer(), IonOOLProxyExitFrameLayout::offsetOfResult());
     masm.loadValue(outparam, output.valueReg());
 
@@ -1319,16 +1321,17 @@ IonCacheIRCompiler::emitCallStringSplitR
 
     masm.storeCallResultValue(output);
     return true;
 }
 
 static bool
 GroupHasPropertyTypes(ObjectGroup* group, jsid* id, Value* v)
 {
+    AutoUnsafeCallWithABI unsafe;
     if (group->unknownPropertiesDontCheckGeneration())
         return true;
     HeapTypeSet* propTypes = group->maybeGetPropertyDontCheckGeneration(*id);
     if (!propTypes)
         return true;
     if (!propTypes->nonConstantProperty())
         return false;
     return propTypes->hasType(TypeSet::GetValueType(*v));
@@ -1986,17 +1989,18 @@ IonCacheIRCompiler::emitCallNativeSetter
         return false;
     masm.enterFakeExitFrame(argJSContext, scratch, ExitFrameToken::IonOOLNative);
 
     // Make the call.
     masm.setupUnalignedABICall(scratch);
     masm.passABIArg(argJSContext);
     masm.passABIArg(argUintN);
     masm.passABIArg(argVp);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()), MoveOp::GENERAL,
+                     CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     // Test for failure.
     masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
     masm.adjustStack(IonOOLNativeExitFrameLayout::Size(1));
     return true;
 }
 
--- a/js/src/jit/MacroAssembler-inl.h
+++ b/js/src/jit/MacroAssembler-inl.h
@@ -121,18 +121,32 @@ MacroAssembler::passABIArg(Register reg)
 }
 
 void
 MacroAssembler::passABIArg(FloatRegister reg, MoveOp::Type type)
 {
     passABIArg(MoveOperand(reg), type);
 }
 
-template <typename T> void
-MacroAssembler::callWithABI(const T& fun, MoveOp::Type result)
+void
+MacroAssembler::callWithABI(void* fun, MoveOp::Type result, CheckUnsafeCallWithABI check)
+{
+    AutoProfilerCallInstrumentation profiler(*this);
+    callWithABINoProfiler(fun, result, check);
+}
+
+void
+MacroAssembler::callWithABI(Register fun, MoveOp::Type result)
+{
+    AutoProfilerCallInstrumentation profiler(*this);
+    callWithABINoProfiler(fun, result);
+}
+
+void
+MacroAssembler::callWithABI(const Address& fun, MoveOp::Type result)
 {
     AutoProfilerCallInstrumentation profiler(*this);
     callWithABINoProfiler(fun, result);
 }
 
 void
 MacroAssembler::appendSignatureType(MoveOp::Type type)
 {
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1038,17 +1038,17 @@ FindStartOfUninitializedAndUndefinedSlot
     } else {
         *startOfUninitialized = *startOfUndefined;
     }
 }
 
 static void
 AllocateObjectBufferWithInit(JSContext* cx, TypedArrayObject* obj, int32_t count)
 {
-    JS::AutoCheckCannotGC nogc(cx);
+    AutoUnsafeCallWithABI unsafe;
 
     obj->initPrivate(nullptr);
 
     // Negative numbers or zero will bail out to the slow path, which in turn will raise
     // an invalid argument exception or create a correct object with zero elements.
     if (count <= 0 || uint32_t(count) >= INT32_MAX / obj->bytesPerElement()) {
         obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(0));
         return;
@@ -1545,17 +1545,18 @@ MacroAssembler::generateBailoutTail(Regi
     branch32(Equal, ReturnReg, Imm32(BAILOUT_RETURN_OK), &baseline);
     branch32(Equal, ReturnReg, Imm32(BAILOUT_RETURN_FATAL_ERROR), exceptionLabel());
 
     // Fall-through: overrecursed.
     {
         loadJSContext(ReturnReg);
         setupUnalignedABICall(scratch);
         passABIArg(ReturnReg);
-        callWithABI(JS_FUNC_TO_DATA_PTR(void*, BailoutReportOverRecursed));
+        callWithABI(JS_FUNC_TO_DATA_PTR(void*, BailoutReportOverRecursed), MoveOp::GENERAL,
+                    CheckUnsafeCallWithABI::DontCheckHasExitFrame);
         jump(exceptionLabel());
     }
 
     bind(&baseline);
     {
         // Prepare a register set for use in this case.
         AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
         MOZ_ASSERT(!regs.has(getStackPointer()));
@@ -1610,17 +1611,18 @@ MacroAssembler::generateBailoutTail(Regi
             pushValue(Address(bailoutInfo, offsetof(BaselineBailoutInfo, valueR0)));
             push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeFramePtr)));
             push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeAddr)));
             push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, monitorStub)));
 
             // Call a stub to free allocated memory and create arguments objects.
             setupUnalignedABICall(temp);
             passABIArg(bailoutInfo);
-            callWithABI(JS_FUNC_TO_DATA_PTR(void*, FinishBailoutToBaseline));
+            callWithABI(JS_FUNC_TO_DATA_PTR(void*, FinishBailoutToBaseline),
+                        MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
             branchTest32(Zero, ReturnReg, ReturnReg, exceptionLabel());
 
             // Restore values where they need to be and resume execution.
             AllocatableGeneralRegisterSet enterMonRegs(GeneralRegisterSet::All());
             enterMonRegs.take(R0);
             enterMonRegs.take(ICStubReg);
             enterMonRegs.take(BaselineFrameReg);
             enterMonRegs.takeUnchecked(ICTailCallReg);
@@ -1648,17 +1650,18 @@ MacroAssembler::generateBailoutTail(Regi
             pushValue(Address(bailoutInfo, offsetof(BaselineBailoutInfo, valueR0)));
             pushValue(Address(bailoutInfo, offsetof(BaselineBailoutInfo, valueR1)));
             push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeFramePtr)));
             push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeAddr)));
 
             // Call a stub to free allocated memory and create arguments objects.
             setupUnalignedABICall(temp);
             passABIArg(bailoutInfo);
-            callWithABI(JS_FUNC_TO_DATA_PTR(void*, FinishBailoutToBaseline));
+            callWithABI(JS_FUNC_TO_DATA_PTR(void*, FinishBailoutToBaseline),
+                        MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
             branchTest32(Zero, ReturnReg, ReturnReg, exceptionLabel());
 
             // Restore values where they need to be and resume execution.
             AllocatableGeneralRegisterSet enterRegs(GeneralRegisterSet::All());
             enterRegs.take(R0);
             enterRegs.take(R1);
             enterRegs.take(BaselineFrameReg);
             Register jitcodeReg = enterRegs.takeAny();
@@ -1724,17 +1727,19 @@ MacroAssembler::assumeUnreachable(const 
         AllocatableRegisterSet regs(RegisterSet::Volatile());
         LiveRegisterSet save(regs.asLiveSet());
         PushRegsInMask(save);
         Register temp = regs.takeAnyGeneral();
 
         setupUnalignedABICall(temp);
         movePtr(ImmPtr(output), temp);
         passABIArg(temp);
-        callWithABI(JS_FUNC_TO_DATA_PTR(void*, AssumeUnreachable_));
+        callWithABI(JS_FUNC_TO_DATA_PTR(void*, AssumeUnreachable_),
+                    MoveOp::GENERAL,
+                    CheckUnsafeCallWithABI::DontCheckOther);
 
         PopRegsInMask(save);
     }
 #endif
 
     breakpoint();
 }
 
@@ -1748,17 +1753,20 @@ MacroAssembler::assertTestInt32(Conditio
     assumeUnreachable(output);
     bind(&ok);
 #endif
 }
 
 template void MacroAssembler::assertTestInt32(Condition, const Address&, const char*);
 
 static void
-Printf0_(const char* output) {
+Printf0_(const char* output)
+{
+    AutoUnsafeCallWithABI unsafe;
+
     // Use stderr instead of stdout because this is only used for debug
     // output. stderr is less likely to interfere with the program's normal
     // output, and it's always unbuffered.
     fprintf(stderr, "%s", output);
 }
 
 void
 MacroAssembler::printf(const char* output)
@@ -1773,17 +1781,19 @@ MacroAssembler::printf(const char* outpu
     movePtr(ImmPtr(output), temp);
     passABIArg(temp);
     callWithABI(JS_FUNC_TO_DATA_PTR(void*, Printf0_));
 
     PopRegsInMask(save);
 }
 
 static void
-Printf1_(const char* output, uintptr_t value) {
+Printf1_(const char* output, uintptr_t value)
+{
+    AutoUnsafeCallWithABI unsafe;
     AutoEnterOOMUnsafeRegion oomUnsafe;
     js::UniqueChars line = JS_sprintf_append(nullptr, output, value);
     if (!line)
         oomUnsafe.crash("OOM at masm.printf");
     fprintf(stderr, "%s", line.get());
 }
 
 void
@@ -1819,17 +1829,18 @@ MacroAssembler::tracelogStartId(Register
     regs.takeUnchecked(logger);
 
     Register temp = regs.takeAnyGeneral();
 
     setupUnalignedABICall(temp);
     passABIArg(logger);
     move32(Imm32(textId), temp);
     passABIArg(temp);
-    callWithABI(JS_FUNC_TO_DATA_PTR(void*, TraceLogStartEventPrivate));
+    callWithABI(JS_FUNC_TO_DATA_PTR(void*, TraceLogStartEventPrivate), MoveOp::GENERAL,
+                CheckUnsafeCallWithABI::DontCheckOther);
 
     PopRegsInMask(save);
 }
 
 void
 MacroAssembler::tracelogStartId(Register logger, Register textId)
 {
     AllocatableRegisterSet regs(RegisterSet::Volatile());
@@ -1838,17 +1849,18 @@ MacroAssembler::tracelogStartId(Register
     regs.takeUnchecked(logger);
     regs.takeUnchecked(textId);
 
     Register temp = regs.takeAnyGeneral();
 
     setupUnalignedABICall(temp);
     passABIArg(logger);
     passABIArg(textId);
-    callWithABI(JS_FUNC_TO_DATA_PTR(void*, TraceLogStartEventPrivate));
+    callWithABI(JS_FUNC_TO_DATA_PTR(void*, TraceLogStartEventPrivate), MoveOp::GENERAL,
+                CheckUnsafeCallWithABI::DontCheckOther);
 
     PopRegsInMask(save);
 }
 
 void
 MacroAssembler::tracelogStartEvent(Register logger, Register event)
 {
     void (&TraceLogFunc)(TraceLoggerThread*, const TraceLoggerEvent&) = TraceLogStartEvent;
@@ -1859,17 +1871,18 @@ MacroAssembler::tracelogStartEvent(Regis
     regs.takeUnchecked(logger);
     regs.takeUnchecked(event);
 
     Register temp = regs.takeAnyGeneral();
 
     setupUnalignedABICall(temp);
     passABIArg(logger);
     passABIArg(event);
-    callWithABI(JS_FUNC_TO_DATA_PTR(void*, TraceLogFunc));
+    callWithABI(JS_FUNC_TO_DATA_PTR(void*, TraceLogFunc), MoveOp::GENERAL,
+                CheckUnsafeCallWithABI::DontCheckOther);
 
     PopRegsInMask(save);
 }
 
 void
 MacroAssembler::tracelogStopId(Register logger, uint32_t textId, bool force)
 {
     if (!force && !TraceLogTextIdEnabled(textId))
@@ -1882,17 +1895,18 @@ MacroAssembler::tracelogStopId(Register 
 
     Register temp = regs.takeAnyGeneral();
 
     setupUnalignedABICall(temp);
     passABIArg(logger);
     move32(Imm32(textId), temp);
     passABIArg(temp);
 
-    callWithABI(JS_FUNC_TO_DATA_PTR(void*, TraceLogStopEventPrivate));
+    callWithABI(JS_FUNC_TO_DATA_PTR(void*, TraceLogStopEventPrivate), MoveOp::GENERAL,
+                CheckUnsafeCallWithABI::DontCheckOther);
 
     PopRegsInMask(save);
 }
 
 void
 MacroAssembler::tracelogStopId(Register logger, Register textId)
 {
     AllocatableRegisterSet regs(RegisterSet::Volatile());
@@ -1901,17 +1915,18 @@ MacroAssembler::tracelogStopId(Register 
     regs.takeUnchecked(logger);
     regs.takeUnchecked(textId);
 
     Register temp = regs.takeAnyGeneral();
 
     setupUnalignedABICall(temp);
     passABIArg(logger);
     passABIArg(textId);
-    callWithABI(JS_FUNC_TO_DATA_PTR(void*, TraceLogStopEventPrivate));
+    callWithABI(JS_FUNC_TO_DATA_PTR(void*, TraceLogStopEventPrivate), MoveOp::GENERAL,
+                CheckUnsafeCallWithABI::DontCheckOther);
 
     PopRegsInMask(save);
 }
 #endif
 
 void
 MacroAssembler::convertInt32ValueToDouble(const Address& address, Register scratch, Label* done)
 {
@@ -2102,17 +2117,18 @@ MacroAssembler::outOfLineTruncateSlow(Fl
 
     if (compilingWasm) {
         setupWasmABICall();
         passABIArg(src, MoveOp::DOUBLE);
         callWithABI(callOffset, wasm::SymbolicAddress::ToInt32);
     } else {
         setupUnalignedABICall(dest);
         passABIArg(src, MoveOp::DOUBLE);
-        callWithABI(mozilla::BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32));
+        callWithABI(mozilla::BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32),
+                    MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckOther);
     }
     storeCallWordResult(dest);
 
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
     defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     // Nothing
 #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
     if (widenFloatToDouble)
@@ -2796,27 +2812,52 @@ MacroAssembler::passABIArg(const MoveOpe
         return;
 
     if (oom())
         return;
     propagateOOM(moveResolver_.addMove(from, to, type));
 }
 
 void
-MacroAssembler::callWithABINoProfiler(void* fun, MoveOp::Type result)
+MacroAssembler::callWithABINoProfiler(void* fun, MoveOp::Type result, CheckUnsafeCallWithABI check)
 {
     appendSignatureType(result);
 #ifdef JS_SIMULATOR
     fun = Simulator::RedirectNativeFunction(fun, signature());
 #endif
 
     uint32_t stackAdjust;
     callWithABIPre(&stackAdjust);
+
+#ifdef DEBUG
+    if (check == CheckUnsafeCallWithABI::Check) {
+        push(ReturnReg);
+        loadJSContext(ReturnReg);
+        Address flagAddr(ReturnReg, JSContext::offsetOfInUnsafeCallWithABI());
+        store32(Imm32(1), flagAddr);
+        pop(ReturnReg);
+    }
+#endif
+
     call(ImmPtr(fun));
+
     callWithABIPost(stackAdjust, result);
+
+#ifdef DEBUG
+    if (check == CheckUnsafeCallWithABI::Check) {
+        Label ok;
+        push(ReturnReg);
+        loadJSContext(ReturnReg);
+        Address flagAddr(ReturnReg, JSContext::offsetOfInUnsafeCallWithABI());
+        branch32(Assembler::Equal, flagAddr, Imm32(0), &ok);
+        assumeUnreachable("callWithABI: callee did not use AutoInUnsafeCallWithABI");
+        bind(&ok);
+        pop(ReturnReg);
+    }
+#endif
 }
 
 void
 MacroAssembler::callWithABI(wasm::BytecodeOffset callOffset, wasm::SymbolicAddress imm,
                             MoveOp::Type result)
 {
     MOZ_ASSERT(wasm::NeedsBuiltinThunk(imm));
 
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -188,16 +188,30 @@ using mozilla::FloatingPoint;
 namespace js {
 namespace jit {
 
 // Defined in JitFrames.h
 enum class ExitFrameToken : uint8_t;
 
 class AutoSaveLiveRegisters;
 
+enum class CheckUnsafeCallWithABI {
+    // Require the callee to use AutoUnsafeCallWithABI.
+    Check,
+
+    // We pushed an exit frame so this callWithABI can safely GC and walk the
+    // stack.
+    DontCheckHasExitFrame,
+
+    // Don't check this callWithABI uses AutoUnsafeCallWithABI, for instance
+    // because we're calling a simple helper function (like malloc or js_free)
+    // that we can't change and/or that we know won't GC.
+    DontCheckOther,
+};
+
 // The public entrypoint for emitting assembly. Note that a MacroAssembler can
 // use cx->lifoAlloc, so take care not to interleave masm use with other
 // lifoAlloc use if one will be destroyed before the other.
 class MacroAssembler : public MacroAssemblerSpecific
 {
     MacroAssembler* thisFromCtor() {
         return this;
     }
@@ -560,32 +574,34 @@ class MacroAssembler : public MacroAssem
     // temporarily use more stack, in which case esp-relative addresses will be
     // automatically adjusted. It is extremely important that esp-relative
     // addresses are computed *after* setupABICall(). Furthermore, no
     // operations should be emitted while setting arguments.
     void passABIArg(const MoveOperand& from, MoveOp::Type type);
     inline void passABIArg(Register reg);
     inline void passABIArg(FloatRegister reg, MoveOp::Type type);
 
-    template <typename T>
-    inline void callWithABI(const T& fun, MoveOp::Type result = MoveOp::GENERAL);
+    inline void callWithABI(void* fun, MoveOp::Type result = MoveOp::GENERAL,
+                            CheckUnsafeCallWithABI check = CheckUnsafeCallWithABI::Check);
+    inline void callWithABI(Register fun, MoveOp::Type result = MoveOp::GENERAL);
+    inline void callWithABI(const Address& fun, MoveOp::Type result = MoveOp::GENERAL);
 
     void callWithABI(wasm::BytecodeOffset offset, wasm::SymbolicAddress fun,
                      MoveOp::Type result = MoveOp::GENERAL);
 
   private:
     // Reinitialize the variables which have to be cleared before making a call
     // with callWithABI.
     void setupABICall();
 
     // Reserve the stack and resolve the arguments move.
     void callWithABIPre(uint32_t* stackAdjust, bool callFromWasm = false) PER_ARCH;
 
     // Emits a call to a C/C++ function, resolving all argument moves.
-    void callWithABINoProfiler(void* fun, MoveOp::Type result);
+    void callWithABINoProfiler(void* fun, MoveOp::Type result, CheckUnsafeCallWithABI check);
     void callWithABINoProfiler(Register fun, MoveOp::Type result) PER_ARCH;
     void callWithABINoProfiler(const Address& fun, MoveOp::Type result) PER_ARCH;
 
     // Restore the stack to its state before the setup function call.
     void callWithABIPost(uint32_t stackAdjust, MoveOp::Type result, bool callFromWasm = false) PER_ARCH;
 
     // Create the signature to be able to decode the arguments of a native
     // function, when calling a function within the simulator.
--- a/js/src/jit/RematerializedFrame.h
+++ b/js/src/jit/RematerializedFrame.h
@@ -254,22 +254,14 @@ namespace JS {
 template <>
 struct MapTypeToRootKind<js::jit::RematerializedFrame*>
 {
     static const RootKind kind = RootKind::Traceable;
 };
 
 template <>
 struct GCPolicy<js::jit::RematerializedFrame*>
-{
-    static js::jit::RematerializedFrame* initial() {
-        return nullptr;
-    }
-
-    static void trace(JSTracer* trc, js::jit::RematerializedFrame** frame, const char* name) {
-        if (*frame)
-            (*frame)->trace(trc);
-    }
-};
+  : public NonGCPointerPolicy<js::jit::RematerializedFrame*>
+{};
 
 } // namespace JS
 
 #endif // jit_RematerializedFrame_h
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -1200,17 +1200,18 @@ ICBinaryArith_DoubleWithInt32::Compiler:
         Label truncateABICall;
         masm.branchTruncateDoubleMaybeModUint32(FloatReg0, scratchReg, &truncateABICall);
         masm.jump(&doneTruncate);
 
         masm.bind(&truncateABICall);
         masm.push(intReg);
         masm.setupUnalignedABICall(scratchReg);
         masm.passABIArg(FloatReg0, MoveOp::DOUBLE);
-        masm.callWithABI(mozilla::BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32));
+        masm.callWithABI(mozilla::BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32),
+                         MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckOther);
         masm.storeCallWordResult(scratchReg);
         masm.pop(intReg);
 
         masm.bind(&doneTruncate);
     }
 
     Register intReg2 = scratchReg;
     // All handled ops commute, so no need to worry about ordering.
@@ -1353,17 +1354,18 @@ ICUnaryArith_Double::Compiler::generateS
         Label doneTruncate;
         Label truncateABICall;
         masm.branchTruncateDoubleMaybeModUint32(FloatReg0, scratchReg, &truncateABICall);
         masm.jump(&doneTruncate);
 
         masm.bind(&truncateABICall);
         masm.setupUnalignedABICall(scratchReg);
         masm.passABIArg(FloatReg0, MoveOp::DOUBLE);
-        masm.callWithABI(BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32));
+        masm.callWithABI(BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32),
+                         MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckOther);
         masm.storeCallWordResult(scratchReg);
 
         masm.bind(&doneTruncate);
         masm.not32(scratchReg);
         masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0);
     }
 
     EmitReturnFromIC(masm);
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -539,16 +539,17 @@ InterruptCheck(JSContext* cx)
     }
 
     return CheckForInterrupt(cx);
 }
 
 void*
 MallocWrapper(JSRuntime* rt, size_t nbytes)
 {
+    AutoUnsafeCallWithABI unsafe;
     return rt->pod_malloc<uint8_t>(nbytes);
 }
 
 JSObject*
 NewCallObject(JSContext* cx, HandleShape shape, HandleObjectGroup group)
 {
     JSObject* obj = CallObject::create(cx, shape, group);
     if (!obj)
@@ -644,16 +645,18 @@ CreateThis(JSContext* cx, HandleObject c
 
 void
 GetDynamicName(JSContext* cx, JSObject* envChain, JSString* str, Value* vp)
 {
     // Lookup a string on the env chain, returning either the value found or
     // undefined through rval. This function is infallible, and cannot GC or
     // invalidate.
 
+    AutoUnsafeCallWithABI unsafe;
+
     JSAtom* atom;
     if (str->isAtom()) {
         atom = &str->asAtom();
     } else {
         atom = AtomizeString(cx, str);
         if (!atom) {
             vp->setUndefined();
             return;
@@ -674,28 +677,28 @@ GetDynamicName(JSContext* cx, JSObject* 
     }
 
     vp->setUndefined();
 }
 
 void
 PostWriteBarrier(JSRuntime* rt, JSObject* obj)
 {
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
     MOZ_ASSERT(!IsInsideNursery(obj));
     rt->gc.storeBuffer().putWholeCell(obj);
 }
 
 static const size_t MAX_WHOLE_CELL_BUFFER_SIZE = 4096;
 
 template <IndexInBounds InBounds>
 void
 PostWriteElementBarrier(JSRuntime* rt, JSObject* obj, int32_t index)
 {
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     MOZ_ASSERT(!IsInsideNursery(obj));
 
     if (InBounds == IndexInBounds::Yes) {
         MOZ_ASSERT(uint32_t(index) < obj->as<NativeObject>().getDenseInitializedLength());
     } else {
         if (MOZ_UNLIKELY(!obj->is<NativeObject>()) ||
             uint32_t(index) >= obj->as<NativeObject>().getDenseInitializedLength())
@@ -739,33 +742,33 @@ PostGlobalWriteBarrier(JSRuntime* rt, JS
         obj->compartment()->globalWriteBarriered = 1;
     }
 }
 
 int32_t
 GetIndexFromString(JSString* str)
 {
     // We shouldn't GC here as this is called directly from IC code.
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     if (!str->isFlat())
         return -1;
 
     uint32_t index;
     if (!str->asFlat().isIndex(&index) || index > INT32_MAX)
         return -1;
 
     return int32_t(index);
 }
 
 JSObject*
 WrapObjectPure(JSContext* cx, JSObject* obj)
 {
     // IC code calls this directly so we shouldn't GC.
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     MOZ_ASSERT(obj);
     MOZ_ASSERT(cx->compartment() != obj->compartment());
 
     // From: JSCompartment::getNonWrapperObjectForCurrentCompartment
     // Note that if the object is same-compartment, but has been wrapped into a
     // different compartment, we need to unwrap it and return the bare same-
     // compartment object. Note again that windows are always wrapped by a
@@ -859,16 +862,17 @@ DebugEpilogue(JSContext* cx, BaselineFra
     // builds after each callVM, to ensure this flag is not set.
     frame->clearOverridePc();
     return true;
 }
 
 void
 FrameIsDebuggeeCheck(BaselineFrame* frame)
 {
+    AutoUnsafeCallWithABI unsafe;
     if (frame->script()->isDebuggee())
         frame->setIsDebuggee();
 }
 
 JSObject*
 CreateGenerator(JSContext* cx, BaselineFrame* frame)
 {
     return GeneratorObject::create(cx, frame);
@@ -1135,16 +1139,17 @@ OnDebuggerStatement(JSContext* cx, Basel
       default:
         MOZ_CRASH("Invalid trap status");
     }
 }
 
 bool
 GlobalHasLiveOnDebuggerStatement(JSContext* cx)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cx->compartment()->isDebuggee() &&
            Debugger::hasLiveHook(cx->global(), Debugger::OnDebuggerStatement);
 }
 
 bool
 PushLexicalEnv(JSContext* cx, BaselineFrame* frame, Handle<LexicalScope*> scope)
 {
     return frame->pushLexicalEnvironment(cx, scope);
@@ -1227,16 +1232,17 @@ LeaveWith(JSContext* cx, BaselineFrame* 
     frame->popOffEnvironmentChain<WithEnvironmentObject>();
     return true;
 }
 
 bool
 InitBaselineFrameForOsr(BaselineFrame* frame, InterpreterFrame* interpFrame,
                         uint32_t numStackValues)
 {
+    AutoUnsafeCallWithABI unsafe;
     return frame->initForOsr(interpFrame, numStackValues);
 }
 
 JSObject*
 CreateDerivedTypedObj(JSContext* cx, HandleObject descr,
                       HandleObject owner, int32_t offset)
 {
     MOZ_ASSERT(descr->is<TypeDescr>());
@@ -1313,16 +1319,17 @@ void
 AutoDetectInvalidation::setReturnOverride()
 {
     cx_->setIonReturnOverride(rval_.get());
 }
 
 void
 AssertValidObjectPtr(JSContext* cx, JSObject* obj)
 {
+    AutoUnsafeCallWithABI unsafe;
 #ifdef DEBUG
     // Check what we can, so that we'll hopefully assert/crash if we get a
     // bogus object (pointer).
     MOZ_ASSERT(obj->compartment() == cx->compartment());
     MOZ_ASSERT(obj->runtimeFromActiveCooperatingThread() == cx->runtime());
 
     MOZ_ASSERT_IF(!obj->hasLazyGroup() && obj->maybeShape(),
                   obj->group()->clasp() == obj->maybeShape()->getObjectClass());
@@ -1334,23 +1341,25 @@ AssertValidObjectPtr(JSContext* cx, JSOb
         MOZ_ASSERT(obj->asTenured().zone() == cx->zone());
     }
 #endif
 }
 
 void
 AssertValidObjectOrNullPtr(JSContext* cx, JSObject* obj)
 {
+    AutoUnsafeCallWithABI unsafe;
     if (obj)
         AssertValidObjectPtr(cx, obj);
 }
 
 void
 AssertValidStringPtr(JSContext* cx, JSString* str)
 {
+    AutoUnsafeCallWithABI unsafe;
 #ifdef DEBUG
     // We can't closely inspect strings from another runtime.
     if (str->runtimeFromAnyThread() != cx->runtime()) {
         MOZ_ASSERT(str->isPermanentAtom());
         return;
     }
 
     if (str->isAtom())
@@ -1377,16 +1386,18 @@ AssertValidStringPtr(JSContext* cx, JSSt
         MOZ_ASSERT(kind == gc::AllocKind::STRING);
     }
 #endif
 }
 
 void
 AssertValidSymbolPtr(JSContext* cx, JS::Symbol* sym)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     // We can't closely inspect symbols from another runtime.
     if (sym->runtimeFromAnyThread() != cx->runtime()) {
         MOZ_ASSERT(sym->isWellKnownSymbol());
         return;
     }
 
     MOZ_ASSERT(sym->zone()->isAtomsZone());
     MOZ_ASSERT(sym->isAligned());
@@ -1396,65 +1407,73 @@ AssertValidSymbolPtr(JSContext* cx, JS::
     }
 
     MOZ_ASSERT(sym->getAllocKind() == gc::AllocKind::SYMBOL);
 }
 
 void
 AssertValidValue(JSContext* cx, Value* v)
 {
+    AutoUnsafeCallWithABI unsafe;
     if (v->isObject())
         AssertValidObjectPtr(cx, &v->toObject());
     else if (v->isString())
         AssertValidStringPtr(cx, v->toString());
     else if (v->isSymbol())
         AssertValidSymbolPtr(cx, v->toSymbol());
 }
 
 bool
 ObjectIsCallable(JSObject* obj)
 {
+    AutoUnsafeCallWithABI unsafe;
     return obj->isCallable();
 }
 
 bool
 ObjectIsConstructor(JSObject* obj)
 {
+    AutoUnsafeCallWithABI unsafe;
     return obj->isConstructor();
 }
 
 void
 MarkValueFromIon(JSRuntime* rt, Value* vp)
 {
+    AutoUnsafeCallWithABI unsafe;
     TraceManuallyBarrieredEdge(&rt->gc.marker, vp, "write barrier");
 }
 
 void
 MarkStringFromIon(JSRuntime* rt, JSString** stringp)
 {
+    AutoUnsafeCallWithABI unsafe;
     MOZ_ASSERT(*stringp);
     TraceManuallyBarrieredEdge(&rt->gc.marker, stringp, "write barrier");
 }
 
 void
 MarkObjectFromIon(JSRuntime* rt, JSObject** objp)
 {
+    AutoUnsafeCallWithABI unsafe;
     MOZ_ASSERT(*objp);
     TraceManuallyBarrieredEdge(&rt->gc.marker, objp, "write barrier");
 }
 
 void
 MarkShapeFromIon(JSRuntime* rt, Shape** shapep)
 {
+    AutoUnsafeCallWithABI unsafe;
     TraceManuallyBarrieredEdge(&rt->gc.marker, shapep, "write barrier");
 }
 
 void
 MarkObjectGroupFromIon(JSRuntime* rt, ObjectGroup** groupp)
 {
+    AutoUnsafeCallWithABI unsafe;
     TraceManuallyBarrieredEdge(&rt->gc.marker, groupp, "write barrier");
 }
 
 bool
 ThrowRuntimeLexicalError(JSContext* cx, unsigned errorNumber)
 {
     ScriptFrameIter iter(cx);
     RootedScript script(cx, iter.script());
@@ -1546,17 +1565,17 @@ CallNativeSetter(JSContext* cx, HandleFu
 
     return natfun(cx, 1, vp.begin());
 }
 
 bool
 EqualStringsHelper(JSString* str1, JSString* str2)
 {
     // IC code calls this directly so we shouldn't GC.
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     MOZ_ASSERT(str1->isAtom());
     MOZ_ASSERT(!str2->isAtom());
     MOZ_ASSERT(str1->length() == str2->length());
 
     JSLinearString* str2Linear = str2->ensureLinear(nullptr);
     if (!str2Linear)
         return false;
@@ -1576,17 +1595,17 @@ CheckIsCallable(JSContext* cx, HandleVal
 template <bool HandleMissing>
 static MOZ_ALWAYS_INLINE bool
 GetNativeDataProperty(JSContext* cx, NativeObject* obj, jsid id, Value* vp)
 {
     // Fast path used by megamorphic IC stubs. Unlike our other property
     // lookup paths, this is optimized to be as fast as possible for simple
     // data property lookups.
 
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     MOZ_ASSERT(JSID_IS_ATOM(id) || JSID_IS_SYMBOL(id));
 
     while (true) {
         if (Shape* shape = obj->lastProperty()->search(cx, id)) {
             if (!shape->hasSlot() || !shape->hasDefaultGetter())
                 return false;
 
@@ -1628,17 +1647,17 @@ template bool
 GetNativeDataProperty<true>(JSContext* cx, JSObject* obj, PropertyName* name, Value* vp);
 
 template bool
 GetNativeDataProperty<false>(JSContext* cx, JSObject* obj, PropertyName* name, Value* vp);
 
 static MOZ_ALWAYS_INLINE bool
 ValueToAtomOrSymbol(JSContext* cx, Value& idVal, jsid* id)
 {
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     if (MOZ_LIKELY(idVal.isString())) {
         JSString* s = idVal.toString();
         JSAtom* atom;
         if (s->isAtom()) {
             atom = &s->asAtom();
         } else {
             atom = AtomizeString(cx, s);
@@ -1661,17 +1680,17 @@ ValueToAtomOrSymbol(JSContext* cx, Value
 
     return true;
 }
 
 template <bool HandleMissing>
 bool
 GetNativeDataPropertyByValue(JSContext* cx, JSObject* obj, Value* vp)
 {
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     // Condition checked by caller.
     MOZ_ASSERT(obj->isNative());
 
     // vp[0] contains the id, result will be stored in vp[1].
     Value idVal = vp[0];
     jsid id;
     if (!ValueToAtomOrSymbol(cx, idVal, &id))
@@ -1686,17 +1705,17 @@ GetNativeDataPropertyByValue<true>(JSCon
 
 template bool
 GetNativeDataPropertyByValue<false>(JSContext* cx, JSObject* obj, Value* vp);
 
 template <bool NeedsTypeBarrier>
 bool
 SetNativeDataProperty(JSContext* cx, JSObject* obj, PropertyName* name, Value* val)
 {
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     if (MOZ_UNLIKELY(!obj->isNative()))
         return false;
 
     NativeObject* nobj = &obj->as<NativeObject>();
     Shape* shape = nobj->lastProperty()->search(cx, NameToId(name));
     if (!shape ||
         !shape->hasSlot() ||
@@ -1718,17 +1737,17 @@ template bool
 SetNativeDataProperty<true>(JSContext* cx, JSObject* obj, PropertyName* name, Value* val);
 
 template bool
 SetNativeDataProperty<false>(JSContext* cx, JSObject* obj, PropertyName* name, Value* val);
 
 bool
 ObjectHasGetterSetter(JSContext* cx, JSObject* objArg, Shape* propShape)
 {
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     MOZ_ASSERT(propShape->hasGetterObject() || propShape->hasSetterObject());
 
     // Window objects may require outerizing (passing the WindowProxy to the
     // getter/setter), so we don't support them here.
     if (MOZ_UNLIKELY(!objArg->isNative() || IsWindow(objArg)))
         return false;
 
@@ -1761,17 +1780,17 @@ ObjectHasGetterSetter(JSContext* cx, JSO
             return false;
         nobj = &proto->as<NativeObject>();
     }
 }
 
 bool
 HasOwnNativeDataProperty(JSContext* cx, JSObject* obj, Value* vp)
 {
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     // vp[0] contains the id, result will be stored in vp[1].
     Value idVal = vp[0];
     jsid id;
     if (!ValueToAtomOrSymbol(cx, idVal, &id))
         return false;
 
     if (!obj->isNative()) {
@@ -1798,16 +1817,17 @@ HasOwnNativeDataProperty(JSContext* cx, 
     // Missing property.
     vp[1].setBoolean(false);
     return true;
 }
 
 JSString*
 TypeOfObject(JSObject* obj, JSRuntime* rt)
 {
+    AutoUnsafeCallWithABI unsafe;
     JSType type = js::TypeOfObject(obj);
     return TypeName(type, *rt->commonNames);
 }
 
 bool
 GetPrototypeOf(JSContext* cx, HandleObject target, MutableHandleValue rval)
 {
     MOZ_ASSERT(target->hasDynamicPrototype());
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -625,17 +625,18 @@ CodeGeneratorARM::visitSoftDivI(LSoftDiv
         masm.setupWasmABICall();
         masm.passABIArg(lhs);
         masm.passABIArg(rhs);
         masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::aeabi_idivmod);
     } else {
         masm.setupAlignedABICall();
         masm.passABIArg(lhs);
         masm.passABIArg(rhs);
-        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, __aeabi_idivmod));
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, __aeabi_idivmod), MoveOp::GENERAL,
+                         CheckUnsafeCallWithABI::DontCheckOther);
     }
 
     // idivmod returns the quotient in r0, and the remainder in r1.
     if (!mir->canTruncateRemainder()) {
         MOZ_ASSERT(mir->fallible());
         masm.as_cmp(r1, Imm8(0));
         bailoutIf(Assembler::NonZero, ins->snapshot());
     }
@@ -814,17 +815,18 @@ CodeGeneratorARM::visitSoftModI(LSoftMod
         masm.setupWasmABICall();
         masm.passABIArg(lhs);
         masm.passABIArg(rhs);
         masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::aeabi_idivmod);
     } else {
         masm.setupAlignedABICall();
         masm.passABIArg(lhs);
         masm.passABIArg(rhs);
-        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, __aeabi_idivmod));
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, __aeabi_idivmod), MoveOp::GENERAL,
+                         CheckUnsafeCallWithABI::DontCheckOther);
     }
 
     MOZ_ASSERT(r1 != output);
     masm.move32(r1, output);
 
     // If X%Y == 0 and X < 0, then we *actually* wanted to return -0.0
     if (mir->canBeNegativeDividend()) {
         if (mir->isTruncated()) {
@@ -2836,17 +2838,18 @@ CodeGeneratorARM::visitSoftUDivOrMod(LSo
         masm.passABIArg(lhs);
         masm.passABIArg(rhs);
         wasm::BytecodeOffset bytecodeOffset = (div ? div->bytecodeOffset() : mod->bytecodeOffset());
         masm.callWithABI(bytecodeOffset, wasm::SymbolicAddress::aeabi_uidivmod);
     } else {
         masm.setupAlignedABICall();
         masm.passABIArg(lhs);
         masm.passABIArg(rhs);
-        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, __aeabi_uidivmod));
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, __aeabi_uidivmod), MoveOp::GENERAL,
+                         CheckUnsafeCallWithABI::DontCheckOther);
     }
 
     if (mod) {
         MOZ_ASSERT(output == r0, "output should not be r1 for mod");
         masm.move32(r1, output);
     }
 
     // uidivmod returns the quotient in r0, and the remainder in r1.
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -3593,17 +3593,17 @@ MacroAssemblerARMCompat::handleFailureWi
 
     Imm8 size8(size);
     as_sub(sp, sp, size8);
     ma_mov(sp, r0);
 
     // Call the handler.
     asMasm().setupUnalignedABICall(r1);
     asMasm().passABIArg(r0);
-    asMasm().callWithABI(handler);
+    asMasm().callWithABI(handler, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     Label entryFrame;
     Label catch_;
     Label finally;
     Label return_;
     Label bailout;
 
     {
--- a/js/src/jit/arm/SharedIC-arm.cpp
+++ b/js/src/jit/arm/SharedIC-arm.cpp
@@ -89,17 +89,18 @@ ICBinaryArith_Int32::Compiler::generateS
         // register.
         MOZ_ASSERT(R1 == ValueOperand(r5, r4));
         MOZ_ASSERT(R0 == ValueOperand(r3, r2));
         masm.moveValue(R0, savedValue);
 
         masm.setupAlignedABICall();
         masm.passABIArg(R0.payloadReg());
         masm.passABIArg(R1.payloadReg());
-        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, __aeabi_idivmod));
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, __aeabi_idivmod), MoveOp::GENERAL,
+                         CheckUnsafeCallWithABI::DontCheckOther);
 
         // idivmod returns the quotient in r0, and the remainder in r1.
         if (op_ == JSOP_DIV) {
             // Result is a double if the remainder != 0.
             masm.branch32(Assembler::NotEqual, r1, Imm32(0), &revertRegister);
             masm.tagValue(JSVAL_TYPE_INT32, r0, R0);
         } else {
             // If X % Y == 0 and X < 0, the result is -0.
--- a/js/src/jit/arm/Trampoline-arm.cpp
+++ b/js/src/jit/arm/Trampoline-arm.cpp
@@ -877,17 +877,17 @@ JitRuntime::generateVMWrapper(JSContext*
             break;
         }
     }
 
     // Copy the implicit outparam, if any.
     if (outReg != InvalidReg)
         masm.passABIArg(outReg);
 
-    masm.callWithABI(f.wrapped);
+    masm.callWithABI(f.wrapped, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     if (!generateTLExitVM(cx, masm, f))
         return nullptr;
 
     // Test for failure.
     switch (f.failType()) {
       case Type_Object:
         masm.branchTestPtr(Assembler::Zero, r0, r0, masm.failureLabel());
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -140,17 +140,17 @@ MacroAssemblerCompat::handleFailureWithH
     if (!GetStackPointer64().Is(sp))
         Mov(sp, GetStackPointer64());
 
     Mov(x0, GetStackPointer64());
 
     // Call the handler.
     asMasm().setupUnalignedABICall(r1);
     asMasm().passABIArg(r0);
-    asMasm().callWithABI(handler);
+    asMasm().callWithABI(handler, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     Label entryFrame;
     Label catch_;
     Label finally;
     Label return_;
     Label bailout;
 
     MOZ_ASSERT(GetStackPointer64().Is(x28)); // Lets the code below be a little cleaner.
--- a/js/src/jit/arm64/Trampoline-arm64.cpp
+++ b/js/src/jit/arm64/Trampoline-arm64.cpp
@@ -672,17 +672,17 @@ JitRuntime::generateVMWrapper(JSContext*
 
     // Copy the semi-implicit outparam, if any.
     // It is not a C++-abi outparam, which would get passed in the
     // outparam register, but a real parameter to the function, which
     // was stack-allocated above.
     if (outReg != InvalidReg)
         masm.passABIArg(outReg);
 
-    masm.callWithABI(f.wrapped);
+    masm.callWithABI(f.wrapped, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     if (!generateTLExitVM(cx, masm, f))
         return nullptr;
 
     // SP is used to transfer stack across call boundaries.
     if (!masm.GetStackPointer64().Is(vixl::sp))
         masm.Mov(masm.GetStackPointer64(), vixl::sp);
 
--- a/js/src/jit/mips32/MacroAssembler-mips32.cpp
+++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp
@@ -1843,17 +1843,17 @@ MacroAssemblerMIPSCompat::handleFailureW
     // Reserve space for exception information.
     int size = (sizeof(ResumeFromException) + ABIStackAlignment) & ~(ABIStackAlignment - 1);
     asMasm().subPtr(Imm32(size), StackPointer);
     ma_move(a0, StackPointer); // Use a0 since it is a first function argument
 
     // Call the handler.
     asMasm().setupUnalignedABICall(a1);
     asMasm().passABIArg(a0);
-    asMasm().callWithABI(handler);
+    asMasm().callWithABI(handler, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     Label entryFrame;
     Label catch_;
     Label finally;
     Label return_;
     Label bailout;
 
     // Already clobbered a0, so use it...
--- a/js/src/jit/mips32/Trampoline-mips32.cpp
+++ b/js/src/jit/mips32/Trampoline-mips32.cpp
@@ -842,17 +842,17 @@ JitRuntime::generateVMWrapper(JSContext*
                   doubleArgDisp + sizeof(double) == outParamOffset + outParamSize);
 
     // Copy the implicit outparam, if any.
     if (f.outParam != Type_Void) {
         masm.passABIArg(MoveOperand(doubleArgs, outParamOffset, MoveOperand::EFFECTIVE_ADDRESS),
                             MoveOp::GENERAL);
     }
 
-    masm.callWithABI(f.wrapped);
+    masm.callWithABI(f.wrapped, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     if (!generateTLExitVM(cx, masm, f))
         return nullptr;
 
     // Test for failure.
     switch (f.failType()) {
       case Type_Object:
         masm.branchTestPtr(Assembler::Zero, v0, v0, masm.failureLabel());
--- a/js/src/jit/mips64/MacroAssembler-mips64.cpp
+++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp
@@ -2025,17 +2025,17 @@ MacroAssemblerMIPS64Compat::handleFailur
     // Reserve space for exception information.
     int size = (sizeof(ResumeFromException) + ABIStackAlignment) & ~(ABIStackAlignment - 1);
     asMasm().subPtr(Imm32(size), StackPointer);
     ma_move(a0, StackPointer); // Use a0 since it is a first function argument
 
     // Call the handler.
     asMasm().setupUnalignedABICall(a1);
     asMasm().passABIArg(a0);
-    asMasm().callWithABI(handler);
+    asMasm().callWithABI(handler, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     Label entryFrame;
     Label catch_;
     Label finally;
     Label return_;
     Label bailout;
 
     // Already clobbered a0, so use it...
--- a/js/src/jit/mips64/Trampoline-mips64.cpp
+++ b/js/src/jit/mips64/Trampoline-mips64.cpp
@@ -788,17 +788,17 @@ JitRuntime::generateVMWrapper(JSContext*
             break;
         }
     }
 
     // Copy the implicit outparam, if any.
     if (InvalidReg != outReg)
         masm.passABIArg(outReg);
 
-    masm.callWithABI(f.wrapped);
+    masm.callWithABI(f.wrapped, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     if (!generateTLExitVM(cx, masm, f))
         return nullptr;
 
     // Test for failure.
     switch (f.failType()) {
       case Type_Object:
         masm.branchTestPtr(Assembler::Zero, v0, v0, masm.failureLabel());
--- a/js/src/jit/x64/MacroAssembler-x64.cpp
+++ b/js/src/jit/x64/MacroAssembler-x64.cpp
@@ -302,17 +302,17 @@ MacroAssemblerX64::handleFailureWithHand
 {
     // Reserve space for exception information.
     subq(Imm32(sizeof(ResumeFromException)), rsp);
     movq(rsp, rax);
 
     // Call the handler.
     asMasm().setupUnalignedABICall(rcx);
     asMasm().passABIArg(rax);
-    asMasm().callWithABI(handler);
+    asMasm().callWithABI(handler, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     Label entryFrame;
     Label catch_;
     Label finally;
     Label return_;
     Label bailout;
 
     load32(Address(rsp, offsetof(ResumeFromException, kind)), rax);
--- a/js/src/jit/x64/Trampoline-x64.cpp
+++ b/js/src/jit/x64/Trampoline-x64.cpp
@@ -760,17 +760,17 @@ JitRuntime::generateVMWrapper(JSContext*
             MOZ_CRASH("NYI: x64 callVM should not be used with 128bits values.");
         }
     }
 
     // Copy the implicit outparam, if any.
     if (outReg != InvalidReg)
         masm.passABIArg(outReg);
 
-    masm.callWithABI(f.wrapped);
+    masm.callWithABI(f.wrapped, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     if (!generateTLExitVM(cx, masm, f))
         return nullptr;
 
     // Test for failure.
     switch (f.failType()) {
       case Type_Object:
         masm.branchTestPtr(Assembler::Zero, rax, rax, masm.failureLabel());
--- a/js/src/jit/x86/CodeGenerator-x86.cpp
+++ b/js/src/jit/x86/CodeGenerator-x86.cpp
@@ -739,17 +739,18 @@ CodeGeneratorX86::visitOutOfLineTruncate
 
         if (gen->compilingWasm()) {
             masm.setupWasmABICall();
             masm.passABIArg(input, MoveOp::DOUBLE);
             masm.callWithABI(ins->mir()->bytecodeOffset(), wasm::SymbolicAddress::ToInt32);
         } else {
             masm.setupUnalignedABICall(output);
             masm.passABIArg(input, MoveOp::DOUBLE);
-            masm.callWithABI(BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32));
+            masm.callWithABI(BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32), MoveOp::GENERAL,
+                             CheckUnsafeCallWithABI::DontCheckOther);
         }
         masm.storeCallWordResult(output);
 
         restoreVolatile(output);
     }
 
     masm.jump(ool->rejoin());
 }
@@ -826,20 +827,22 @@ CodeGeneratorX86::visitOutOfLineTruncate
         if (gen->compilingWasm())
             masm.setupWasmABICall();
         else
             masm.setupUnalignedABICall(output);
 
         masm.vcvtss2sd(input, input, input);
         masm.passABIArg(input.asDouble(), MoveOp::DOUBLE);
 
-        if (gen->compilingWasm())
+        if (gen->compilingWasm()) {
             masm.callWithABI(ins->mir()->bytecodeOffset(), wasm::SymbolicAddress::ToInt32);
-        else
-            masm.callWithABI(BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32));
+        } else {
+            masm.callWithABI(BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32), MoveOp::GENERAL,
+                             CheckUnsafeCallWithABI::DontCheckOther);
+        }
 
         masm.storeCallWordResult(output);
         masm.Pop(input);
 
         restoreVolatile(output);
     }
 
     masm.jump(ool->rejoin());
--- a/js/src/jit/x86/MacroAssembler-x86.cpp
+++ b/js/src/jit/x86/MacroAssembler-x86.cpp
@@ -201,17 +201,17 @@ MacroAssemblerX86::handleFailureWithHand
 {
     // Reserve space for exception information.
     subl(Imm32(sizeof(ResumeFromException)), esp);
     movl(esp, eax);
 
     // Call the handler.
     asMasm().setupUnalignedABICall(ecx);
     asMasm().passABIArg(eax);
-    asMasm().callWithABI(handler);
+    asMasm().callWithABI(handler, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     Label entryFrame;
     Label catch_;
     Label finally;
     Label return_;
     Label bailout;
 
     loadPtr(Address(esp, offsetof(ResumeFromException, kind)), eax);
--- a/js/src/jit/x86/Trampoline-x86.cpp
+++ b/js/src/jit/x86/Trampoline-x86.cpp
@@ -790,17 +790,17 @@ JitRuntime::generateVMWrapper(JSContext*
             break;
         }
     }
 
     // Copy the implicit outparam, if any.
     if (outReg != InvalidReg)
         masm.passABIArg(outReg);
 
-    masm.callWithABI(f.wrapped);
+    masm.callWithABI(f.wrapped, MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
     if (!generateTLExitVM(cx, masm, f))
         return nullptr;
 
     // Test for failure.
     switch (f.failType()) {
       case Type_Object:
         masm.branchTestPtr(Assembler::Zero, eax, eax, masm.failureLabel());
--- a/js/src/jsapi-tests/testBug604087.cpp
+++ b/js/src/jsapi-tests/testBug604087.cpp
@@ -9,24 +9,19 @@
 
 #include "jsobj.h"
 #include "jswrapper.h"
 
 #include "jsapi-tests/tests.h"
 
 #include "vm/ProxyObject.h"
 
-static const js::ClassExtension OuterWrapperClassExtension = PROXY_MAKE_EXT(
-    nullptr  /* objectMoved */
-);
-
-const js::Class OuterWrapperClass = PROXY_CLASS_WITH_EXT(
+const js::Class OuterWrapperClass = PROXY_CLASS_DEF(
     "Proxy",
-    JSCLASS_HAS_RESERVED_SLOTS(1), /* additional class flags */
-    &OuterWrapperClassExtension);
+    JSCLASS_HAS_RESERVED_SLOTS(1) /* additional class flags */);
 
 static JSObject*
 wrap(JSContext* cx, JS::HandleObject toWrap, JS::HandleObject target)
 {
     JSAutoCompartment ac(cx, target);
     JS::RootedObject wrapper(cx, toWrap);
     if (!JS_WrapObject(cx, &wrapper))
         return nullptr;
--- a/js/src/jsapi-tests/testGCGrayMarking.cpp
+++ b/js/src/jsapi-tests/testGCGrayMarking.cpp
@@ -22,21 +22,18 @@ struct DeletePolicy<js::ObjectWeakMap> :
 {};
 
 template <>
 struct MapTypeToRootKind<js::ObjectWeakMap*> {
     static const JS::RootKind kind = JS::RootKind::Traceable;
 };
 
 template <>
-struct GCPolicy<js::ObjectWeakMap*> {
-    static void trace(JSTracer* trc, js::ObjectWeakMap** tp, const char* name) {
-        (*tp)->trace(trc);
-    }
-};
+struct GCPolicy<js::ObjectWeakMap*> : public NonGCPointerPolicy<js::ObjectWeakMap*>
+{};
 
 } // namespace JS
 
 class AutoNoAnalysisForTest
 {
   public:
     AutoNoAnalysisForTest() {}
 } JS_HAZ_GC_SUPPRESSED;
--- a/js/src/jsapi-tests/testWeakMap.cpp
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -135,23 +135,25 @@ BEGIN_TEST(testWeakMap_keyDelegates)
     delegateRoot = nullptr;
     keyDelegate = nullptr;
     JS_GC(cx);
     CHECK(checkSize(map, 0));
 
     return true;
 }
 
-static void DelegateObjectMoved(JSObject* obj, const JSObject* old)
+static size_t
+DelegateObjectMoved(JSObject* obj, JSObject* old)
 {
     if (!keyDelegate)
-        return;  // Object got moved before we set keyDelegate to point to it.
+        return 0;  // Object got moved before we set keyDelegate to point to it.
 
     MOZ_RELEASE_ASSERT(keyDelegate == old);
     keyDelegate = obj;
+    return 0;
 }
 
 static JSObject* GetKeyDelegate(JSObject* obj)
 {
     return keyDelegate;
 }
 
 JSObject* newKey()
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2419,16 +2419,17 @@ ShiftMoveBoxedOrUnboxedDenseElements(JSO
     return DenseElementResult::Success;
 }
 
 DefineBoxedOrUnboxedFunctor1(ShiftMoveBoxedOrUnboxedDenseElements, JSObject*);
 
 void
 js::ArrayShiftMoveElements(JSObject* obj)
 {
+    AutoUnsafeCallWithABI unsafe;
     MOZ_ASSERT_IF(obj->is<ArrayObject>(), obj->as<ArrayObject>().lengthIsWritable());
 
     ShiftMoveBoxedOrUnboxedDenseElementsFunctor functor(obj);
     JS_ALWAYS_TRUE(CallBoxedOrUnboxedSpecialization(functor, obj) == DenseElementResult::Success);
 }
 
 template <JSValueType Type>
 DenseElementResult
--- a/js/src/jsboolinlines.h
+++ b/js/src/jsboolinlines.h
@@ -4,25 +4,28 @@
  * 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 jsboolinlines_h
 #define jsboolinlines_h
 
 #include "jsbool.h"
 
+#include "jscntxt.h"
+
 #include "vm/BooleanObject.h"
 #include "vm/WrapperObject.h"
 
 namespace js {
 
 inline bool
 EmulatesUndefined(JSObject* obj)
 {
     // This may be called off the main thread. It's OK not to expose the object
     // here as it doesn't escape.
+    AutoUnsafeCallWithABI unsafe;
     JSObject* actual = MOZ_LIKELY(!obj->is<WrapperObject>()) ? obj : UncheckedUnwrapWithoutExpose(obj);
     return actual->getClass()->emulatesUndefined();
 }
 
 } /* namespace js */
 
 #endif /* jsboolinlines_h */
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1288,16 +1288,18 @@ JSContext::JSContext(JSRuntime* runtime,
     entryMonitor(nullptr),
     noExecuteDebuggerTop(nullptr),
     handlingSegFault(false),
     activityCallback(nullptr),
     activityCallbackArg(nullptr),
     requestDepth(0),
 #ifdef DEBUG
     checkRequestDepth(0),
+    inUnsafeCallWithABI(false),
+    hasAutoUnsafeCallWithABI(false),
 #endif
 #ifdef JS_SIMULATOR
     simulator_(nullptr),
 #endif
 #ifdef JS_TRACE_LOGGING
     traceLogger(nullptr),
 #endif
     autoFlushICache_(nullptr),
@@ -1663,8 +1665,27 @@ AutoEnterOOMUnsafeRegion::crash(size_t s
 {
     {
         JS::AutoSuppressGCAnalysis suppress;
         if (annotateOOMSizeCallback)
             annotateOOMSizeCallback(size);
     }
     crash(reason);
 }
+
+#ifdef DEBUG
+AutoUnsafeCallWithABI::AutoUnsafeCallWithABI()
+  : cx_(TlsContext.get()),
+    nested_(cx_->hasAutoUnsafeCallWithABI),
+    nogc(cx_)
+{
+    cx_->hasAutoUnsafeCallWithABI = true;
+}
+
+AutoUnsafeCallWithABI::~AutoUnsafeCallWithABI()
+{
+    MOZ_ASSERT(cx_->hasAutoUnsafeCallWithABI);
+    if (!nested_) {
+        cx_->hasAutoUnsafeCallWithABI = false;
+        cx_->inUnsafeCallWithABI = false;
+    }
+}
+#endif
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -374,16 +374,22 @@ struct JSContext : public JS::RootingCon
 
     js::Activation* profilingActivation() const {
         return profilingActivation_;
     }
     static size_t offsetOfProfilingActivation() {
         return offsetof(JSContext, profilingActivation_);
      }
 
+#ifdef DEBUG
+    static size_t offsetOfInUnsafeCallWithABI() {
+        return offsetof(JSContext, inUnsafeCallWithABI);
+    }
+#endif
+
   private:
     /* Space for interpreter frames. */
     js::ThreadLocalData<js::InterpreterStack> interpreterStack_;
 
   public:
     js::InterpreterStack& interpreterStack() {
         return interpreterStack_.ref();
     }
@@ -414,16 +420,18 @@ struct JSContext : public JS::RootingCon
     js::ThreadLocalData<void*>                activityCallbackArg;
     void triggerActivityCallback(bool active);
 
     /* The request depth for this thread. */
     js::ThreadLocalData<unsigned> requestDepth;
 
 #ifdef DEBUG
     js::ThreadLocalData<unsigned> checkRequestDepth;
+    js::ThreadLocalData<uint32_t> inUnsafeCallWithABI;
+    js::ThreadLocalData<bool> hasAutoUnsafeCallWithABI;
 #endif
 
 #ifdef JS_SIMULATOR
   private:
     js::ThreadLocalData<js::jit::Simulator*> simulator_;
   public:
     js::jit::Simulator* simulator() const;
     uintptr_t* addressOfSimulatorStackLimit();
@@ -1281,16 +1289,33 @@ class MOZ_RAII AutoEnterIonCompilation
         cx->ionCompiling = false;
         cx->ionCompilingSafeForMinorGC = false;
 #endif
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
+// Should be used in functions called directly from JIT code (with
+// masm.callWithABI) to assert invariants in debug builds.
+class MOZ_RAII AutoUnsafeCallWithABI
+{
+#ifdef DEBUG
+    JSContext* cx_;
+    bool nested_;
+#endif
+    JS::AutoCheckCannotGC nogc;
+
+  public:
+#ifdef DEBUG
+    AutoUnsafeCallWithABI();
+    ~AutoUnsafeCallWithABI();
+#endif
+};
+
 namespace gc {
 
 // In debug builds, set/unset the performing GC flag for the current thread.
 struct MOZ_RAII AutoSetThreadIsPerformingGC
 {
 #ifdef DEBUG
     AutoSetThreadIsPerformingGC()
       : cx(TlsContext.get())
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -8078,16 +8078,17 @@ JS::AssertGCThingIsNotAnObjectSubclass(C
 {
     MOZ_ASSERT(cell);
     MOZ_ASSERT(cell->getTraceKind() != JS::TraceKind::Object);
 }
 
 JS_FRIEND_API(void)
 js::gc::AssertGCThingHasType(js::gc::Cell* cell, JS::TraceKind kind)
 {
+    MOZ_ASSERT(IsCellPointerValid(cell));
     if (!cell)
         MOZ_ASSERT(kind == JS::TraceKind::Null);
     else if (IsInsideNursery(cell))
         MOZ_ASSERT(kind == JS::TraceKind::Object);
     else
         MOZ_ASSERT(MapAllocToTraceKind(cell->asTenured().getAllocKind()) == kind);
 }
 #endif
--- a/js/src/jslibmath.h
+++ b/js/src/jslibmath.h
@@ -6,16 +6,17 @@
 
 #ifndef jslibmath_h
 #define jslibmath_h
 
 #include "mozilla/FloatingPoint.h"
 
 #include <math.h>
 
+#include "jscntxt.h"
 #include "jsnum.h"
 
 /*
  * Use system provided math routines.
  */
 
 /* The right copysign function is not always named the same thing. */
 #ifdef __GNUC__
@@ -43,16 +44,17 @@ js_fmod(double d, double d2)
     return fmod(d, d2);
 }
 
 namespace js {
 
 inline double
 NumberDiv(double a, double b)
 {
+    AutoUnsafeCallWithABI unsafe;
     if (b == 0) {
         if (a == 0 || mozilla::IsNaN(a)
 #ifdef XP_WIN
             || mozilla::IsNaN(b) /* XXX MSVC miscompiles such that (NaN == 0) */
 #endif
         )
             return JS::GenericNaN();
 
@@ -60,17 +62,19 @@ NumberDiv(double a, double b)
             return mozilla::NegativeInfinity<double>();
         return mozilla::PositiveInfinity<double>();
     }
 
     return a / b;
 }
 
 inline double
-NumberMod(double a, double b) {
+NumberMod(double a, double b)
+{
+    AutoUnsafeCallWithABI unsafe;
     if (b == 0)
         return JS::GenericNaN();
     return js_fmod(a, b);
 }
 
 } // namespace js
 
 #endif /* jslibmath_h */
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -178,22 +178,24 @@ js::math_abs(JSContext* cx, unsigned arg
     }
 
     return math_abs_handle(cx, args[0], args.rval());
 }
 
 double
 js::math_acos_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::acos, x, MathCache::Acos);
 }
 
 double
 js::math_acos_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::acos(x);
 }
 
 bool
 js::math_acos(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -213,22 +215,24 @@ js::math_acos(JSContext* cx, unsigned ar
     double z = math_acos_impl(mathCache, x);
     args.rval().setDouble(z);
     return true;
 }
 
 double
 js::math_asin_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::asin, x, MathCache::Asin);
 }
 
 double
 js::math_asin_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::asin(x);
 }
 
 bool
 js::math_asin(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -248,22 +252,24 @@ js::math_asin(JSContext* cx, unsigned ar
     double z = math_asin_impl(mathCache, x);
     args.rval().setDouble(z);
     return true;
 }
 
 double
 js::math_atan_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::atan, x, MathCache::Atan);
 }
 
 double
 js::math_atan_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::atan(x);
 }
 
 bool
 js::math_atan(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -283,16 +289,17 @@ js::math_atan(JSContext* cx, unsigned ar
     double z = math_atan_impl(mathCache, x);
     args.rval().setDouble(z);
     return true;
 }
 
 double
 js::ecmaAtan2(double y, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::atan2(y, x);
 }
 
 bool
 js::math_atan2_handle(JSContext* cx, HandleValue y, HandleValue x, MutableHandleValue res)
 {
     double dy;
     if (!ToNumber(cx, y, &dy))
@@ -313,16 +320,17 @@ js::math_atan2(JSContext* cx, unsigned a
     CallArgs args = CallArgsFromVp(argc, vp);
 
     return math_atan2_handle(cx, args.get(0), args.get(1), args.rval());
 }
 
 double
 js::math_ceil_impl(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::ceil(x);
 }
 
 bool
 js::math_ceil_handle(JSContext* cx, HandleValue v, MutableHandleValue res)
 {
     double d;
     if(!ToNumber(cx, v, &d))
@@ -367,22 +375,24 @@ js::math_clz32(JSContext* cx, unsigned a
 
     args.rval().setInt32(mozilla::CountLeadingZeroes32(n));
     return true;
 }
 
 double
 js::math_cos_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(cos, x, MathCache::Cos);
 }
 
 double
 js::math_cos_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cos(x);
 }
 
 bool
 js::math_cos(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -402,22 +412,24 @@ js::math_cos(JSContext* cx, unsigned arg
     double z = math_cos_impl(mathCache, x);
     args.rval().setDouble(z);
     return true;
 }
 
 double
 js::math_exp_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::exp, x, MathCache::Exp);
 }
 
 double
 js::math_exp_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::exp(x);
 }
 
 bool
 js::math_exp(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -437,16 +449,17 @@ js::math_exp(JSContext* cx, unsigned arg
     double z = math_exp_impl(mathCache, x);
     args.rval().setNumber(z);
     return true;
 }
 
 double
 js::math_floor_impl(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::floor(x);
 }
 
 bool
 js::math_floor_handle(JSContext* cx, HandleValue v, MutableHandleValue r)
 {
     double d;
     if (!ToNumber(cx, v, &d))
@@ -527,22 +540,24 @@ js::math_fround(JSContext* cx, unsigned 
     }
 
     return RoundFloat32(cx, args[0], args.rval());
 }
 
 double
 js::math_log_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(math_log_uncached, x, MathCache::Log);
 }
 
 double
 js::math_log_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::log(x);
 }
 
 bool
 js::math_log_handle(JSContext* cx, HandleValue val, MutableHandleValue res)
 {
     double in;
     if (!ToNumber(cx, val, &in))
@@ -568,16 +583,18 @@ js::math_log(JSContext* cx, unsigned arg
     }
 
     return math_log_handle(cx, args[0], args.rval());
 }
 
 double
 js::math_max_impl(double x, double y)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     // Math.max(num, NaN) => NaN, Math.max(-0, +0) => +0
     if (x > y || IsNaN(x) || (x == y && IsNegative(y)))
         return x;
     return y;
 }
 
 bool
 js::math_max(JSContext* cx, unsigned argc, Value* vp)
@@ -593,16 +610,18 @@ js::math_max(JSContext* cx, unsigned arg
     }
     args.rval().setNumber(maxval);
     return true;
 }
 
 double
 js::math_min_impl(double x, double y)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     // Math.min(num, NaN) => NaN, Math.min(-0, +0) => -0
     if (x < y || IsNaN(x) || (x == y && IsNegativeZero(x)))
         return x;
     return y;
 }
 
 bool
 js::math_min(JSContext* cx, unsigned argc, Value* vp)
@@ -636,16 +655,17 @@ js::minmax_impl(JSContext* cx, bool max,
         res.setNumber(math_min_impl(x, y));
 
     return true;
 }
 
 double
 js::powi(double x, int y)
 {
+    AutoUnsafeCallWithABI unsafe;
     unsigned n = (y < 0) ? -y : y;
     double m = x;
     double p = 1;
     while (true) {
         if ((n & 1) != 0) p *= m;
         n >>= 1;
         if (n == 0) {
             if (y < 0) {
@@ -664,16 +684,18 @@ js::powi(double x, int y)
         }
         m *= m;
     }
 }
 
 double
 js::ecmaPow(double x, double y)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     /*
      * Use powi if the exponent is an integer-valued double. We don't have to
      * check for NaN since a comparison with NaN is always false.
      */
     int32_t yi;
     if (NumberEqualsInt32(y, &yi))
         return powi(x, yi);
 
@@ -820,31 +842,35 @@ js::GetBiggestNumberLessThan(T x)
 }
 
 template double js::GetBiggestNumberLessThan<>(double x);
 template float js::GetBiggestNumberLessThan<>(float x);
 
 double
 js::math_round_impl(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     int32_t ignored;
     if (NumberIsInt32(x, &ignored))
         return x;
 
     /* Some numbers are so big that adding 0.5 would give the wrong number. */
     if (ExponentComponent(x) >= int_fast16_t(FloatingPoint<double>::kExponentShift))
         return x;
 
     double add = (x >= 0) ? GetBiggestNumberLessThan(0.5) : 0.5;
     return js_copysign(fdlibm::floor(x + add), x);
 }
 
 float
 js::math_roundf_impl(float x)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     int32_t ignored;
     if (NumberIsInt32(x, &ignored))
         return x;
 
     /* Some numbers are so big that adding 0.5 would give the wrong number. */
     if (ExponentComponent(x) >= int_fast16_t(FloatingPoint<float>::kExponentShift))
         return x;
 
@@ -863,22 +889,24 @@ js::math_round(JSContext* cx, unsigned a
     }
 
     return math_round_handle(cx, args[0], args.rval());
 }
 
 double
 js::math_sin_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(math_sin_uncached, x, MathCache::Sin);
 }
 
 double
 js::math_sin_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
 #ifdef _WIN64
     // Workaround MSVC bug where sin(-0) is +0 instead of -0 on x64 on
     // CPUs without FMA3 (pre-Haswell). See bug 1076670.
     if (IsNegativeZero(x))
         return -0.0;
 #endif
     return sin(x);
 }
@@ -910,29 +938,31 @@ js::math_sin(JSContext* cx, unsigned arg
     }
 
     return math_sin_handle(cx, args[0], args.rval());
 }
 
 void
 js::math_sincos_uncached(double x, double *sin, double *cos)
 {
+    AutoUnsafeCallWithABI unsafe;
 #if defined(HAVE_SINCOS)
     sincos(x, sin, cos);
 #elif defined(HAVE___SINCOS)
     __sincos(x, sin, cos);
 #else
     *sin = js::math_sin_uncached(x);
     *cos = js::math_cos_uncached(x);
 #endif
 }
 
 void
 js::math_sincos_impl(MathCache* mathCache, double x, double *sin, double *cos)
 {
+    AutoUnsafeCallWithABI unsafe;
     unsigned indexSin;
     unsigned indexCos;
     bool hasSin = mathCache->isCached(x, MathCache::Sin, sin, &indexSin);
     bool hasCos = mathCache->isCached(x, MathCache::Cos, cos, &indexCos);
     if (!(hasSin || hasCos)) {
         js::math_sincos_uncached(x, sin, cos);
         mathCache->store(MathCache::Sin, x, *sin, indexSin);
         mathCache->store(MathCache::Cos, x, *cos, indexCos);
@@ -973,22 +1003,24 @@ js::math_sqrt(JSContext* cx, unsigned ar
     }
 
     return math_sqrt_handle(cx, args[0], args.rval());
 }
 
 double
 js::math_tan_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(tan, x, MathCache::Tan);
 }
 
 double
 js::math_tan_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return tan(x);
 }
 
 bool
 js::math_tan(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -1032,197 +1064,218 @@ static bool math_function(JSContext* cx,
     args.rval().setNumber(z);
 
     return true;
 }
 
 double
 js::math_log10_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::log10, x, MathCache::Log10);
 }
 
 double
 js::math_log10_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::log10(x);
 }
 
 bool
 js::math_log10(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_log10_impl>(cx, argc, vp);
 }
 
 double
 js::math_log2_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::log2, x, MathCache::Log2);
 }
 
 double
 js::math_log2_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::log2(x);
 }
 
 bool
 js::math_log2(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_log2_impl>(cx, argc, vp);
 }
 
 double
 js::math_log1p_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::log1p, x, MathCache::Log1p);
 }
 
 double
 js::math_log1p_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::log1p(x);
 }
 
 bool
 js::math_log1p(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_log1p_impl>(cx, argc, vp);
 }
 
 double
 js::math_expm1_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::expm1, x, MathCache::Expm1);
 }
 
 double
 js::math_expm1_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::expm1(x);
 }
 
 bool
 js::math_expm1(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_expm1_impl>(cx, argc, vp);
 }
 
 double
 js::math_cosh_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::cosh, x, MathCache::Cosh);
 }
 
 double
 js::math_cosh_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::cosh(x);
 }
 
 bool
 js::math_cosh(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_cosh_impl>(cx, argc, vp);
 }
 
 double
 js::math_sinh_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::sinh, x, MathCache::Sinh);
 }
 
 double
 js::math_sinh_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::sinh(x);
 }
 
 bool
 js::math_sinh(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_sinh_impl>(cx, argc, vp);
 }
 
 double
 js::math_tanh_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::tanh, x, MathCache::Tanh);
 }
 
 double
 js::math_tanh_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::tanh(x);
 }
 
 bool
 js::math_tanh(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_tanh_impl>(cx, argc, vp);
 }
 
 double
 js::math_acosh_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::acosh, x, MathCache::Acosh);
 }
 
 double
 js::math_acosh_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::acosh(x);
 }
 
 bool
 js::math_acosh(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_acosh_impl>(cx, argc, vp);
 }
 
 double
 js::math_asinh_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::asinh, x, MathCache::Asinh);
 }
 
 double
 js::math_asinh_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::asinh(x);
 }
 
 bool
 js::math_asinh(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_asinh_impl>(cx, argc, vp);
 }
 
 double
 js::math_atanh_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::atanh, x, MathCache::Atanh);
 }
 
 double
 js::math_atanh_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::atanh(x);
 }
 
 bool
 js::math_atanh(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_atanh_impl>(cx, argc, vp);
 }
 
 /* Consistency wrapper for platform deviations in hypot() */
 double
 js::ecmaHypot(double x, double y)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::hypot(x, y);
 }
 
 static inline
 void
 hypot_step(double& scale, double& sumsq, double x)
 {
     double xabs = mozilla::Abs(x);
@@ -1232,16 +1285,18 @@ hypot_step(double& scale, double& sumsq,
     } else if (scale != 0) {
         sumsq += (xabs / scale) * (xabs / scale);
     }
 }
 
 double
 js::hypot4(double x, double y, double z, double w)
 {
+    AutoUnsafeCallWithABI unsafe;
+
     /* Check for infinity or NaNs so that we can return immediatelly.
      * Does not need to be WIN_XP specific as ecmaHypot
      */
     if (mozilla::IsInfinite(x) || mozilla::IsInfinite(y) ||
             mozilla::IsInfinite(z) || mozilla::IsInfinite(w))
         return mozilla::PositiveInfinity<double>();
 
     if (mozilla::IsNaN(x) || mozilla::IsNaN(y) || mozilla::IsNaN(z) ||
@@ -1257,16 +1312,17 @@ js::hypot4(double x, double y, double z,
     hypot_step(scale, sumsq, w);
 
     return scale * sqrt(sumsq);
 }
 
 double
 js::hypot3(double x, double y, double z)
 {
+    AutoUnsafeCallWithABI unsafe;
     return hypot4(x, y, z, 0.0);
 }
 
 bool
 js::math_hypot(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return math_hypot_handle(cx, args, args.rval());
@@ -1313,22 +1369,24 @@ js::math_hypot_handle(JSContext* cx, Han
                     scale * sqrt(sumsq);
     res.setNumber(result);
     return true;
 }
 
 double
 js::math_trunc_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::trunc, x, MathCache::Trunc);
 }
 
 double
 js::math_trunc_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::trunc(x);
 }
 
 bool
 js::math_trunc(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_trunc_impl>(cx, argc, vp);
 }
@@ -1339,40 +1397,44 @@ static double sign(double x)
         return GenericNaN();
 
     return x == 0 ? x : x < 0 ? -1 : 1;
 }
 
 double
 js::math_sign_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(sign, x, MathCache::Sign);
 }
 
 double
 js::math_sign_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return sign(x);
 }
 
 bool
 js::math_sign(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_sign_impl>(cx, argc, vp);
 }
 
 double
 js::math_cbrt_impl(MathCache* cache, double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return cache->lookup(fdlibm::cbrt, x, MathCache::Cbrt);
 }
 
 double
 js::math_cbrt_uncached(double x)
 {
+    AutoUnsafeCallWithABI unsafe;
     return fdlibm::cbrt(x);
 }
 
 bool
 js::math_cbrt(JSContext* cx, unsigned argc, Value* vp)
 {
     return math_function<math_cbrt_impl>(cx, argc, vp);
 }
--- a/js/src/proxy/BaseProxyHandler.cpp
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -368,19 +368,20 @@ BaseProxyHandler::trace(JSTracer* trc, J
 {
 }
 
 void
 BaseProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const
 {
 }
 
-void
-BaseProxyHandler::objectMoved(JSObject* proxy, const JSObject* old) const
+size_t
+BaseProxyHandler::objectMoved(JSObject* proxy, JSObject* old) const
 {
+    return 0;
 }
 
 JSObject*
 BaseProxyHandler::weakmapKeyDelegate(JSObject* proxy) const
 {
     return nullptr;
 }
 
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -736,21 +736,29 @@ proxy_Finalize(FreeOp* fop, JSObject* ob
 
     MOZ_ASSERT(obj->is<ProxyObject>());
     obj->as<ProxyObject>().handler()->finalize(fop, obj);
 
     if (!obj->as<ProxyObject>().usingInlineValueArray())
         js_free(js::detail::GetProxyDataLayout(obj)->values());
 }
 
-static void
-proxy_ObjectMoved(JSObject* obj, const JSObject* old)
+size_t
+js::proxy_ObjectMoved(JSObject* obj, JSObject* old)
 {
-    MOZ_ASSERT(obj->is<ProxyObject>());
-    obj->as<ProxyObject>().handler()->objectMoved(obj, old);
+    ProxyObject& proxy = obj->as<ProxyObject>();
+
+    if (IsInsideNursery(old)) {
+        // Objects in the nursery are never swapped so the proxy must have an
+        // inline ProxyValueArray.
+        MOZ_ASSERT(old->as<ProxyObject>().usingInlineValueArray());
+        proxy.setInlineValueArray();
+    }
+
+    return proxy.handler()->objectMoved(obj, old);
 }
 
 bool
 js::proxy_Call(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject proxy(cx, &args.callee());
     MOZ_ASSERT(proxy->is<ProxyObject>());
@@ -775,19 +783,20 @@ const ClassOps js::ProxyClassOps = {
     nullptr,                 /* mayResolve  */
     proxy_Finalize,          /* finalize    */
     nullptr,                 /* call        */
     Proxy::hasInstance,      /* hasInstance */
     nullptr,                 /* construct   */
     ProxyObject::trace,      /* trace       */
 };
 
-const ClassExtension js::ProxyClassExtension = PROXY_MAKE_EXT(
+const ClassExtension js::ProxyClassExtension = {
+    proxy_WeakmapKeyDelegate,
     proxy_ObjectMoved
-);
+};
 
 const ObjectOps js::ProxyObjectOps = {
     proxy_LookupProperty,
     Proxy::defineProperty,
     Proxy::has,
     Proxy::get,
     Proxy::set,
     Proxy::getOwnPropertyDescriptor,
--- a/js/src/proxy/Proxy.h
+++ b/js/src/proxy/Proxy.h
@@ -75,16 +75,18 @@ class Proxy
 
     static void trace(JSTracer* trc, JSObject* obj);
 };
 
 bool
 proxy_Call(JSContext* cx, unsigned argc, Value* vp);
 bool
 proxy_Construct(JSContext* cx, unsigned argc, Value* vp);
+size_t
+proxy_ObjectMoved(JSObject* obj, JSObject* old);
 
 // These functions are used by JIT code
 
 bool
 ProxyHasOwn(JSContext* cx, HandleObject proxy, HandleValue idVal, MutableHandleValue result);
 
 bool
 ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id, MutableHandleValue vp);
--- a/js/src/shell/OSObject.cpp
+++ b/js/src/shell/OSObject.cpp
@@ -583,18 +583,19 @@ osfile_close(JSContext* cx, unsigned arg
 
     args.rval().setUndefined();
     return true;
 }
 
 static const JSFunctionSpecWithHelp osfile_functions[] = {
     JS_FN_HELP("readFile", osfile_readFile, 1, 0,
 "readFile(filename, [\"binary\"])",
-"  Read filename into returned string. Filename is relative to the current\n"
-               "  working directory."),
+"  Read entire contents of filename. Returns a string, unless \"binary\" is passed\n"
+"  as the second argument, in which case it returns a Uint8Array. Filename is\n"
+"  relative to the current working directory."),
 
     JS_FN_HELP("readRelativeToScript", osfile_readRelativeToScript, 1, 0,
 "readRelativeToScript(filename, [\"binary\"])",
 "  Read filename into returned string. Filename is relative to the directory\n"
 "  containing the current script."),
 
     JS_FS_HELP_END
 };
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -58,16 +58,17 @@
 #include "jsutil.h"
 #ifdef XP_WIN
 # include "jswin.h"
 #endif
 #include "jswrapper.h"
 #include "shellmoduleloader.out.h"
 
 #include "builtin/ModuleObject.h"
+#include "builtin/RegExp.h"
 #include "builtin/TestingFunctions.h"
 #include "frontend/Parser.h"
 #include "gc/GCInternals.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/InlinableNatives.h"
 #include "jit/Ion.h"
 #include "jit/JitcodeMap.h"
 #include "jit/OptimizationTracking.h"
@@ -6279,17 +6280,17 @@ static const JSFunctionSpecWithHelp shel
 "putstr([exp])",
 "  Evaluate and print expression without newline."),
 
     JS_FN_HELP("dateNow", Now, 0, 0,
 "dateNow()",
 "  Return the current time with sub-ms precision."),
 
     JS_FN_HELP("help", Help, 0, 0,
-"help([name ...])",
+"help([function or interface object or /pattern/])",
 "  Display usage and help messages."),
 
     JS_FN_HELP("quit", Quit, 0, 0,
 "quit()",
 "  Quit the shell."),
 
     JS_FN_HELP("assertEq", AssertEq, 2, 0,
 "assertEq(actual, expected[, msg])",
@@ -6857,68 +6858,116 @@ PrintHelp(JSContext* cx, HandleObject ob
 
     if (!usage.isString() || !help.isString())
         return true;
 
     return PrintHelpString(cx, usage) && PrintHelpString(cx, help);
 }
 
 static bool
-PrintEnumeratedHelp(JSContext* cx, HandleObject obj, bool brief)
+PrintEnumeratedHelp(JSContext* cx, HandleObject obj, HandleObject pattern, bool brief)
 {
     AutoIdVector idv(cx);
     if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &idv))
         return false;
 
+    Rooted<RegExpObject*> regex(cx);
+    if (pattern)
+        regex = &UncheckedUnwrap(pattern)->as<RegExpObject>();
+
     for (size_t i = 0; i < idv.length(); i++) {
         RootedValue v(cx);
         RootedId id(cx, idv[i]);
         if (!JS_GetPropertyById(cx, obj, id, &v))
             return false;
-        if (v.isObject()) {
-            RootedObject funcObj(cx, &v.toObject());
-            if (!PrintHelp(cx, funcObj))
+        if (!v.isObject())
+            continue;
+
+        RootedObject funcObj(cx, &v.toObject());
+        if (regex) {
+            // Only pay attention to objects with a 'help' property, which will
+            // either be documented functions or interface objects.
+            if (!JS_GetProperty(cx, funcObj, "help", &v))
+                return false;
+            if (!v.isString())
+                continue;
+
+            // For functions, match against the name. For interface objects,
+            // match against the usage string.
+            if (!JS_GetProperty(cx, funcObj, "name", &v))
                 return false;
-        }
+            if (!v.isString()) {
+                if (!JS_GetProperty(cx, funcObj, "usage", &v))
+                    return false;
+                if (!v.isString())
+                    continue;
+            }
+
+            size_t ignored = 0;
+            if (!JSString::ensureLinear(cx, v.toString()))
+                return false;
+            RootedLinearString input(cx, &v.toString()->asLinear());
+            if (!ExecuteRegExpLegacy(cx, nullptr, regex, input, &ignored, true, &v))
+                return false;
+            if (v.isNull())
+                continue;
+        }
+
+        if (!PrintHelp(cx, funcObj))
+            return false;
     }
 
     return true;
 }
 
 static bool
 Help(JSContext* cx, unsigned argc, Value* vp)
 {
     if (!gOutFile->isOpen()) {
         JS_ReportErrorASCII(cx, "output file is closed");
         return false;
     }
 
     CallArgs args = CallArgsFromVp(argc, vp);
-
-    RootedObject obj(cx);
+    args.rval().setUndefined();
+    RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+
+    // help() - display the version and dump out help for all functions on the
+    // global.
     if (args.length() == 0) {
         fprintf(gOutFile->fp, "%s\n", JS_GetImplementationVersion());
 
-        RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
-        if (!PrintEnumeratedHelp(cx, global, false))
-            return false;
-    } else {
-        for (unsigned i = 0; i < args.length(); i++) {
-            if (args[i].isPrimitive()) {
-                JS_ReportErrorASCII(cx, "primitive arg");
-                return false;
-            }
-            obj = args[i].toObjectOrNull();
-            if (!PrintHelp(cx, obj))
-                return false;
-        }
-    }
-
-    args.rval().setUndefined();
-    return true;
+        if (!PrintEnumeratedHelp(cx, global, nullptr, false))
+            return false;
+        return true;
+    }
+
+    RootedValue v(cx);
+
+    if (args[0].isPrimitive()) {
+        // help("foo")
+        JS_ReportErrorASCII(cx, "primitive arg");
+        return false;
+    }
+
+    RootedObject obj(cx, &args[0].toObject());
+    if (!obj)
+        return true;
+    bool isRegexp;
+    if (!JS_ObjectIsRegExp(cx, obj, &isRegexp))
+        return false;
+
+    if (isRegexp) {
+        // help(/pattern/)
+        return PrintEnumeratedHelp(cx, global, obj, false);
+    }
+
+    // help(function)
+    // help(namespace_obj)
+    return PrintHelp(cx, obj);
 }
 
 static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
 #define MSG_DEF(name, count, exception, format) \
     { #name, format, count, JSEXN_ERR } ,
 #include "jsshell.msg"
 #undef MSG_DEF
 };
--- a/js/src/shell/jsshell.cpp
+++ b/js/src/shell/jsshell.cpp
@@ -3,16 +3,18 @@
  * 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/. */
 
 // jsshell.cpp - Utilities for the JS shell
 
 #include "shell/jsshell.h"
 
+#include "mozilla/Sprintf.h"
+
 #include "jsapi.h"
 #include "jsfriendapi.h"
 
 #include "vm/StringBuffer.h"
 
 using namespace JS;
 
 namespace js {
@@ -33,24 +35,17 @@ namespace shell {
 bool
 GenerateInterfaceHelp(JSContext* cx, HandleObject obj, const char* name)
 {
     AutoIdVector idv(cx);
     if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &idv))
         return false;
 
     StringBuffer buf(cx);
-    if (!buf.append(name, strlen(name)) || !buf.append(" - interface object", 19))
-        return false;
-    RootedString s(cx, buf.finishString());
-    if (!s || !JS_DefineProperty(cx, obj, "usage", s, 0))
-        return false;
-    buf.clear();
-
-    bool first = true;
+    int numEntries = 0;
     for (size_t i = 0; i < idv.length(); i++) {
         RootedId id(cx, idv[i]);
         RootedValue v(cx);
         if (!JS_GetPropertyById(cx, obj, id, &v))
             return false;
         if (!v.isObject())
             continue;
         RootedObject prop(cx, &v.toObject());
@@ -59,29 +54,40 @@ GenerateInterfaceHelp(JSContext* cx, Han
         RootedValue help(cx);
         if (!JS_GetProperty(cx, prop, "usage", &usage))
             return false;
         if (!JS_GetProperty(cx, prop, "help", &help))
             return false;
         if (!usage.isString() && !help.isString())
             continue;
 
-        if (!first && !buf.append("\n"))
+        if (numEntries && !buf.append("\n"))
             return false;
-        first = false;
+        numEntries++;
 
         if (!buf.append("  ", 2))
             return false;
 
         if (!buf.append(usage.isString() ? usage.toString() : JSID_TO_FLAT_STRING(id)))
             return false;
     }
 
+    RootedString s(cx, buf.finishString());
+    if (!s || !JS_DefineProperty(cx, obj, "help", s, 0))
+        return false;
+
+    buf.clear();
+    if (!buf.append(name, strlen(name)) || !buf.append(" - interface object with ", 25))
+        return false;
+    char cbuf[100];
+    SprintfLiteral(cbuf, "%d %s", numEntries, numEntries == 1 ? "entry" : "entries");
+    if (!buf.append(cbuf, strlen(cbuf)))
+        return false;
     s = buf.finishString();
-    if (!s || !JS_DefineProperty(cx, obj, "help", s, 0))
+    if (!s || !JS_DefineProperty(cx, obj, "usage", s, 0))
         return false;
 
     return true;
 }
 
 bool
 CreateAlias(JSContext* cx, const char* dstName, JS::HandleObject namespaceObj, const char* srcName)
 {
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -366,17 +366,17 @@ ArgumentsObject::createForIon(JSContext*
 }
 
 /* static */ ArgumentsObject*
 ArgumentsObject::finishForIon(JSContext* cx, jit::JitFrameLayout* frame,
                               JSObject* scopeChain, ArgumentsObject* obj)
 {
     // JIT code calls this directly (no callVM), because it's faster, so we're
     // not allowed to GC in here.
-    JS::AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     JSFunction* callee = jit::CalleeTokenToFunction(frame->calleeToken());
     RootedObject callObj(cx, scopeChain->is<CallObject>() ? scopeChain : nullptr);
     CopyJitFrameArgs copy(frame, callObj);
 
     unsigned numActuals = frame->numActualArgs();
     unsigned numFormals = callee->nargs();
     unsigned numArgs = Max(numActuals, numFormals);
@@ -850,22 +850,25 @@ void
 ArgumentsObject::trace(JSTracer* trc, JSObject* obj)
 {
     ArgumentsObject& argsobj = obj->as<ArgumentsObject>();
     if (ArgumentsData* data = argsobj.data()) // Template objects have no ArgumentsData.
         TraceRange(trc, data->numArgs, data->begin(), js_arguments_str);
 }
 
 /* static */ size_t
-ArgumentsObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src)
+ArgumentsObject::objectMoved(JSObject* dst, JSObject* src)
 {
     ArgumentsObject* ndst = &dst->as<ArgumentsObject>();
-    ArgumentsObject* nsrc = &src->as<ArgumentsObject>();
+    const ArgumentsObject* nsrc = &src->as<ArgumentsObject>();
     MOZ_ASSERT(ndst->data() == nsrc->data());
 
+    if (!IsInsideNursery(src))
+        return 0;
+
     Nursery& nursery = dst->zone()->group()->nursery();
 
     size_t nbytesTotal = 0;
     if (!nursery.isInside(nsrc->data())) {
         nursery.removeMallocedBuffer(nsrc->data());
     } else {
         AutoEnterOOMUnsafeRegion oomUnsafe;
         uint32_t nbytes = ArgumentsData::bytesRequired(nsrc->data()->numArgs);
@@ -912,31 +915,36 @@ const ClassOps MappedArgumentsObject::cl
     ArgumentsObject::obj_mayResolve,
     ArgumentsObject::finalize,
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
     ArgumentsObject::trace
 };
 
+const js::ClassExtension MappedArgumentsObject::classExt_ = {
+    nullptr,                      /* weakmapKeyDelegateOp */
+    ArgumentsObject::objectMoved  /* objectMovedOp */
+};
+
 const ObjectOps MappedArgumentsObject::objectOps_ = {
     nullptr,                 /* lookupProperty */
     MappedArgumentsObject::obj_defineProperty
 };
 
 const Class MappedArgumentsObject::class_ = {
     "Arguments",
     JSCLASS_DELAY_METADATA_BUILDER |
     JSCLASS_HAS_RESERVED_SLOTS(MappedArgumentsObject::RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
     JSCLASS_SKIP_NURSERY_FINALIZE |
     JSCLASS_BACKGROUND_FINALIZE,
     &MappedArgumentsObject::classOps_,
     nullptr,
-    nullptr,
+    &MappedArgumentsObject::classExt_,
     &MappedArgumentsObject::objectOps_
 };
 
 /*
  * Unmapped arguments is significantly less magical than mapped arguments, so
  * it is represented by a different class while sharing some functionality.
  */
 const ClassOps UnmappedArgumentsObject::classOps_ = {
@@ -948,17 +956,24 @@ const ClassOps UnmappedArgumentsObject::
     ArgumentsObject::obj_mayResolve,
     ArgumentsObject::finalize,
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
     ArgumentsObject::trace
 };
 
+const js::ClassExtension UnmappedArgumentsObject::classExt_ = {
+    nullptr,                      /* weakmapKeyDelegateOp */
+    ArgumentsObject::objectMoved  /* objectMovedOp */
+};
+
 const Class UnmappedArgumentsObject::class_ = {
     "Arguments",
     JSCLASS_DELAY_METADATA_BUILDER |
     JSCLASS_HAS_RESERVED_SLOTS(UnmappedArgumentsObject::RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
     JSCLASS_SKIP_NURSERY_FINALIZE |
     JSCLASS_BACKGROUND_FINALIZE,
-    &UnmappedArgumentsObject::classOps_
+    &UnmappedArgumentsObject::classOps_,
+    nullptr,
+    &UnmappedArgumentsObject::classExt_
 };
--- a/js/src/vm/ArgumentsObject.h
+++ b/js/src/vm/ArgumentsObject.h
@@ -356,17 +356,17 @@ class ArgumentsObject : public NativeObj
     }
     size_t sizeOfData() const {
         return ArgumentsData::bytesRequired(data()->numArgs) +
                (maybeRareData() ? RareArgumentsData::bytesRequired(initialLength()) : 0);
     }
 
     static void finalize(FreeOp* fop, JSObject* obj);
     static void trace(JSTracer* trc, JSObject* obj);
-    static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src);
+    static size_t objectMoved(JSObject* dst, JSObject* src);
 
     /* For jit use: */
     static size_t getDataSlotOffset() {
         return getFixedSlotOffset(DATA_SLOT);
     }
     static size_t getInitialLengthSlotOffset() {
         return getFixedSlotOffset(INITIAL_LENGTH_SLOT);
     }
@@ -394,16 +394,17 @@ class ArgumentsObject : public NativeObj
                                          ArgumentsData* data);
     static void MaybeForwardToCallObject(jit::JitFrameLayout* frame, HandleObject callObj,
                                          ArgumentsObject* obj, ArgumentsData* data);
 };
 
 class MappedArgumentsObject : public ArgumentsObject
 {
     static const ClassOps classOps_;
+    static const ClassExtension classExt_;
     static const ObjectOps objectOps_;
 
   public:
     static const Class class_;
 
     JSFunction& callee() const {
         return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
     }
@@ -423,16 +424,17 @@ class MappedArgumentsObject : public Arg
     static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp);
     static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
                                    Handle<JS::PropertyDescriptor> desc, ObjectOpResult& result);
 };
 
 class UnmappedArgumentsObject : public ArgumentsObject
 {
     static const ClassOps classOps_;
+    static const ClassExtension classExt_;
 
   public:
     static const Class class_;
 
   private:
     static bool obj_enumerate(JSContext* cx, HandleObject obj);
     static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp);
 };
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -1249,25 +1249,27 @@ ArrayBufferObject::trace(JSTracer* trc, 
 
     JSObject* view = MaybeForwarded(buf.firstView());
     MOZ_ASSERT(view && view->is<InlineTransparentTypedObject>());
 
     TraceManuallyBarrieredEdge(trc, &view, "array buffer inline typed object owner");
     buf.setSlot(DATA_SLOT, PrivateValue(view->as<InlineTransparentTypedObject>().inlineTypedMem()));
 }
 
-/* static */ void
-ArrayBufferObject::objectMoved(JSObject* obj, const JSObject* old)
+/* static */ size_t
+ArrayBufferObject::objectMoved(JSObject* obj, JSObject* old)
 {
     ArrayBufferObject& dst = obj->as<ArrayBufferObject>();
     const ArrayBufferObject& src = old->as<ArrayBufferObject>();
 
     // Fix up possible inline data pointer.
     if (src.hasInlineData())
         dst.setSlot(DATA_SLOT, PrivateValue(dst.inlineDataPointer()));
+
+    return 0;
 }
 
 ArrayBufferViewObject*
 ArrayBufferObject::firstView()
 {
     return getSlot(FIRST_VIEW_SLOT).isObject()
         ? static_cast<ArrayBufferViewObject*>(&getSlot(FIRST_VIEW_SLOT).toObject())
         : nullptr;
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -254,17 +254,17 @@ class ArrayBufferObject : public ArrayBu
     // initialize()d to become a real, content-visible ArrayBufferObject.
     static ArrayBufferObject* createEmpty(JSContext* cx);
 
     static void copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
                          Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
                          uint32_t count);
 
     static void trace(JSTracer* trc, JSObject* obj);
-    static void objectMoved(JSObject* obj, const JSObject* old);
+    static size_t objectMoved(JSObject* obj, JSObject* old);
 
     static BufferContents externalizeContents(JSContext* cx,
                                               Handle<ArrayBufferObject*> buffer,
                                               bool hasStealableContents);
     static BufferContents stealContents(JSContext* cx,
                                         Handle<ArrayBufferObject*> buffer,
                                         bool hasStealableContents);
 
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -401,30 +401,30 @@ NativeObject::growSlots(JSContext* cx, u
 
     return true;
 }
 
 /* static */ bool
 NativeObject::growSlotsDontReportOOM(JSContext* cx, NativeObject* obj, uint32_t newCount)
 {
     // IC code calls this directly.
-    AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     if (!obj->growSlots(cx, obj->numDynamicSlots(), newCount)) {
         cx->recoverFromOutOfMemory();
         return false;
     }
     return true;
 }
 
 /* static */ bool
 NativeObject::addDenseElementDontReportOOM(JSContext* cx, NativeObject* obj)
 {
     // IC code calls this directly.
-    AutoCheckCannotGC nogc;
+    AutoUnsafeCallWithABI unsafe;
 
     MOZ_ASSERT(obj->getDenseInitializedLength() == obj->getDenseCapacity());
     MOZ_ASSERT(!obj->denseElementsAreCopyOnWrite());
     MOZ_ASSERT(!obj->denseElementsAreFrozen());
     MOZ_ASSERT(!obj->isIndexed());
     MOZ_ASSERT(!obj->is<TypedArrayObject>());
     MOZ_ASSERT_IF(obj->is<ArrayObject>(), obj->as<ArrayObject>().lengthIsWritable());
 
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -1512,27 +1512,17 @@ class MutableWrappedPtrOperations<ScopeI
 
 namespace JS {
 
 template <>
 struct GCPolicy<js::ScopeKind> : public IgnoreGCPolicy<js::ScopeKind>
 { };
 
 template <typename T>
-struct ScopeDataGCPolicy
-{
-    static T initial() {
-        return nullptr;
-    }
-
-    static void trace(JSTracer* trc, T* vp, const char* name) {
-        if (*vp)
-            (*vp)->trace(trc);
-    }
-};
+struct ScopeDataGCPolicy : public NonGCPointerPolicy<T> {};
 
 #define DEFINE_SCOPE_DATA_GCPOLICY(Data)                        \
     template <>                                                 \
     struct MapTypeToRootKind<Data*> {                           \
         static const RootKind kind = RootKind::Traceable;       \
     };                                                          \
     template <>                                                 \
     struct GCPolicy<Data*> : public ScopeDataGCPolicy<Data*>    \
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -175,44 +175,36 @@ TypedArrayObject::finalize(FreeOp* fop, 
     if (curObj->hasBuffer())
         return;
 
     // Free the data slot pointer if it does not point into the old JSObject.
     if (!curObj->hasInlineElements())
         js_free(curObj->elements());
 }
 
-/* static */ void
-TypedArrayObject::objectMoved(JSObject* obj, const JSObject* old)
+/* static */ size_t
+TypedArrayObject::objectMoved(JSObject* obj, JSObject* old)
 {
     TypedArrayObject* newObj = &obj->as<TypedArrayObject>();
-    const TypedArrayObject* oldObj = &old->as<TypedArrayObject>();
-
-    // Typed arrays with a buffer object do not need an update.
-    if (oldObj->hasBuffer())
-        return;
-
-    // Update the data slot pointer if it points to the old JSObject.
-    if (oldObj->hasInlineElements())
-        newObj->setInlineElements();
-}
-
-/* static */ size_t
-TypedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* obj, const JSObject* old,
-                                           gc::AllocKind newAllocKind)
-{
-    TypedArrayObject* newObj = &obj->as<TypedArrayObject>();
-    const TypedArrayObject* oldObj = &old->as<TypedArrayObject>();
+    TypedArrayObject* oldObj = &old->as<TypedArrayObject>();
     MOZ_ASSERT(newObj->elementsRaw() == oldObj->elementsRaw());
     MOZ_ASSERT(obj->isTenured());
 
     // Typed arrays with a buffer object do not need an update.
     if (oldObj->hasBuffer())
         return 0;
 
+    if (!IsInsideNursery(old)) {
+        // Update the data slot pointer if it points to the old JSObject.
+        if (oldObj->hasInlineElements())
+            newObj->setInlineElements();
+
+        return 0;
+    }
+
     Nursery& nursery = obj->zone()->group()->nursery();
     void* buf = oldObj->elements();
 
     if (!nursery.isInside(buf)) {
         nursery.removeMallocedBuffer(buf);
         return 0;
     }
 
@@ -229,16 +221,17 @@ JS_FOR_EACH_TYPED_ARRAY(OBJECT_MOVED_TYP
 #undef OBJECT_MOVED_TYPED_ARRAY
       default:
         MOZ_CRASH("Unsupported TypedArray type");
     }
 
     size_t headerSize = dataOffset() + sizeof(HeapSlot);
 
     // See AllocKindForLazyBuffer.
+    AllocKind newAllocKind = obj->asTenured().getAllocKind();
     MOZ_ASSERT_IF(nbytes == 0, headerSize + sizeof(uint8_t) <= GetGCKindBytes(newAllocKind));
 
     if (headerSize + nbytes <= GetGCKindBytes(newAllocKind)) {
         MOZ_ASSERT(oldObj->hasInlineElements());
 #ifdef DEBUG
         if (nbytes == 0) {
             uint8_t* output = newObj->fixedData(TypedArrayObject::FIXED_DATA_START);
             output[0] = ZeroLengthArrayData;
@@ -255,18 +248,18 @@ JS_FOR_EACH_TYPED_ARRAY(OBJECT_MOVED_TYP
         MOZ_ASSERT(!nursery.isInside(data));
         newObj->initPrivate(data);
     }
 
     mozilla::PodCopy(newObj->elements(), oldObj->elements(), nbytes);
 
     // Set a forwarding pointer for the element buffers in case they were
     // preserved on the stack by Ion.
-    nursery.maybeSetForwardingPointer(trc, oldObj->elements(), newObj->elements(),
-                                      /* direct = */nbytes >= sizeof(uintptr_t));
+    nursery.setForwardingPointerWhileTenuring(oldObj->elements(), newObj->elements(),
+                                              /* direct = */nbytes >= sizeof(uintptr_t));
 
     return newObj->hasInlineElements() ? 0 : nbytes;
 }
 
 bool
 TypedArrayObject::hasInlineElements() const
 {
     return elements() == this->fixedData(TypedArrayObject::FIXED_DATA_START) &&
--- a/js/src/vm/TypedArrayObject.h
+++ b/js/src/vm/TypedArrayObject.h
@@ -268,19 +268,17 @@ class TypedArrayObject : public NativeOb
         // Note, do not check whether shared or not
         // Keep synced with js::Get<Type>ArrayLengthAndData in jsfriendapi.h!
         return static_cast<void*>(getPrivate(DATA_SLOT));
     }
 
   public:
     static void trace(JSTracer* trc, JSObject* obj);
     static void finalize(FreeOp* fop, JSObject* obj);
-    static void objectMoved(JSObject* obj, const JSObject* old);
-    static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* obj, const JSObject* old,
-                                           gc::AllocKind allocKind);
+    static size_t objectMoved(JSObject* obj, JSObject* old);
 
     /* Initialization bits */
 
     template<Value ValueGetter(TypedArrayObject* tarr)>
     static bool
     GetterImpl(JSContext* cx, const CallArgs& args)
     {
         MOZ_ASSERT(is(args.thisv()));
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -1199,71 +1199,67 @@ UnboxedArrayObject::trace(JSTracer* trc,
         break;
 
       default:
         MOZ_CRASH();
     }
 }
 
 /* static */ void
-UnboxedArrayObject::objectMoved(JSObject* obj, const JSObject* old)
-{
-    UnboxedArrayObject& dst = obj->as<UnboxedArrayObject>();
-    const UnboxedArrayObject& src = old->as<UnboxedArrayObject>();
-
-    // Fix up possible inline data pointer.
-    if (src.hasInlineElements())
-        dst.setInlineElements();
-}
-
-/* static */ void
 UnboxedArrayObject::finalize(FreeOp* fop, JSObject* obj)
 {
     MOZ_ASSERT(!IsInsideNursery(obj));
     if (!obj->as<UnboxedArrayObject>().hasInlineElements())
         js_free(obj->as<UnboxedArrayObject>().elements());
 }
 
 /* static */ size_t
-UnboxedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
-                                             gc::AllocKind allocKind)
+UnboxedArrayObject::objectMoved(JSObject* dst, JSObject* src)
 {
     UnboxedArrayObject* ndst = &dst->as<UnboxedArrayObject>();
     UnboxedArrayObject* nsrc = &src->as<UnboxedArrayObject>();
     MOZ_ASSERT(ndst->elements() == nsrc->elements());
 
+    if (!IsInsideNursery(src)) {
+        // Fix up possible inline data pointer.
+        if (nsrc->hasInlineElements())
+            ndst->setInlineElements();
+
+        return 0;
+    }
+
     Nursery& nursery = dst->zone()->group()->nursery();
-
     if (!nursery.isInside(nsrc->elements())) {
         nursery.removeMallocedBuffer(nsrc->elements());
         return 0;
     }
 
     // Determine if we can use inline data for the target array. If this is
     // possible, the nursery will have picked an allocation size that is large
     // enough.
     size_t nbytes = nsrc->capacity() * nsrc->elementSize();
-    if (offsetOfInlineElements() + nbytes <= GetGCKindBytes(allocKind)) {
+    gc::AllocKind allocKind = dst->asTenured().getAllocKind();
+    if (offsetOfInlineElements() + nbytes <= gc::GetGCKindBytes(allocKind)) {
         ndst->setInlineElements();
     } else {
         MOZ_ASSERT(allocKind == gc::AllocKind::OBJECT0);
 
         AutoEnterOOMUnsafeRegion oomUnsafe;
         uint8_t* data = nsrc->zone()->pod_malloc<uint8_t>(nbytes);
         if (!data)
             oomUnsafe.crash("Failed to allocate unboxed array elements while tenuring.");
         ndst->elements_ = data;
     }
 
     PodCopy(ndst->elements(), nsrc->elements(), nsrc->initializedLength() * nsrc->elementSize());
 
     // Set a forwarding pointer for the element buffers in case they were
     // preserved on the stack by Ion.
     bool direct = nsrc->capacity() * nsrc->elementSize() >= sizeof(uintptr_t);
-    nursery.maybeSetForwardingPointer(trc, nsrc->elements(), ndst->elements(), direct);
+    nursery.setForwardingPointerWhileTenuring(nsrc->elements(), ndst->elements(), direct);
 
     return ndst->hasInlineElements() ? 0 : nbytes;
 }
 
 // Possible capacities for unboxed arrays. Some of these capacities might seem
 // a little weird, but were chosen to allow the inline data of objects of each
 // size to be fully utilized for arrays of the various types on both 32 bit and
 // 64 bit platforms.
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -438,22 +438,19 @@ class UnboxedArrayObject : public Unboxe
     static bool convertToNativeWithGroup(JSContext* cx, JSObject* obj,
                                          ObjectGroup* group, Shape* shape);
     bool convertInt32ToDouble(JSContext* cx, ObjectGroup* group);
 
     void fillAfterConvert(JSContext* cx,
                           Handle<GCVector<Value>> values, size_t* valueCursor);
 
     static void trace(JSTracer* trc, JSObject* object);
-    static void objectMoved(JSObject* obj, const JSObject* old);
+    static size_t objectMoved(JSObject* obj, JSObject* old);
     static void finalize(FreeOp* fop, JSObject* obj);
 
-    static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
-                                           gc::AllocKind allocKind);
-
     uint8_t* elements() {
         return elements_;
     }
 
     bool hasInlineElements() const {
         return elements_ == &inlineElements_[0];
     }
 
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -419,26 +419,28 @@ sandbox_finalize(js::FreeOp* fop, JSObje
         return;
     }
 
     static_cast<SandboxPrivate*>(sop)->ForgetGlobalObject(obj);
     DestroyProtoAndIfaceCache(obj);
     DeferredFinalize(sop);
 }
 
-static void
-sandbox_moved(JSObject* obj, const JSObject* old)
+static size_t
+sandbox_moved(JSObject* obj, JSObject* old)
 {
     // Note that this hook can be called before the private pointer is set. In
     // this case the SandboxPrivate will not exist yet, so there is nothing to
     // do.
     nsIScriptObjectPrincipal* sop =
         static_cast<nsIScriptObjectPrincipal*>(xpc_GetJSPrivate(obj));
-    if (sop)
-        static_cast<SandboxPrivate*>(sop)->ObjectMoved(obj, old);
+    if (!sop)
+        return 0;
+
+    return static_cast<SandboxPrivate*>(sop)->ObjectMoved(obj, old);
 }
 
 static bool
 writeToProto_setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                          JS::MutableHandleValue vp, JS::ObjectOpResult& result)
 {
     RootedObject proto(cx);
     if (!JS_GetPrototype(cx, obj, &proto))
--- a/js/xpconnect/src/SandboxPrivate.h
+++ b/js/xpconnect/src/SandboxPrivate.h
@@ -48,19 +48,20 @@ public:
         ClearWrapper(obj);
     }
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override
     {
         MOZ_CRASH("SandboxPrivate doesn't use DOM bindings!");
     }
 
-    void ObjectMoved(JSObject* obj, const JSObject* old)
+    size_t ObjectMoved(JSObject* obj, JSObject* old)
     {
         UpdateWrapper(obj, old);
+        return 0;
     }
 
 private:
     virtual ~SandboxPrivate() { }
 
     nsCOMPtr<nsIPrincipal> mPrincipal;
 };
 
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -540,25 +540,26 @@ WrappedNativeFinalize(js::FreeOp* fop, J
         return;
 
     XPCWrappedNative* wrapper = static_cast<XPCWrappedNative*>(p);
     if (helperType == WN_HELPER)
         wrapper->GetScriptable()->Finalize(wrapper, js::CastToJSFreeOp(fop), obj);
     wrapper->FlatJSObjectFinalized();
 }
 
-static void
-WrappedNativeObjectMoved(JSObject* obj, const JSObject* old)
+static size_t
+WrappedNativeObjectMoved(JSObject* obj, JSObject* old)
 {
     nsISupports* p = static_cast<nsISupports*>(xpc_GetJSPrivate(obj));
     if (!p)
-        return;
+        return 0;
 
     XPCWrappedNative* wrapper = static_cast<XPCWrappedNative*>(p);
     wrapper->FlatJSObjectMoved(obj, old);
+    return 0;
 }
 
 void
 XPC_WN_NoHelper_Finalize(js::FreeOp* fop, JSObject* obj)
 {
     WrappedNativeFinalize(fop, obj, WN_NOHELPER);
 }
 
@@ -1004,23 +1005,26 @@ static void
 XPC_WN_Shared_Proto_Finalize(js::FreeOp* fop, JSObject* obj)
 {
     // This can be null if xpc shutdown has already happened
     XPCWrappedNativeProto* p = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj);
     if (p)
         p->JSProtoObjectFinalized(fop, obj);
 }
 
-static void
-XPC_WN_Shared_Proto_ObjectMoved(JSObject* obj, const JSObject* old)
+static size_t
+XPC_WN_Shared_Proto_ObjectMoved(JSObject* obj, JSObject* old)
 {
     // This can be null if xpc shutdown has already happened
     XPCWrappedNativeProto* p = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj);
-    if (p)
-        p->JSProtoObjectMoved(obj, old);
+    if (!p)
+        return 0;
+
+    p->JSProtoObjectMoved(obj, old);
+    return 0;
 }
 
 static void
 XPC_WN_Shared_Proto_Trace(JSTracer* trc, JSObject* obj)
 {
     // This can be null if xpc shutdown has already happened
     XPCWrappedNativeProto* p =
         (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj);
@@ -1205,24 +1209,25 @@ XPC_WN_TearOff_Finalize(js::FreeOp* fop,
 {
     XPCWrappedNativeTearOff* p = (XPCWrappedNativeTearOff*)
         xpc_GetJSPrivate(obj);
     if (!p)
         return;
     p->JSObjectFinalized();
 }
 
-static void
-XPC_WN_TearOff_ObjectMoved(JSObject* obj, const JSObject* old)
+static size_t
+XPC_WN_TearOff_ObjectMoved(JSObject* obj, JSObject* old)
 {
     XPCWrappedNativeTearOff* p = (XPCWrappedNativeTearOff*)
         xpc_GetJSPrivate(obj);
     if (!p)
-        return;
+        return 0;
     p->JSObjectMoved(obj, old);
+    return 0;
 }
 
 // Make sure XPC_WRAPPER_FLAGS has no reserved slots, so our
 // XPC_WN_TEAROFF_RESERVED_SLOTS value is OK.
 
 static_assert(((XPC_WRAPPER_FLAGS >> JSCLASS_RESERVED_SLOTS_SHIFT) &
                JSCLASS_RESERVED_SLOTS_MASK) == 0,
               "XPC_WRAPPER_FLAGS should not include any reserved slots");
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1400599-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+onload = function() {
+  document.documentElement.remove();
+}
+</script>
+<body style="overflow: scroll">
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -496,8 +496,9 @@ load 1381323.html
 asserts-if(!stylo,1) load 1388625-1.html # bug 1389286
 load 1390389.html
 load 1395591-1.html
 load 1395715-1.html
 load 1397398-1.html
 load 1397398-2.html
 load 1397398-3.html
 load 1398500.html
+load 1400599-1.html
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -8610,17 +8610,25 @@ nsCSSFrameConstructor::ContentRemoved(ns
 {
   MOZ_ASSERT(aChild);
   AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
   NS_PRECONDITION(mUpdateCount != 0,
                   "Should be in an update while destroying frames");
   nsPresContext* presContext = mPresShell->GetPresContext();
   MOZ_ASSERT(presContext, "Our presShell should have a valid presContext");
 
-  if (aChild == presContext->GetViewportScrollbarStylesOverrideElement()) {
+  // We want to detect when the viewport override element stored in the
+  // prescontext is in the subtree being removed.  Except in fullscreen cases
+  // (which are handled in Element::UnbindFromTree and do not get stored on the
+  // prescontext), the override element is always either the root element or a
+  // <body> child of the root element.  So we can only be removing the stored
+  // override element if the thing being removed is either the override element
+  // itself or the root element (which can be a parent of the override element).
+  if (aChild == presContext->GetViewportScrollbarStylesOverrideElement() ||
+      (!aContainer && aChild->IsElement())) {
     // We might be removing the element that we propagated viewport scrollbar
     // styles from.  Recompute those. (This clause covers two of the three
     // possible scrollbar-propagation sources: the <body> [as aChild or a
     // descendant] and the root node. The other possible scrollbar-propagation
     // source is a fullscreen element, and we have code elsewhere to update
     // scrollbars after fullscreen elements are removed -- specifically, it's
     // part of the fullscreen cleanup code called by Element::UnbindFromTree.
     // We don't handle the fullscreen case here, because it doesn't change the
--- a/layout/base/tests/chrome/chrome.ini
+++ b/layout/base/tests/chrome/chrome.ini
@@ -26,16 +26,17 @@ support-files =
 [test_bug533845.xul]
 skip-if = os == 'linux' && !debug # Bug 1208197
 [test_bug551434.html]
 [test_bug708062.html]
 [test_bug812817.xul]
 [test_bug847890_paintFlashing.html]
 [test_bug1018265.xul]
 [test_bug1041200.xul]
+skip-if = os == 'win' && bits == 64 # Bug 1272321
 support-files =
   bug1041200_frame.html
   bug1041200_window.html
 [test_chrome_content_integration.xul]
 [test_chrome_over_plugin.xul]
 support-files =
   chrome_over_plugin_window.xul
   chrome_over_plugin_window_frame.html
--- a/layout/reftests/abs-pos/reftest.list
+++ b/layout/reftests/abs-pos/reftest.list
@@ -48,15 +48,15 @@ fuzzy-if(/^Windows\x20NT\x206\.1/.test(h
 == table-footer-group-7.html table-internal-7-ref.html
 == continuation-positioned-inline-1.html continuation-positioned-inline-ref.html
 == continuation-positioned-inline-2.html continuation-positioned-inline-ref.html
 == scrollframe-1.html scrollframe-1-ref.html
 fuzzy-if(gtkWidget,1,1) fuzzy-if(Android,9,185) fuzzy-if(asyncPan&&!layersGPUAccelerated,140,144) == scrollframe-2.html scrollframe-2-ref.html #bug 756530
 fuzzy-if(gtkWidget,1,8) == select-1.html select-1-ref.html
 fuzzy-if(gtkWidget,1,8) == select-1-dynamic.html select-1-ref.html
 == select-2.html select-2-ref.html
-fuzzy-if(gtkWidget,1,19) fuzzy-if(Android,17,726) fuzzy-if(asyncPan&&!layersGPUAccelerated,110,114) fuzzy-if(browserIsRemote&&winWidget,110,114) == select-3.html select-3-ref.html
+fuzzy-if(gtkWidget,1,19) fuzzy-if(Android,17,726) fuzzy-if(asyncPan&&!layersGPUAccelerated,110,114) fuzzy-if(browserIsRemote&&winWidget,143,114) == select-3.html select-3-ref.html
 == multi-column-1.html multi-column-1-ref.html
 == button-1.html button-1-ref.html
 == button-2.html button-2-ref.html
 == relative-row-animation-1.html relative-row-animation-1-ref.html
 fuzzy-if(Android,12,50) == fixed-pos-auto-offset-1a.html fixed-pos-auto-offset-1-ref.html
 fuzzy-if(Android,12,50) == fixed-pos-auto-offset-1b.html fixed-pos-auto-offset-1-ref.html
--- a/layout/reftests/columns/reftest.list
+++ b/layout/reftests/columns/reftest.list
@@ -29,13 +29,13 @@ HTTP(..) == columnfill-auto-3.html colum
 == columnfill-overflow.html columnfill-overflow-ref.html
 == margin-collapsing-bug616722-1.html margin-collapsing-bug616722-1-ref.html
 == margin-collapsing-bug616722-2.html margin-collapsing-bug616722-2-ref.html
 == column-balancing-nested-000.html column-balancing-nested-000-ref.html
 == column-balancing-nested-001.html column-balancing-nested-001-ref.html
 == columnrule-overflow.html columnrule-overflow-ref.html
 == columns-table-caption-000.html columns-table-caption-000-ref.html
 == positioning-transforms-bug1112501.html positioning-transforms-bug1112501-ref.html
-fuzzy-if(browserIsRemote&&winWidget,140,276) == fieldset-columns-001.html fieldset-columns-001-ref.html
+fuzzy-if(browserIsRemote&&winWidget,142,276) == fieldset-columns-001.html fieldset-columns-001-ref.html
 == dynamic-change-with-overflow-1.html dynamic-change-with-overflow-1-ref.html
 == dynamic-text-indent-1.html dynamic-text-indent-1-ref.html
 == dynamic-text-indent-2.html dynamic-text-indent-2-ref.html
 == break-avoid-line-position-1.html break-avoid-line-position-1-ref.html
--- a/layout/reftests/forms/fieldset/reftest.list
+++ b/layout/reftests/forms/fieldset/reftest.list
@@ -1,16 +1,16 @@
 fuzzy-if(skiaContent,2,13) == dynamic-legend-scroll-1.html dynamic-legend-scroll-1-ref.html
 == fieldset-hidden-1.html fieldset-hidden-1-ref.html
 == fieldset-intrinsic-width-1.html fieldset-intrinsic-width-1-ref.html
 == fieldset-percentage-padding-1.html fieldset-percentage-padding-1-ref.html
 == fieldset-scroll-1.html fieldset-scroll-1-ref.html
 == fieldset-scrolled-1.html fieldset-scrolled-1-ref.html
 == fieldset-overflow-auto-1.html fieldset-overflow-auto-1-ref.html
-fuzzy-if(winWidget&&!layersGPUAccelerated,140,276) == positioned-container-1.html positioned-container-1-ref.html
+fuzzy-if(winWidget&&!layersGPUAccelerated,142,276) == positioned-container-1.html positioned-container-1-ref.html
 == relpos-legend-1.html relpos-legend-1-ref.html
 == relpos-legend-2.html relpos-legend-2-ref.html
 == relpos-legend-3.html relpos-legend-3-ref.html
 == relpos-legend-4.html relpos-legend-4-ref.html
 == sticky-legend-1.html sticky-legend-1-ref.html
 fuzzy-if(skiaContent,1,40768) == abs-pos-child-sizing.html abs-pos-child-sizing-ref.html
 == overflow-hidden.html overflow-hidden-ref.html
 == legend-rtl.html legend-rtl-ref.html
--- a/layout/reftests/forms/input/text/reftest.list
+++ b/layout/reftests/forms/input/text/reftest.list
@@ -1,10 +1,10 @@
 == bounds-1.html bounds-1-ref.html
-fuzzy-if(asyncPan&&!layersGPUAccelerated,140,111) == size-1.html size-1-ref.html
+fuzzy-if(asyncPan&&!layersGPUAccelerated,151,111) == size-1.html size-1-ref.html
 == size-2.html size-2-ref.html
 HTTP(..) == baseline-1.html baseline-1-ref.html
 HTTP(..) == centering-1.xul centering-1-ref.xul
 == dynamic-height-1.xul dynamic-height-1-ref.xul
 fuzzy-if(skiaContent,1,500) needs-focus == select.html select-ref.html
 == intrinsic-size.html intrinsic-size-ref.html
 == line-height-0.5.html line-height-1.0.html
 != line-height-1.5.html line-height-1.0.html
--- a/layout/reftests/forms/reftest.list
+++ b/layout/reftests/forms/reftest.list
@@ -1,10 +1,10 @@
 fuzzy-if(skiaContent,1,10) HTTP(..) == text-control-baseline-1.html text-control-baseline-1-ref.html
-fuzzy-if(cocoaWidget,16,64) fuzzy-if(Android,52,64) fuzzy-if(winWidget,88,400) == display-block-baselines-1.html display-block-baselines-1-ref.html # anti-aliasing issues
+fuzzy-if(cocoaWidget,16,64) fuzzy-if(Android,52,64) fuzzy-if(winWidget,88,624) == display-block-baselines-1.html display-block-baselines-1-ref.html # anti-aliasing issues
 == display-block-baselines-2.html display-block-baselines-2-ref.html
 == display-block-baselines-3.html display-block-baselines-3-ref.html
 == display-block-baselines-4.html display-block-baselines-4-ref.html
 fuzzy-if(Android,4,8) fuzzy-if(skiaContent,7,2) == display-block-baselines-5.html display-block-baselines-5-ref.html
 
 # button element
 include button/reftest.list
 
--- a/layout/reftests/position-dynamic-changes/relative/reftest.list
+++ b/layout/reftests/position-dynamic-changes/relative/reftest.list
@@ -1,5 +1,5 @@
-fuzzy-if(cocoaWidget,1,2) fuzzy-if(d2d,47,26) fuzzy-if(asyncPan&&!layersGPUAccelerated,140,716) == move-right-bottom.html move-right-bottom-ref.html
-fuzzy-if(cocoaWidget,1,2) fuzzy-if(asyncPan&&!layersGPUAccelerated,140,716) == move-top-left.html move-top-left-ref.html # Bug 688545
-fuzzy-if(cocoaWidget,1,3) fuzzy-if(asyncPan&&!layersGPUAccelerated,140,580) == move-right-bottom-table.html move-right-bottom-table-ref.html
-fuzzy-if(cocoaWidget,1,3) fuzzy-if(asyncPan&&!layersGPUAccelerated,140,580) == move-top-left-table.html move-top-left-table-ref.html # Bug 688545
+fuzzy-if(cocoaWidget,1,2) fuzzy-if(d2d,47,26) fuzzy-if(asyncPan&&!layersGPUAccelerated,149,716) == move-right-bottom.html move-right-bottom-ref.html
+fuzzy-if(cocoaWidget,1,2) fuzzy-if(asyncPan&&!layersGPUAccelerated,149,716) == move-top-left.html move-top-left-ref.html # Bug 688545
+fuzzy-if(cocoaWidget,1,3) fuzzy-if(asyncPan&&!layersGPUAccelerated,144,580) == move-right-bottom-table.html move-right-bottom-table-ref.html
+fuzzy-if(cocoaWidget,1,3) fuzzy-if(asyncPan&&!layersGPUAccelerated,144,580) == move-top-left-table.html move-top-left-table-ref.html # Bug 688545
 == percent.html percent-ref.html
--- a/layout/reftests/scrolling/reftest.list
+++ b/layout/reftests/scrolling/reftest.list
@@ -24,17 +24,17 @@ pref(layout.css.scroll-behavior.enabled,
 HTTP == simple-1.html simple-1.html?ref
 skip-if(styloVsGecko) HTTP == subpixel-1.html#d subpixel-1-ref.html#d # bug 1354406
 fuzzy-if(Android,4,120) HTTP == text-1.html text-1.html?ref
 fuzzy-if(Android,4,120) HTTP == text-2.html?up text-2.html?ref
 fuzzy-if(d2d,1,4) HTTP == transformed-1.html transformed-1.html?ref
 HTTP == transformed-1.html?up transformed-1.html?ref
 fuzzy-if(Android,5,20000) == uncovering-1.html uncovering-1-ref.html
 fuzzy-if(Android,5,20000) == uncovering-2.html uncovering-2-ref.html
-fuzzy-if(asyncPan&&!layersGPUAccelerated,140,4520) == less-than-scrollbar-height.html less-than-scrollbar-height-ref.html
+fuzzy-if(asyncPan&&!layersGPUAccelerated,149,4520) == less-than-scrollbar-height.html less-than-scrollbar-height-ref.html
 == huge-horizontal-overflow.html huge-horizontal-overflow-ref.html
 == huge-vertical-overflow.html huge-vertical-overflow-ref.html
 fuzzy-if(asyncPan&&!layersGPUAccelerated,102,6818) == iframe-scrolling-attr-1.html iframe-scrolling-attr-ref.html
 fuzzy-if(asyncPan&&!layersGPUAccelerated,140,6818) == iframe-scrolling-attr-2.html iframe-scrolling-attr-ref.html
 == frame-scrolling-attr-1.html frame-scrolling-attr-ref.html
 fuzzy-if(asyncPan&&!layersGPUAccelerated,102,2420) == frame-scrolling-attr-2.html frame-scrolling-attr-ref.html
 == move-item.html move-item-ref.html # bug 1125750
 == fractional-scroll-area.html?top=-0.4&outerBottom=100&innerBottom=200 fractional-scroll-area.html?top=0&outerBottom=100&innerBottom=200
--- a/layout/reftests/svg/svg-integration/reftest.list
+++ b/layout/reftests/svg/svg-integration/reftest.list
@@ -43,10 +43,10 @@ fuzzy(1,5000) == mask-clipPath-opacity-0
 fuzzy(1,5000) == mask-clipPath-opacity-01c.xhtml mask-clipPath-opacity-01-ref.xhtml
 fuzzy(1,5000) == mask-clipPath-opacity-01d.xhtml mask-clipPath-opacity-01-ref.xhtml
 fuzzy(1,5000) == mask-clipPath-opacity-01e.xhtml mask-clipPath-opacity-01-ref.xhtml
 
 == transform-outer-svg-01.xhtml transform-outer-svg-01-ref.xhtml
 
 # box-decoration-break tests
 fuzzy-if(Android,4,10) == box-decoration-break-01.xhtml box-decoration-break-01-ref.xhtml
-fuzzy(56,14) == box-decoration-break-02.xhtml box-decoration-break-02-ref.xhtml
+fuzzy(62,14) == box-decoration-break-02.xhtml box-decoration-break-02-ref.xhtml
 fuzzy(67,234) == box-decoration-break-03.xhtml box-decoration-break-01-ref.xhtml
--- a/layout/reftests/text-overflow/reftest.list
+++ b/layout/reftests/text-overflow/reftest.list
@@ -1,15 +1,15 @@
 == ellipsis-font-fallback.html ellipsis-font-fallback-ref.html
 == line-clipping.html line-clipping-ref.html
 fuzzy-if(Android,16,244) HTTP(..) == marker-basic.html marker-basic-ref.html  # Bug 1128229
 HTTP(..) == marker-string.html marker-string-ref.html
 skip-if(Android) HTTP(..) == bidi-simple.html bidi-simple-ref.html # Fails on Android due to anti-aliasing
 skip-if(!gtkWidget) fuzzy-if(gtkWidget,2,289) HTTP(..) == bidi-simple-scrolled.html bidi-simple-scrolled-ref.html # Fails on Windows and OSX due to anti-aliasing
-fuzzy-if(Android,24,4000) fuzzy-if(cocoaWidget,1,40) fuzzy-if(asyncPan&&!layersGPUAccelerated,140,1836) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264
+fuzzy-if(Android,24,4000) fuzzy-if(cocoaWidget,1,40) fuzzy-if(asyncPan&&!layersGPUAccelerated,149,1836) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264
 fuzzy(2,453) fuzzy-if(skiaContent,9,2100) fails-if(gtkWidget) HTTP(..) == anonymous-block.html anonymous-block-ref.html # gtkWidget:bug 1309103
 HTTP(..) == false-marker-overlap.html false-marker-overlap-ref.html
 HTTP(..) == visibility-hidden.html visibility-hidden-ref.html
 fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1724) fuzzy-if(gtkWidget,10,8) HTTP(..) == block-padding.html block-padding-ref.html
 HTTP(..) == quirks-decorations.html quirks-decorations-ref.html
 HTTP(..) == quirks-line-height.html quirks-line-height-ref.html
 HTTP(..) == standards-decorations.html standards-decorations-ref.html
 HTTP(..) == standards-line-height.html standards-line-height-ref.html
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -2642,16 +2642,17 @@ Gecko_RegisterNamespace(nsIAtom* aNamesp
     return -1;
   }
   return id;
 }
 
 bool
 Gecko_ShouldCreateStyleThreadPool()
 {
+  MOZ_ASSERT(NS_IsMainThread());
   return !mozilla::BrowserTabsRemoteAutostart() || XRE_IsContentProcess();
 }
 
 NS_IMPL_FFI_REFCOUNTING(nsCSSFontFaceRule, CSSFontFaceRule);
 
 nsCSSCounterStyleRule*
 Gecko_CSSCounterStyle_Create(nsIAtom* aName)
 {
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -393,16 +393,20 @@ enum nsStyleImageType {
   eStyleImageType_Image,
   eStyleImageType_Gradient,
   eStyleImageType_Element,
   eStyleImageType_URL
 };
 
 struct CachedBorderImageData
 {
+  ~CachedBorderImageData() {
+    PurgeCachedImages();
+  }
+
   // Caller are expected to ensure that the value of aSVGViewportSize is
   // different from the cached one since the method won't do the check.
   void SetCachedSVGViewportSize(const mozilla::Maybe<nsSize>& aSVGViewportSize);
   const mozilla::Maybe<nsSize>& GetCachedSVGViewportSize();
   void PurgeCachedImages();
   void SetSubImage(uint8_t aIndex, imgIContainer* aSubImage);
   imgIContainer* GetSubImage(uint8_t aIndex);
 
--- a/mobile/android/config/mozconfigs/common
+++ b/mobile/android/config/mozconfigs/common
@@ -5,29 +5,37 @@
 # This file is included at the top of all native android mozconfigs
 if [ "x$IS_NIGHTLY" = "xyes" ]; then
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
 fi
 
 MOZ_AUTOMATION_L10N_CHECK=0
 . "$topsrcdir/build/mozconfig.common"
 
-# In TaskCluster, the Java JRE/JDK are installed from tooltool, but that
-# install doesn't work on the old Buildbot mock builders (CentOS 6.2), so
-# the relevant env vars are not set up in that case, leaving the build to
-# run from the JRE/JDK in /usr/lib/jvm.
-if [ ! -f /etc/redhat-release ] || [ "$(< /etc/redhat-release)" != "CentOS release 6.2 (Final)" ]; then
+# For actual Android builds in TaskCluster, the system image is Debian,
+# and we use the Java JRE/JDK from the system, as well as the system
+# GCC for the host compiler.  l10n builds are still special, however:
+# they are run on older CentOS systems, and l10n builds on release are
+# still run on Buildbot.  So we have to set things up so this mozconfig
+# works in all cases.
+if [ -f /etc/debian_version ]; then
+    # We're on Debian, there's nothing to do.
+    true
+elif [ ! -f /etc/redhat-release ] || [ "$(< /etc/redhat-release)" != "CentOS release 6.2 (Final)" ]; then
     # set JAVA_HOME to find the JRE/JDK from tooltool.  Several scripts in the JDK
     # assume `java` is in PATH, so set that too.  To see how this tarball is built,
-    # see taskcluster/scripts/misc/repackage-jdk.sh
+    # see taskcluster/scripts/builder/build-android-dependencies/repackage-jdk-centos.sh
     export JAVA_HOME="$topsrcdir/java_home"
     export PATH="$PATH:$topsrcdir/java_home/bin"
 
     mk_add_options "export JAVA_HOME=$topsrcdir/java_home"
     mk_add_options "export PATH=$PATH:$topsrcdir/java_home/bin"
+
+    HOST_CC="$topsrcdir/gcc/bin/gcc"
+    HOST_CXX="$topsrcdir/gcc/bin/g++"
 fi
 
 ac_add_options --enable-elf-hack
 
 ANDROID_NDK_VERSION="r10e"
 ANDROID_NDK_VERSION_32BIT="r8c"
 
 # Build Fennec
@@ -93,19 +101,14 @@ else
     ac_add_options --with-pocket-api-keyfile="$topsrcdir/mobile/android/base/pocket-api-sandbox.token"
 fi
 
 export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=/builds/crash-stats-api.token
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
-# Use ccache
-
-HOST_CC="$topsrcdir/gcc/bin/gcc"
-HOST_CXX="$topsrcdir/gcc/bin/g++"
-
 . "$topsrcdir/build/unix/mozconfig.stdcxx"
 
 # Use libc++ as our C++ standard library
 ac_add_options --with-android-cxx-stl=libc++
 
 JS_BINARY="$topsrcdir/mobile/android/config/js_wrapper.sh"
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -1334,58 +1334,16 @@ NS_NewLocalFileStream(nsIFileStream **re
         rv = stream->Init(file, ioFlags, perm, behaviorFlags);
         if (NS_SUCCEEDED(rv))
             stream.forget(result);
     }
     return rv;
 }
 
 nsresult
-NS_BackgroundInputStream(nsIInputStream **result,
-                         nsIInputStream  *stream,
-                         uint32_t         segmentSize /* = 0 */,
-                         uint32_t         segmentCount /* = 0 */)
-{
-    nsresult rv;
-    nsCOMPtr<nsIStreamTransportService> sts =
-        do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
-    if (NS_SUCCEEDED(rv)) {
-        nsCOMPtr<nsITransport> inTransport;
-        rv = sts->CreateInputTransport(stream, int64_t(-1), int64_t(-1),
-                                       true, getter_AddRefs(inTransport));
-        if (NS_SUCCEEDED(rv))
-            rv = inTransport->OpenInputStream(nsITransport::OPEN_BLOCKING,
-                                              segmentSize, segmentCount,
-                                              result);
-    }
-    return rv;
-}
-
-nsresult
-NS_BackgroundOutputStream(nsIOutputStream **result,
-                          nsIOutputStream  *stream,
-                          uint32_t          segmentSize  /* = 0 */,
-                          uint32_t          segmentCount /* = 0 */)
-{
-    nsresult rv;
-    nsCOMPtr<nsIStreamTransportService> sts =
-        do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
-    if (NS_SUCCEEDED(rv)) {
-        nsCOMPtr<nsITransport> inTransport;
-        rv = sts->CreateOutputTransport(stream, int64_t(-1), int64_t(-1),
-                                        true, getter_AddRefs(inTransport));
-        if (NS_SUCCEEDED(rv))
-            rv = inTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING,
-                                               segmentSize, segmentCount,
-                                               result);
-    }
-    return rv;
-}
-
-nsresult
 NS_NewBufferedOutputStream(nsIOutputStream **result,
                            nsIOutputStream  *str,
                            uint32_t          bufferSize)
 {
     nsresult rv;
     nsCOMPtr<nsIBufferedOutputStream> out =
         do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
     if (NS_SUCCEEDED(rv)) {
--- a/netwerk/base/nsNetUtil.h
+++ b/netwerk/base/nsNetUtil.h
@@ -507,32 +507,16 @@ nsresult NS_NewSafeLocalFileOutputStream
                                          int32_t           behaviorFlags = 0);
 
 nsresult NS_NewLocalFileStream(nsIFileStream **result,
                                nsIFile        *file,
                                int32_t         ioFlags       = -1,
                                int32_t         perm          = -1,
                                int32_t         behaviorFlags = 0);
 
-// returns the input end of a pipe.  the output end of the pipe
-// is attached to the original stream.  data from the original
-// stream is read into the pipe on a background thread.
-nsresult NS_BackgroundInputStream(nsIInputStream **result,
-                                  nsIInputStream  *stream,
-                                  uint32_t         segmentSize  = 0,
-                                  uint32_t         segmentCount = 0);
-
-// returns the output end of a pipe.  the input end of the pipe
-// is attached to the original stream.  data written to the pipe
-// is copied to the original stream on a background thread.
-nsresult NS_BackgroundOutputStream(nsIOutputStream **result,
-                                   nsIOutputStream  *stream,
-                                   uint32_t          segmentSize  = 0,
-                                   uint32_t          segmentCount = 0);
-
 MOZ_MUST_USE nsresult
 NS_NewBufferedInputStream(nsIInputStream **result,
                           nsIInputStream  *str,
                           uint32_t         bufferSize);
 
 // note: the resulting stream can be QI'ed to nsISafeOutputStream iff the
 // provided stream supports it.
 nsresult NS_NewBufferedOutputStream(nsIOutputStream **result,
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -377,17 +377,18 @@ nsHttpTransaction::Init(uint32_t caps,
         if (NS_FAILED(rv)) return rv;
 
         rv = multi->AppendStream(requestBody);
         if (NS_FAILED(rv)) return rv;
 
         // wrap the multiplexed input stream with a buffered input stream, so
         // that we write data in the largest chunks possible.  this is actually
         // necessary to workaround some common server bugs (see bug 137155).
-        rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream), multi,
+        nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multi));
+        rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream), stream,
                                        nsIOService::gDefaultSegmentSize);
         if (NS_FAILED(rv)) return rv;
     } else {
         mRequestStream = headers;
     }
 
     nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(mChannel);
     nsIInputChannelThrottleQueue* queue;
--- a/netwerk/test/browser/browser.ini
+++ b/netwerk/test/browser/browser.ini
@@ -1,12 +1,12 @@
 [DEFAULT]
 support-files =
   dummy.html
 
 [browser_about_cache.js]
 [browser_NetUtil.js]
 [browser_child_resource.js]
-skip-if = !crashreporter || (e10s && debug && os == "linux" && bits == 64)
+skip-if = !crashreporter || (e10s && debug && os == "linux" && bits == 64) || debug # Bug 1370783
 [browser_post_file.js]
 [browser_nsIFormPOSTActionChannel.js]
 skip-if = e10s # protocol handler and channel does not work in content process
 [browser_resource_navigation.js]
--- a/nsprpub/TAG-INFO
+++ b/nsprpub/TAG-INFO
@@ -1,1 +1,1 @@
-NSPR_4_17_BETA2
+NSPR_4_17_RTM
--- a/nsprpub/config/prdepend.h
+++ b/nsprpub/config/prdepend.h
@@ -5,9 +5,8 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSPR in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
-
--- a/nsprpub/pr/include/prinit.h
+++ b/nsprpub/pr/include/prinit.h
@@ -26,21 +26,21 @@ PR_BEGIN_EXTERN_C
 /*
 ** NSPR's version is used to determine the likelihood that the version you
 ** used to build your component is anywhere close to being compatible with
 ** what is in the underlying library.
 **
 ** The format of the version string is
 **     "<major version>.<minor version>[.<patch level>] [<Beta>]"
 */
-#define PR_VERSION  "4.17 Beta"
+#define PR_VERSION  "4.17"
 #define PR_VMAJOR   4
 #define PR_VMINOR   17
 #define PR_VPATCH   0
-#define PR_BETA     PR_TRUE
+#define PR_BETA     PR_FALSE
 
 /*
 ** PRVersionCheck
 **
 ** The basic signature of the function that is called to provide version
 ** checking. The result will be a boolean that indicates the likelihood
 ** that the underling library will perform as the caller expects.
 **
--- a/old-configure.in
+++ b/old-configure.in
@@ -43,17 +43,17 @@ dnl ====================================
 _SUBDIR_HOST_LDFLAGS="$HOST_LDFLAGS"
 _SUBDIR_CONFIG_ARGS="$ac_configure_args"
 
 dnl Set the version number of the libs included with mozilla
 dnl ========================================================
 MOZJPEG=62
 MOZPNG=10631
 NSPR_VERSION=4
-NSPR_MINVER=4.16
+NSPR_MINVER=4.17
 NSS_VERSION=3
 
 dnl Set the minimum version of toolkit libs used by mozilla
 dnl ========================================================
 GLIB_VERSION=2.22
 # 2_26 is the earliest version we can set GLIB_VERSION_MIN_REQUIRED.
 # The macro won't be used when compiling with earlier versions anyway.
 GLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_26
--- a/taskcluster/ci/build/android-stuff.yml
+++ b/taskcluster/ci/build/android-stuff.yml
@@ -53,17 +53,17 @@ android-test/opt:
         job-name: android-test
     treeherder:
         platform: android-4-0-armv7-api16/opt
         kind: build
         tier: 2
         symbol: tc(test)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
-        docker-image: {in-tree: desktop-build}
+        docker-image: {in-tree: android-build}
         env:
             GRADLE_USER_HOME: "/builds/worker/workspace/build/src/dotgradle"
             PERFHERDER_EXTRA_OPTIONS: android-test
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-frontend/releng.manifest"
         artifacts:
           - name: public/android/unittest
             path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/reports/tests
             type: directory
@@ -80,32 +80,33 @@ android-test/opt:
             - platform_supports_post_upload_to_latest.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: android-test
         tooltool-downloads: internal
     optimizations:
       - - skip-unless-changed
         - - "mobile/android/base/**"
+          - "mobile/android/config/**"
           - "mobile/android/tests/background/junit4/**"
           - "**/*.gradle"
 
 android-lint/opt:
     description: "Android lint"
     index:
         product: mobile
         job-name: android-lint
     treeherder:
         platform: android-4-0-armv7-api16/opt
         kind: build
         tier: 2
         symbol: tc(lint)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
-        docker-image: {in-tree: desktop-build}
+        docker-image: {in-tree: android-build}
         env:
             GRADLE_USER_HOME: "/builds/worker/workspace/build/src/dotgradle"
             PERFHERDER_EXTRA_OPTIONS: android-lint
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-frontend/releng.manifest"
         artifacts:
           - name: public/android/lint/lint-results-officialAustralisDebug.html
             path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/outputs/lint-results-officialAustralisDebug.html
             type: file
@@ -143,32 +144,33 @@ android-lint/opt:
       - - skip-unless-changed
         - - "mobile/android/**/*.java"
           - "mobile/android/**/*.jpeg"
           - "mobile/android/**/*.jpg"
           - "mobile/android/**/*.png"
           - "mobile/android/**/*.svg"
           - "mobile/android/**/*.xml" # Manifest & android resources
           - "mobile/android/**/Makefile.in"
+          - "mobile/android/config/**"
           - "mobile/android/**/moz.build"
           - "**/*.gradle"
 
 android-checkstyle/opt:
     description: "Android checkstyle"
     index:
         product: mobile
         job-name: android-checkstyle
     treeherder:
         platform: android-4-0-armv7-api16/opt
         kind: build
         tier: 2
         symbol: tc(checkstyle)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
-        docker-image: {in-tree: desktop-build}
+        docker-image: {in-tree: android-build}
         env:
             GRADLE_USER_HOME: "/builds/worker/workspace/build/src/dotgradle"
             PERFHERDER_EXTRA_OPTIONS: android-checkstyle
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-frontend/releng.manifest"
         artifacts:
           - name: public/android/checkstyle/checkstyle.html
             path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/reports/checkstyle/checkstyle.html
             type: file
@@ -190,32 +192,33 @@ android-checkstyle/opt:
         secrets: true
         custom-build-variant-cfg: android-checkstyle
         tooltool-downloads: internal
     optimizations:
       - - skip-unless-changed
         - - "mobile/android/**/checkstyle.xml"
           - "mobile/android/**/*.java"
           - "mobile/android/**/Makefile.in"
+          - "mobile/android/config/**"
           - "mobile/android/**/moz.build"
           - "**/*.gradle"
 
 android-findbugs/opt:
     description: "Android findbugs"
     index:
         product: mobile
         job-name: android-findbugs
     treeherder:
         platform: android-4-0-armv7-api16/opt
         kind: build
         tier: 2
         symbol: tc(findbugs)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
-        docker-image: {in-tree: desktop-build}
+        docker-image: {in-tree: android-build}
         env:
             GRADLE_USER_HOME: "/builds/worker/workspace/build/src/dotgradle"
             PERFHERDER_EXTRA_OPTIONS: android-findbugs
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-frontend/releng.manifest"
         artifacts:
           - name: public/android/findbugs/findbugs-officialAustralisDebug-output.html
             path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/outputs/findbugs/findbugs-officialAustralisDebug-output.html
             type: file
@@ -242,10 +245,11 @@ android-findbugs/opt:
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: android-findbugs
         tooltool-downloads: internal
     optimizations:
       - - skip-unless-changed
         - - "mobile/android/**/*.java"
           - "mobile/android/**/Makefile.in"
+          - "mobile/android/config/**"
           - "mobile/android/**/moz.build"
           - "**/*.gradle"
--- a/taskcluster/ci/build/android.yml
+++ b/taskcluster/ci/build/android.yml
@@ -3,196 +3,197 @@ android-api-16/debug:
     index:
         product: mobile
         job-name: android-api-16-debug
     treeherder:
         platform: android-4-0-armv7-api16/debug
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: api-16-debug
         tooltool-downloads: internal
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-x86/opt:
     description: "Android 4.2 x86 Opt"
     index:
         product: mobile
         job-name: android-x86-opt
     treeherder:
         platform: android-4-2-x86/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-x86/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: x86
         tooltool-downloads: internal
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-x86-nightly/opt:
     description: "Android 4.2 x86 Nightly"
     attributes:
         nightly: true
     index:
         product: mobile
         job-name: android-x86-opt
         type: nightly
     treeherder:
         platform: android-4-2-x86/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-x86/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
             - taskcluster_nightly.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: x86
         tooltool-downloads: internal
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-api-16/opt:
     description: "Android 4.0 api-16+ Opt"
     index:
         product: mobile
         job-name: android-api-16-opt
     treeherder:
         platform: android-4-0-armv7-api16/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: api-16
         tooltool-downloads: internal
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-api-16-nightly/opt:
     description: "Android 4.0 api-16+ Nightly"
     attributes:
         nightly: true
     index:
         product: mobile
         job-name: android-api-16-opt
         type: nightly-with-multi-l10n
     treeherder:
         platform: android-4-0-armv7-api16/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
             - taskcluster_nightly.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: api-16
         tooltool-downloads: internal
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-x86-old-id/opt:
     description: "Android 4.2 x86 Opt OldId"
     index:
         product: mobile
         job-name: android-x86-old-id-opt
     treeherder:
         platform: android-4-2-x86-old-id/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-x86/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: x86-old-id
         tooltool-downloads: internal
     run-on-projects: [ 'mozilla-central' ]
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-x86-old-id-nightly/opt:
     description: "Android 4.2 x86 OldId Nightly"
     attributes:
         nightly: true
     index:
         product: mobile
         job-name: android-x86-old-id-opt
         type: nightly
     treeherder:
         platform: android-4-2-x86-old-id/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-x86/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
@@ -200,61 +201,61 @@ android-x86-old-id-nightly/opt:
             - platform_supports_post_upload_to_latest.py
             - taskcluster_nightly.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: x86-old-id
         tooltool-downloads: internal
     run-on-projects: [ 'mozilla-central' ]
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-api-16-old-id/opt:
     description: "Android 4.0 api-16+ Opt OldId"
     index:
         product: mobile
         job-name: android-api-16-old-id-opt
     treeherder:
         platform: android-4-0-armv7-api16-old-id/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: api-16-old-id
         tooltool-downloads: internal
     run-on-projects: [ 'mozilla-central' ]
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-api-16-old-id-nightly/opt:
     description: "Android 4.0 api-16+ OldId Nightly"
     attributes:
         nightly: true
     index:
         product: mobile
         job-name: android-api-16-old-id-opt
         type: nightly-with-multi-l10n
     treeherder:
         platform: android-4-0-armv7-api16-old-id/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
@@ -262,30 +263,30 @@ android-api-16-old-id-nightly/opt:
             - platform_supports_post_upload_to_latest.py
             - taskcluster_nightly.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: api-16-old-id
         tooltool-downloads: internal
     run-on-projects: [ 'mozilla-central' ]
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-api-16-gradle/opt:
     description: "Android 4.0 api-16+ (Gradle) Opt"
     index:
         product: mobile
         job-name: android-api-16-gradle-opt
     treeherder:
         platform: android-api-16-gradle/opt
         symbol: tc(Bg)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             # Bug 1292762 - Set GRADLE_USER_HOME to avoid sdk-manager-plugin intermittent
             GRADLE_USER_HOME: /builds/worker/workspace/build/src/dotgradle
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
         artifacts:
           - name: public/android/maven
             path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
@@ -303,70 +304,69 @@ android-api-16-gradle/opt:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: api-16-gradle
         tooltool-downloads: internal
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-aarch64/opt:
     description: "Android 5.0 AArch64 Opt"
     index:
         product: mobile
         job-name: android-aarch64-opt
     treeherder:
         platform: android-5-0-aarch64/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: aarch64
         tooltool-downloads: internal
     toolchains:
-        - linux64-gcc
         - linux64-sccache
 
 android-aarch64-nightly/opt:
     description: "Android 5.0 AArch64 Nightly"
     attributes:
         nightly: true
     index:
         product: mobile
         job-name: android-aarch64-opt
         type: nightly
     treeherder:
         platform: android-5-0-aarch64/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
+        docker-image: {in-tree: android-build}
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
             - taskcluster_nightly.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: aarch64
         tooltool-downloads: internal
     toolchains:
-        - linux64-gcc
         - linux64-sccache
--- a/taskcluster/ci/test/test-platforms.yml
+++ b/taskcluster/ci/test/test-platforms.yml
@@ -244,38 +244,16 @@ windows10-64-devedition/opt:
         - desktop-screenshot-capture
         - windows-tests
 
 windows10-64-asan/opt:
     build-platform: win64-asan/opt
     test-sets:
         - common-tests
 
-# Windows8 tests; all via BBB
-windows8-64/debug:
-    build-platform: win64/debug
-    test-sets:
-        - windows8-tests
-windows8-64/opt:
-    build-platform: win64/opt
-    test-sets:
-        - windows8-tests
-windows8-64-pgo/opt:
-    build-platform: win64-pgo/opt
-    test-sets:
-        - windows8-tests
-windows8-64-nightly/opt:
-    build-platform: win64-nightly/opt
-    test-sets:
-        - windows8-tests
-windows8-64-devedition/opt:
-    build-platform: win64-devedition-nightly/opt
-    test-sets:
-        - windows8-tests
-
 ##
 # MacOS X platforms (matching /macosx.*/)
 
 macosx64/debug:
     build-platform: macosx64/debug
     test-sets:
         - macosx64-tests
 
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -310,14 +310,10 @@ android-gradle-tests:
     - mochitest-chrome
     - robocop
     - geckoview