merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 25 Jun 2015 11:09:46 +0200
changeset 282540 0b2f5e8b7be546f2cf649b13adea51b800e940d2
parent 282492 5ed704eb26e90d69f30c6662810f48b147550c4f (current diff)
parent 282539 3ef9ca698c521a6836d0e76dcf21389419fbc1a7 (diff)
child 282541 bbc26cc168c7239f0946028e90a0862149ee13ec
child 282554 3ce3e827434e3cc68e0e69dc04b205943b404be6
child 282602 5b38df79819f9a6d45ec057dad29cc000c19a425
push id897
push userjlund@mozilla.com
push dateMon, 14 Sep 2015 18:56:12 +0000
treeherdermozilla-release@9411e2d2b214 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone41.0a1
first release with
nightly linux32
0b2f5e8b7be5 / 41.0a1 / 20150625030202 / files
nightly linux64
0b2f5e8b7be5 / 41.0a1 / 20150625030202 / files
nightly mac
0b2f5e8b7be5 / 41.0a1 / 20150625030202 / files
nightly win32
0b2f5e8b7be5 / 41.0a1 / 20150625030202 / files
nightly win64
0b2f5e8b7be5 / 41.0a1 / 20150625030202 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
toolkit/mozapps/update/nsUpdateService.js
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -980,16 +980,19 @@ HyperTextAccessible::NativeAttributes()
     GetAccService()->MarkupAttributes(mContent, attributes);
 
   return attributes.forget();
 }
 
 nsIAtom*
 HyperTextAccessible::LandmarkRole() const
 {
+  if (!HasOwnContent())
+    return nullptr;
+
   // For the html landmark elements we expose them like we do ARIA landmarks to
   // make AT navigation schemes "just work".
   if (mContent->IsHTMLElement(nsGkAtoms::nav)) {
     return nsGkAtoms::navigation;
   }
 
   if (mContent->IsAnyOfHTMLElements(nsGkAtoms::header,
                                     nsGkAtoms::footer)) {
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -472,27 +472,28 @@ var FullScreen = {
     gNavToolbox.removeAttribute("fullscreenShouldAnimate");
     gNavToolbox.style.marginTop = "";
 
     if (!this._isChromeCollapsed) {
       return;
     }
 
     // Track whether mouse is near the toolbox
-    this._isChromeCollapsed = false;
     if (trackMouse && !this.useLionFullScreen) {
       let rect = gBrowser.mPanelContainer.getBoundingClientRect();
       this._mouseTargetRect = {
         top: rect.top + 50,
         bottom: rect.bottom,
         left: rect.left,
         right: rect.right
       };
       MousePosTracker.addListener(this);
     }
+
+    this._isChromeCollapsed = false;
   },
 
   hideNavToolbox: function (aAnimate = false) {
     if (this._isChromeCollapsed || !this._safeToCollapse())
       return;
 
     this._fullScrToggler.hidden = false;
 
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -23,17 +23,17 @@ namespace mozilla {
 
 using dom::URLSearchParams;
 
 void
 OriginAttributes::CreateSuffix(nsACString& aStr) const
 {
   MOZ_RELEASE_ASSERT(mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
 
-  nsRefPtr<URLSearchParams> usp = new URLSearchParams();
+  nsRefPtr<URLSearchParams> usp = new URLSearchParams(nullptr);
   nsAutoString value;
 
   if (mAppId != nsIScriptSecurityManager::NO_APP_ID) {
     value.AppendInt(mAppId);
     usp->Set(NS_LITERAL_STRING("appId"), value);
   }
 
   if (mInBrowser) {
@@ -103,18 +103,18 @@ OriginAttributes::PopulateFromSuffix(con
   if (aStr.IsEmpty()) {
     return true;
   }
 
   if (aStr[0] != '!') {
     return false;
   }
 
-  nsRefPtr<URLSearchParams> usp = new URLSearchParams();
-  usp->ParseInput(Substring(aStr, 1, aStr.Length() - 1), nullptr);
+  nsRefPtr<URLSearchParams> usp = new URLSearchParams(nullptr);
+  usp->ParseInput(Substring(aStr, 1, aStr.Length() - 1));
 
   PopulateFromSuffixIterator iterator(this);
   return usp->ForEach(iterator);
 }
 
 void
 OriginAttributes::CookieJar(nsACString& aStr)
 {
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -188,17 +188,16 @@
 #include "nsIStringBundle.h"
 #include "nsISupportsArray.h"
 #include "nsIURIFixup.h"
 #include "nsIURILoader.h"
 #include "nsIWebBrowserFind.h"
 #include "nsIWidget.h"
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/dom/ScriptSettings.h"
-#include "mozilla/dom/URLSearchParams.h"
 #include "nsPerformance.h"
 
 #ifdef MOZ_TOOLKIT_SEARCH
 #include "nsIBrowserSearchService.h"
 #endif
 
 #include "mozIThirdPartyUtil.h"
 // Values for the network.cookie.cookieBehavior pref are documented in
@@ -2017,34 +2016,16 @@ nsDocShell::SetCurrentURI(nsIURI* aURI, 
   if (root.get() == static_cast<nsIDocShellTreeItem*>(this)) {
     // This is the root docshell
     isRoot = true;
   }
   if (mLSHE) {
     mLSHE->GetIsSubFrame(&isSubFrame);
   }
 
-  // nsDocShell owns a URLSearchParams that is used by
-  // window.location.searchParams to be in sync with the current location.
-  if (!mURLSearchParams) {
-    mURLSearchParams = new URLSearchParams();
-  }
-
-  nsAutoCString search;
-
-  nsCOMPtr<nsIURL> url(do_QueryInterface(mCurrentURI));
-  if (url) {
-    nsresult rv = url->GetQuery(search);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("Failed to get the query from a nsIURL.");
-    }
-  }
-
-  mURLSearchParams->ParseInput(search, nullptr);
-
   if (!isSubFrame && !isRoot) {
     /*
      * We don't want to send OnLocationChange notifications when
      * a subframe is being loaded for the first time, while
      * visiting a frameset page
      */
     return false;
   }
@@ -5841,21 +5822,16 @@ nsDocShell::Destroy()
     mContentViewer = nullptr;
   }
 
   nsDocLoader::Destroy();
 
   mParentWidget = nullptr;
   mCurrentURI = nullptr;
 
-  if (mURLSearchParams) {
-    mURLSearchParams->RemoveObservers();
-    mURLSearchParams = nullptr;
-  }
-
   if (mScriptGlobal) {
     mScriptGlobal->DetachFromDocShell();
     mScriptGlobal = nullptr;
   }
 
   if (mSessionHistory) {
     // We want to destroy these content viewers now rather than
     // letting their destruction wait for the session history
@@ -13926,22 +13902,16 @@ nsDocShell::SetOpener(nsITabParent* aOpe
 
 nsITabParent*
 nsDocShell::GetOpener()
 {
   nsCOMPtr<nsITabParent> opener(do_QueryReferent(mOpener));
   return opener;
 }
 
-URLSearchParams*
-nsDocShell::GetURLSearchParams()
-{
-  return mURLSearchParams;
-}
-
 class JavascriptTimelineMarker : public TimelineMarker
 {
 public:
   JavascriptTimelineMarker(nsDocShell* aDocShell, const char* aName,
                            const char* aReason,
                            const char16_t* aFunctionName,
                            const char16_t* aFileName,
                            uint32_t aLineNumber)
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -55,17 +55,16 @@
 #include "nsCRT.h"
 #include "prtime.h"
 #include "nsRect.h"
 #include "Units.h"
 
 namespace mozilla {
 namespace dom {
 class EventTarget;
-class URLSearchParams;
 }
 }
 
 class nsDocShell;
 class nsDOMNavigationTiming;
 class nsGlobalWindow;
 class nsIController;
 class nsIScrollableFrame;
@@ -836,19 +835,16 @@ protected:
 
   // Set in LoadErrorPage from the method argument and used later
   // in CreateContentViewer. We have to delay an shistory entry creation
   // for which these objects are needed.
   nsCOMPtr<nsIURI> mFailedURI;
   nsCOMPtr<nsIChannel> mFailedChannel;
   uint32_t mFailedLoadType;
 
-  // window.location.searchParams is updated in sync with this object.
-  nsRefPtr<mozilla::dom::URLSearchParams> mURLSearchParams;
-
   // Set in DoURILoad when either the LOAD_RELOAD_ALLOW_MIXED_CONTENT flag or
   // the LOAD_NORMAL_ALLOW_MIXED_CONTENT flag is set.
   // Checked in nsMixedContentBlocker, to see if the channels match.
   nsCOMPtr<nsIChannel> mMixedContentChannel;
 
   // WEAK REFERENCES BELOW HERE.
   // Note these are intentionally not addrefd. Doing so will create a cycle.
   // For that reasons don't use nsCOMPtr.
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -6,32 +6,24 @@
 
 #include "domstubs.idl"
 #include "nsIDocShellTreeItem.idl"
 
 %{ C++
 #include "js/TypeDecls.h"
 class nsPresContext;
 class nsIPresShell;
-
-namespace mozilla {
-namespace dom {
-class URLSearchParams;
-}
-}
-
 %}
 
 /**
  * The nsIDocShell interface.
  */
 
 [ptr] native nsPresContext(nsPresContext);
 [ptr] native nsIPresShell(nsIPresShell);
-[ptr] native URLSearchParams(mozilla::dom::URLSearchParams);
 
 interface nsIURI;
 interface nsIChannel;
 interface nsIContentViewer;
 interface nsIURIContentListener;
 interface nsIDOMEventTarget;
 interface nsIDocShellLoadInfo;
 interface nsIEditor;
@@ -49,17 +41,17 @@ interface nsIWebBrowserPrint;
 interface nsIVariant;
 interface nsIPrivacyTransitionObserver;
 interface nsIReflowObserver;
 interface nsIScrollObserver;
 interface nsITabParent;
  
 typedef unsigned long nsLoadFlags;
 
-[scriptable, builtinclass, uuid(696b32a1-3cf1-4909-b501-474b25fc7954)]
+[scriptable, builtinclass, uuid(b3137b7c-d589-48aa-b89b-e02aa451d42c)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -1026,19 +1018,16 @@ interface nsIDocShell : nsIDocShellTreeI
    * Regarding setOpener / getOpener - We can't use XPIDL's "attribute"
    * for notxpcom, so we're relegated to using explicit gets / sets. This
    * should be fine, considering that these methods should only ever be
    * called from native code.
    */
   [noscript,notxpcom,nostdcall] void setOpener(in nsITabParent aOpener);
   [noscript,notxpcom,nostdcall] nsITabParent getOpener();
 
-  // URLSearchParams for the window.location is owned by the docShell.
-  [noscript,notxpcom] URLSearchParams getURLSearchParams();
-
   /**
    * Notify DocShell when the browser is about to start executing JS, and after
    * that execution has stopped.  This only occurs when the Timeline devtool
    * is collecting information.
    */
   [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason,
                                                                   in wstring functionName,
                                                                   in wstring fileName,
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -14,10 +14,19 @@ namespace dom {
 ChromeUtils::OriginAttributesToCookieJar(GlobalObject& aGlobal,
                                          const OriginAttributesDictionary& aAttrs,
                                          nsCString& aCookieJar)
 {
   OriginAttributes attrs(aAttrs);
   attrs.CookieJar(aCookieJar);
 }
 
+/* static */ void
+ChromeUtils::OriginAttributesToSuffix(dom::GlobalObject& aGlobal,
+                                      const dom::OriginAttributesDictionary& aAttrs,
+                                      nsCString& aSuffix)
+
+{
+  OriginAttributes attrs(aAttrs);
+  attrs.CreateSuffix(aSuffix);
+}
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -39,14 +39,19 @@ public:
 
 class ChromeUtils : public ThreadSafeChromeUtils
 {
 public:
   static void
   OriginAttributesToCookieJar(dom::GlobalObject& aGlobal,
                               const dom::OriginAttributesDictionary& aAttrs,
                               nsCString& aCookieJar);
+
+  static void
+  OriginAttributesToSuffix(dom::GlobalObject& aGlobal,
+                           const dom::OriginAttributesDictionary& aAttrs,
+                           nsCString& aSuffix);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeUtils__
--- a/dom/base/Link.cpp
+++ b/dom/base/Link.cpp
@@ -574,31 +574,16 @@ Link::SizeOfExcludingThis(mozilla::Mallo
 URLSearchParams*
 Link::SearchParams()
 {
   CreateSearchParamsIfNeeded();
   return mSearchParams;
 }
 
 void
-Link::SetSearchParams(URLSearchParams& aSearchParams)
-{
-  if (mSearchParams) {
-    mSearchParams->RemoveObserver(this);
-  }
-
-  mSearchParams = &aSearchParams;
-  mSearchParams->AddObserver(this);
-
-  nsAutoString search;
-  mSearchParams->Serialize(search);
-  SetSearchInternal(search);
-}
-
-void
 Link::URLSearchParamsUpdated(URLSearchParams* aSearchParams)
 {
   MOZ_ASSERT(mSearchParams);
   MOZ_ASSERT(mSearchParams == aSearchParams);
 
   nsString search;
   mSearchParams->Serialize(search);
   SetSearchInternal(search);
@@ -616,34 +601,32 @@ Link::UpdateURLSearchParams()
   nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
   if (url) {
     nsresult rv = url->GetQuery(search);
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to get the query from a nsIURL.");
     }
   }
 
-  mSearchParams->ParseInput(search, this);
+  mSearchParams->ParseInput(search);
 }
 
 void
 Link::CreateSearchParamsIfNeeded()
 {
   if (!mSearchParams) {
-    mSearchParams = new URLSearchParams();
-    mSearchParams->AddObserver(this);
+    mSearchParams = new URLSearchParams(this);
     UpdateURLSearchParams();
   }
 }
 
 void
 Link::Unlink()
 {
   if (mSearchParams) {
-    mSearchParams->RemoveObserver(this);
     mSearchParams = nullptr;
   }
 }
 
 void
 Link::Traverse(nsCycleCollectionTraversalCallback &cb)
 {
   Link* tmp = this;
--- a/dom/base/Link.h
+++ b/dom/base/Link.h
@@ -59,17 +59,16 @@ public:
    */
   void SetProtocol(const nsAString &aProtocol, ErrorResult& aError);
   void SetUsername(const nsAString &aUsername, ErrorResult& aError);
   void SetPassword(const nsAString &aPassword, ErrorResult& aError);
   void SetHost(const nsAString &aHost, ErrorResult& aError);
   void SetHostname(const nsAString &aHostname, ErrorResult& aError);
   void SetPathname(const nsAString &aPathname, ErrorResult& aError);
   void SetSearch(const nsAString &aSearch, ErrorResult& aError);
-  void SetSearchParams(mozilla::dom::URLSearchParams& aSearchParams);
   void SetPort(const nsAString &aPort, ErrorResult& aError);
   void SetHash(const nsAString &aHash, ErrorResult& aError);
   void GetOrigin(nsAString &aOrigin, ErrorResult& aError);
   void GetProtocol(nsAString &_protocol, ErrorResult& aError);
   void GetUsername(nsAString &aUsername, ErrorResult& aError);
   void GetPassword(nsAString &aPassword, ErrorResult& aError);
   void GetHost(nsAString &_host, ErrorResult& aError);
   void GetHostname(nsAString &_hostname, ErrorResult& aError);
--- a/dom/base/PostMessageEvent.cpp
+++ b/dom/base/PostMessageEvent.cpp
@@ -233,17 +233,23 @@ PostMessageEvent::TransferStructuredClon
 
 /* static */ void
 PostMessageEvent::FreeTransferStructuredClone(uint32_t aTag,
                                               JS::TransferableOwnership aOwnership,
                                               void *aContent,
                                               uint64_t aExtraData,
                                               void* aClosure)
 {
-  // Nothing to do.
+  if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
+    MOZ_ASSERT(aClosure);
+    MOZ_ASSERT(!aContent);
+
+    StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(aClosure);
+    MessagePort::ForceClose(scInfo->event->GetPortIdentifier(aExtraData));
+  }
 }
 
 PostMessageEvent::PostMessageEvent(nsGlobalWindow* aSource,
                                    const nsAString& aCallerOrigin,
                                    nsGlobalWindow* aTargetWindow,
                                    nsIPrincipal* aProvidedPrincipal,
                                    bool aTrustedCaller)
 : mSource(aSource),
--- a/dom/base/URL.cpp
+++ b/dom/base/URL.cpp
@@ -22,17 +22,16 @@
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(URL)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(URL)
   if (tmp->mSearchParams) {
-    tmp->mSearchParams->RemoveObserver(tmp);
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mSearchParams)
   }
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(URL)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSearchParams)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
@@ -369,17 +368,17 @@ URL::UpdateURLSearchParams()
   nsCOMPtr<nsIURL> url(do_QueryInterface(mURI));
   if (url) {
     nsresult rv = url->GetQuery(search);
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to get the query from a nsIURL.");
     }
   }
 
-  mSearchParams->ParseInput(search, this);
+  mSearchParams->ParseInput(search);
 }
 
 void
 URL::GetHostname(nsAString& aHostname, ErrorResult& aRv) const
 {
   aHostname.Truncate();
   nsContentUtils::GetHostOrIPv6WithBrackets(mURI, aHostname);
 }
@@ -496,32 +495,16 @@ URL::SetSearchInternal(const nsAString& 
 URLSearchParams*
 URL::SearchParams()
 {
   CreateSearchParamsIfNeeded();
   return mSearchParams;
 }
 
 void
-URL::SetSearchParams(URLSearchParams& aSearchParams)
-{
-  if (mSearchParams) {
-    mSearchParams->RemoveObserver(this);
-  }
-
-  // the observer will be cleared using the cycle collector.
-  mSearchParams = &aSearchParams;
-  mSearchParams->AddObserver(this);
-
-  nsAutoString search;
-  mSearchParams->Serialize(search);
-  SetSearchInternal(search);
-}
-
-void
 URL::GetHash(nsAString& aHash, ErrorResult& aRv) const
 {
   aHash.Truncate();
 
   nsAutoCString ref;
   nsresult rv = mURI->GetRef(ref);
   if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
     aHash.Assign(char16_t('#'));
@@ -545,16 +528,15 @@ bool IsChromeURI(nsIURI* aURI)
       return isChrome;
   return false;
 }
 
 void
 URL::CreateSearchParamsIfNeeded()
 {
   if (!mSearchParams) {
-    mSearchParams = new URLSearchParams();
-    mSearchParams->AddObserver(this);
+    mSearchParams = new URLSearchParams(this);
     UpdateURLSearchParams();
   }
 }
 
 }
 }
--- a/dom/base/URL.h
+++ b/dom/base/URL.h
@@ -114,18 +114,16 @@ public:
   void SetPathname(const nsAString& aPathname, ErrorResult& aRv);
 
   void GetSearch(nsAString& aRetval, ErrorResult& aRv) const;
 
   void SetSearch(const nsAString& aArg, ErrorResult& aRv);
 
   URLSearchParams* SearchParams();
 
-  void SetSearchParams(URLSearchParams& aSearchParams);
-
   void GetHash(nsAString& aRetval, ErrorResult& aRv) const;
 
   void SetHash(const nsAString& aArg, ErrorResult& aRv);
 
   void Stringify(nsAString& aRetval, ErrorResult& aRv) const
   {
     GetHref(aRetval, aRv);
   }
--- a/dom/base/URLSearchParams.cpp
+++ b/dom/base/URLSearchParams.cpp
@@ -7,26 +7,27 @@
 #include "URLSearchParams.h"
 #include "mozilla/dom/URLSearchParamsBinding.h"
 #include "mozilla/dom/EncodingUtils.h"
 #include "nsDOMString.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URLSearchParams, mObservers)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URLSearchParams, mObserver)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(URLSearchParams)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(URLSearchParams)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URLSearchParams)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-URLSearchParams::URLSearchParams()
+URLSearchParams::URLSearchParams(URLSearchParamsObserver* aObserver)
+  : mObserver(aObserver)
 {
 }
 
 URLSearchParams::~URLSearchParams()
 {
   DeleteAll();
 }
 
@@ -36,34 +37,33 @@ URLSearchParams::WrapObject(JSContext* a
   return URLSearchParamsBinding::Wrap(aCx, this, aGivenProto);
 }
 
 /* static */ already_AddRefed<URLSearchParams>
 URLSearchParams::Constructor(const GlobalObject& aGlobal,
                              const nsAString& aInit,
                              ErrorResult& aRv)
 {
-  nsRefPtr<URLSearchParams> sp = new URLSearchParams();
-  sp->ParseInput(NS_ConvertUTF16toUTF8(aInit), nullptr);
+  nsRefPtr<URLSearchParams> sp = new URLSearchParams(nullptr);
+  sp->ParseInput(NS_ConvertUTF16toUTF8(aInit));
   return sp.forget();
 }
 
 /* static */ already_AddRefed<URLSearchParams>
 URLSearchParams::Constructor(const GlobalObject& aGlobal,
                              URLSearchParams& aInit,
                              ErrorResult& aRv)
 {
-  nsRefPtr<URLSearchParams> sp = new URLSearchParams();
+  nsRefPtr<URLSearchParams> sp = new URLSearchParams(nullptr);
   sp->mSearchParams = aInit.mSearchParams;
   return sp.forget();
 }
 
 void
-URLSearchParams::ParseInput(const nsACString& aInput,
-                            URLSearchParamsObserver* aObserver)
+URLSearchParams::ParseInput(const nsACString& aInput)
 {
   // Remove all the existing data before parsing a new input.
   DeleteAll();
 
   nsACString::const_iterator start, end;
   aInput.BeginReading(start);
   aInput.EndReading(end);
   nsACString::const_iterator iter(start);
@@ -103,18 +103,16 @@ URLSearchParams::ParseInput(const nsACSt
     nsAutoString decodedName;
     DecodeString(name, decodedName);
 
     nsAutoString decodedValue;
     DecodeString(value, decodedValue);
 
     AppendInternal(decodedName, decodedValue);
   }
-
-  NotifyObservers(aObserver);
 }
 
 void
 URLSearchParams::DecodeString(const nsACString& aInput, nsAString& aOutput)
 {
   nsACString::const_iterator start, end;
   aInput.BeginReading(start);
   aInput.EndReading(end);
@@ -204,37 +202,16 @@ URLSearchParams::ConvertString(const nsA
   }
 
   if (newOutputLength < outputLength) {
     aOutput.Truncate(newOutputLength);
   }
 }
 
 void
-URLSearchParams::AddObserver(URLSearchParamsObserver* aObserver)
-{
-  MOZ_ASSERT(aObserver);
-  MOZ_ASSERT(!mObservers.Contains(aObserver));
-  mObservers.AppendElement(aObserver);
-}
-
-void
-URLSearchParams::RemoveObserver(URLSearchParamsObserver* aObserver)
-{
-  MOZ_ASSERT(aObserver);
-  mObservers.RemoveElement(aObserver);
-}
-
-void
-URLSearchParams::RemoveObservers()
-{
-  mObservers.Clear();
-}
-
-void
 URLSearchParams::Get(const nsAString& aName, nsString& aRetval)
 {
   SetDOMStringToNull(aRetval);
 
   for (uint32_t i = 0, len = mSearchParams.Length(); i < len; ++i) {
     if (mSearchParams[i].mKey.Equals(aName)) {
       aRetval.Assign(mSearchParams[i].mValue);
       break;
@@ -275,24 +252,24 @@ URLSearchParams::Set(const nsAString& aN
 
   if (!param) {
     param = mSearchParams.AppendElement();
     param->mKey = aName;
   }
 
   param->mValue = aValue;
 
-  NotifyObservers(nullptr);
+  NotifyObserver();
 }
 
 void
 URLSearchParams::Append(const nsAString& aName, const nsAString& aValue)
 {
   AppendInternal(aName, aValue);
-  NotifyObservers(nullptr);
+  NotifyObserver();
 }
 
 void
 URLSearchParams::AppendInternal(const nsAString& aName, const nsAString& aValue)
 {
   Param* param = mSearchParams.AppendElement();
   param->mKey = aName;
   param->mValue = aValue;
@@ -319,17 +296,17 @@ URLSearchParams::Delete(const nsAString&
       mSearchParams.RemoveElementAt(i);
       found = true;
     } else {
       ++i;
     }
   }
 
   if (found) {
-    NotifyObservers(nullptr);
+    NotifyObserver();
   }
 }
 
 void
 URLSearchParams::DeleteAll()
 {
   mSearchParams.Clear();
 }
@@ -375,19 +352,17 @@ URLSearchParams::Serialize(nsAString& aV
 
     SerializeString(NS_ConvertUTF16toUTF8(mSearchParams[i].mKey), aValue);
     aValue.Append('=');
     SerializeString(NS_ConvertUTF16toUTF8(mSearchParams[i].mValue), aValue);
   }
 }
 
 void
-URLSearchParams::NotifyObservers(URLSearchParamsObserver* aExceptObserver)
+URLSearchParams::NotifyObserver()
 {
-  for (uint32_t i = 0; i < mObservers.Length(); ++i) {
-    if (mObservers[i] != aExceptObserver) {
-      mObservers[i]->URLSearchParamsUpdated(this);
-    }
+  if (mObserver) {
+    mObserver->URLSearchParamsUpdated(this);
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/URLSearchParams.h
+++ b/dom/base/URLSearchParams.h
@@ -35,17 +35,17 @@ class URLSearchParams final : public nsI
                               public nsWrapperCache
 {
   ~URLSearchParams();
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(URLSearchParams)
 
-  URLSearchParams();
+  explicit URLSearchParams(URLSearchParamsObserver* aObserver);
 
   // WebIDL methods
   nsISupports* GetParentObject() const
   {
     return nullptr;
   }
 
   virtual JSObject*
@@ -54,22 +54,17 @@ public:
   static already_AddRefed<URLSearchParams>
   Constructor(const GlobalObject& aGlobal, const nsAString& aInit,
               ErrorResult& aRv);
 
   static already_AddRefed<URLSearchParams>
   Constructor(const GlobalObject& aGlobal, URLSearchParams& aInit,
               ErrorResult& aRv);
 
-  void ParseInput(const nsACString& aInput,
-                  URLSearchParamsObserver* aObserver);
-
-  void AddObserver(URLSearchParamsObserver* aObserver);
-  void RemoveObserver(URLSearchParamsObserver* aObserver);
-  void RemoveObservers();
+  void ParseInput(const nsACString& aInput);
 
   void Serialize(nsAString& aValue) const;
 
   void Get(const nsAString& aName, nsString& aRetval);
 
   void GetAll(const nsAString& aName, nsTArray<nsString >& aRetval);
 
   void Set(const nsAString& aName, const nsAString& aValue);
@@ -108,26 +103,26 @@ public:
 private:
   void AppendInternal(const nsAString& aName, const nsAString& aValue);
 
   void DeleteAll();
 
   void DecodeString(const nsACString& aInput, nsAString& aOutput);
   void ConvertString(const nsACString& aInput, nsAString& aOutput);
 
-  void NotifyObservers(URLSearchParamsObserver* aExceptObserver);
+  void NotifyObserver();
 
   struct Param
   {
     nsString mKey;
     nsString mValue;
   };
 
   nsTArray<Param> mSearchParams;
 
-  nsTArray<nsRefPtr<URLSearchParamsObserver>> mObservers;
+  nsRefPtr<URLSearchParamsObserver> mObserver;
   nsCOMPtr<nsIUnicodeDecoder> mDecoder;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_URLSearchParams_h */
--- a/dom/base/nsLocation.cpp
+++ b/dom/base/nsLocation.cpp
@@ -56,37 +56,33 @@ nsLocation::nsLocation(nsPIDOMWindow* aW
   MOZ_ASSERT(aDocShell);
   MOZ_ASSERT(mInnerWindow->IsInnerWindow());
 
   mDocShell = do_GetWeakReference(aDocShell);
 }
 
 nsLocation::~nsLocation()
 {
-  RemoveURLSearchParams();
 }
 
 // QueryInterface implementation for nsLocation
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsLocation)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsIDOMLocation)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMLocation)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsLocation)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsLocation)
-  tmp->RemoveURLSearchParams();
-
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mInnerWindow);
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsLocation)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSearchParams)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInnerWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsLocation)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsLocation)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsLocation)
@@ -1052,124 +1048,8 @@ nsLocation::CallerSubsumes()
   return subsumes;
 }
 
 JSObject*
 nsLocation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return LocationBinding::Wrap(aCx, this, aGivenProto);
 }
-
-URLSearchParams*
-nsLocation::GetDocShellSearchParams()
-{
-  nsCOMPtr<nsIDocShell> docShell = GetDocShell();
-  if (!docShell) {
-    return nullptr;
-  }
-
-  return docShell->GetURLSearchParams();
-}
-
-URLSearchParams*
-nsLocation::SearchParams()
-{
-  if (!mSearchParams) {
-    // We must register this object to the URLSearchParams of the docshell in
-    // order to receive updates.
-    nsRefPtr<URLSearchParams> searchParams = GetDocShellSearchParams();
-    if (searchParams) {
-      searchParams->AddObserver(this);
-    }
-
-    mSearchParams = new URLSearchParams();
-    mSearchParams->AddObserver(this);
-    UpdateURLSearchParams();
-  }
-
-  return mSearchParams;
-}
-
-void
-nsLocation::SetSearchParams(URLSearchParams& aSearchParams)
-{
-  if (mSearchParams) {
-    mSearchParams->RemoveObserver(this);
-  }
-
-  // the observer will be cleared using the cycle collector.
-  mSearchParams = &aSearchParams;
-  mSearchParams->AddObserver(this);
-
-  nsAutoString search;
-  mSearchParams->Serialize(search);
-  SetSearchInternal(search);
-
-  // We don't need to inform the docShell about this new SearchParams because
-  // setting the new value the docShell will refresh its value automatically.
-}
-
-void
-nsLocation::URLSearchParamsUpdated(URLSearchParams* aSearchParams)
-{
-  MOZ_ASSERT(mSearchParams);
-
-  // This change comes from content.
-  if (aSearchParams == mSearchParams) {
-    nsAutoString search;
-    mSearchParams->Serialize(search);
-    SetSearchInternal(search);
-    return;
-  }
-
-  // This change comes from the docShell.
-#ifdef DEBUG
-  {
-    nsRefPtr<URLSearchParams> searchParams = GetDocShellSearchParams();
-    MOZ_ASSERT(searchParams);
-    MOZ_ASSERT(aSearchParams == searchParams);
-  }
-#endif
-
-  nsAutoString search;
-  aSearchParams->Serialize(search);
-  mSearchParams->ParseInput(NS_ConvertUTF16toUTF8(search), this);
-}
-
-void
-nsLocation::UpdateURLSearchParams()
-{
-  if (!mSearchParams) {
-    return;
-  }
-
-  nsAutoCString search;
-
-  nsCOMPtr<nsIURI> uri;
-  nsresult rv = GetURI(getter_AddRefs(uri));
-  if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!uri)) {
-    return;
-  }
-
-  nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
-  if (url) {
-    nsresult rv = url->GetQuery(search);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("Failed to get the query from a nsIURL.");
-    }
-  }
-
-  mSearchParams->ParseInput(search, this);
-}
-
-void
-nsLocation::RemoveURLSearchParams()
-{
-  if (mSearchParams) {
-    mSearchParams->RemoveObserver(this);
-    mSearchParams = nullptr;
-
-    nsRefPtr<URLSearchParams> docShellSearchParams = GetDocShellSearchParams();
-    if (docShellSearchParams) {
-      docShellSearchParams->RemoveObserver(this);
-    }
-  }
-}
--- a/dom/base/nsLocation.h
+++ b/dom/base/nsLocation.h
@@ -9,30 +9,28 @@
 
 #include "nsIDOMLocation.h"
 #include "nsString.h"
 #include "nsIWeakReferenceUtils.h"
 #include "nsWrapperCache.h"
 #include "nsCycleCollectionParticipant.h"
 #include "js/TypeDecls.h"
 #include "mozilla/ErrorResult.h"
-#include "mozilla/dom/URLSearchParams.h"
 #include "nsPIDOMWindow.h"
 
 class nsIURI;
 class nsIDocShell;
 class nsIDocShellLoadInfo;
 
 //*****************************************************************************
 // nsLocation: Script "location" object
 //*****************************************************************************
 
 class nsLocation final : public nsIDOMLocation
                        , public nsWrapperCache
-                       , public mozilla::dom::URLSearchParamsObserver
 {
   typedef mozilla::ErrorResult ErrorResult;
 
 public:
   nsLocation(nsPIDOMWindow* aWindow, nsIDocShell *aDocShell);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsLocation,
@@ -117,20 +115,16 @@ public:
   {
     aError = GetSearch(aSeach);
   }
   void SetSearch(const nsAString& aSeach, ErrorResult& aError)
   {
     aError = SetSearch(aSeach);
   }
 
-  mozilla::dom::URLSearchParams* SearchParams();
-
-  void SetSearchParams(mozilla::dom::URLSearchParams& aSearchParams);
-
   void GetHash(nsAString& aHash, ErrorResult& aError)
   {
     aError = GetHash(aHash);
   }
   void SetHash(const nsAString& aHash, ErrorResult& aError)
   {
     aError = SetHash(aHash);
   }
@@ -139,27 +133,20 @@ public:
     GetHref(aRetval, aError);
   }
   nsPIDOMWindow* GetParentObject() const
   {
     return mInnerWindow;
   }
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
-  // URLSearchParamsObserver
-  void URLSearchParamsUpdated(mozilla::dom::URLSearchParams* aSearchParams) override;
-
 protected:
   virtual ~nsLocation();
 
   nsresult SetSearchInternal(const nsAString& aSearch);
-  void UpdateURLSearchParams();
-  void RemoveURLSearchParams();
-
-  mozilla::dom::URLSearchParams* GetDocShellSearchParams();
 
   // In the case of jar: uris, we sometimes want the place the jar was
   // fetched from as the URI instead of the jar: uri itself.  Pass in
   // true for aGetInnermostURI when that's the case.
   nsresult GetURI(nsIURI** aURL, bool aGetInnermostURI = false);
   nsresult GetWritableURI(nsIURI** aURL);
   nsresult SetURI(nsIURI* aURL, bool aReplace = false);
   nsresult SetHrefWithBase(const nsAString& aHref, nsIURI* aBase,
@@ -168,14 +155,12 @@ protected:
                               bool aReplace);
 
   nsresult GetSourceBaseURL(JSContext* cx, nsIURI** sourceURL);
   nsresult CheckURL(nsIURI *url, nsIDocShellLoadInfo** aLoadInfo);
   bool CallerSubsumes();
 
   nsString mCachedHash;
   nsCOMPtr<nsPIDOMWindow> mInnerWindow;
-  nsRefPtr<mozilla::dom::URLSearchParams> mSearchParams;
   nsWeakPtr mDocShell;
 };
 
 #endif // nsLocation_h__
-
--- a/dom/base/test/test_XHRDocURI.html
+++ b/dom/base/test/test_XHRDocURI.html
@@ -4,34 +4,41 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=459470
 -->
 <head>
   <title>XMLHttpRequest return document URIs</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <base href="http://example.org/">
 </head>
-<body>
+<body onload="startTest();">
 <a target="_blank"
    href="https://bugzilla.mozilla.org/show_bug.cgi?id=459470">Mozilla Bug 459470</a><br />
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=859095">Mozilla Bug 859095</a>
 
 <p id="display">
 <iframe id=loader></iframe>
 </p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.8">
 
 SimpleTest.waitForExplicitFinish();
+var gen;
 
-gen = runTest();
-gen.next();
+function startTest() {
+  // The test uses history API, so don't do anything before load event has been
+  // handled.
+  SimpleTest.executeSoon(function() {
+    gen = runTest();
+    gen.next();
+  });
+}
 
 function testXMLDocURI(aDoc, aExpects) {
   is(aDoc.documentURI, aExpects.documentURI, "wrong url");
   is(aDoc.baseURI, aExpects.baseURI, "wrong base");
   is(aDoc.documentElement.baseURI, aExpects.elementBaseURI,
      "wrong base (xml:base)");
 }
 
--- a/dom/base/test/test_urlSearchParams.html
+++ b/dom/base/test/test_urlSearchParams.html
@@ -119,140 +119,51 @@ https://bugzilla.mozilla.org/show_bug.cg
     ok(url.searchParams.has('a'), "URL.searchParams.has('a')");
     is(url.searchParams.get('a'), 'b', "URL.searchParams.get('a')");
     ok(url.searchParams.has('c'), "URL.searchParams.has('c')");
     is(url.searchParams.get('c'), 'd', "URL.searchParams.get('c')");
 
     url.searchParams.set('e', 'f');
     ok(url.href.indexOf('e=f') != 1, 'URL right');
 
-    var u = new URLSearchParams();
-    u.append('foo', 'bar');
-    url.searchParams = u;
-    is(url.searchParams, u, "URL.searchParams is the same object");
-    is(url.searchParams.get('foo'), 'bar', "URL.searchParams.get('foo')");
-    is(url.href, 'http://www.example.net/?foo=bar', 'URL right');
-
-    try {
-      url.searchParams = null;
-      ok(false, "URLSearchParams is not nullable");
-    } catch(e) {
-      ok(true, "URLSearchParams is not nullable");
-    }
-
-    var url2 = new URL('http://www.example.net?e=f');
-    url.searchParams = url2.searchParams;
-    is(url.searchParams, url2.searchParams, "URL.searchParams is not the same object");
-    is(url.searchParams.get('e'), 'f', "URL.searchParams.get('e')");
-
-    url.href = "http://www.example.net?bar=foo";
-    is(url.searchParams.get('bar'), 'foo', "URL.searchParams.get('bar')");
-
     runTest();
   }
 
   function testElement(e) {
     ok(e, 'element exists');
     ok(e.searchParams, "e.searchParams exists!");
     ok(e.searchParams.has('a'), "e.searchParams.has('a')");
     is(e.searchParams.get('a'), 'b', "e.searchParams.get('a')");
     ok(e.searchParams.has('c'), "e.searchParams.has('c')");
     is(e.searchParams.get('c'), 'd', "e.searchParams.get('c')");
 
     e.searchParams.set('e', 'f');
     ok(e.href.indexOf('e=f') != 1, 'e is right');
 
-    var u = new URLSearchParams();
-    u.append('foo', 'bar');
-    e.searchParams = u;
-    is(e.searchParams, u, "e.searchParams is the same object");
-    is(e.searchParams.get('foo'), 'bar', "e.searchParams.get('foo')");
-    is(e.href, 'http://www.example.net/?foo=bar', 'e is right');
-
-    try {
-      e.searchParams = null;
-      ok(false, "URLSearchParams is not nullable");
-    } catch(e) {
-      ok(true, "URLSearchParams is not nullable");
-    }
-
-    var url2 = new URL('http://www.example.net?e=f');
-    e.searchParams = url2.searchParams;
-    is(e.searchParams, url2.searchParams, "e.searchParams is not the same object");
-    is(e.searchParams.get('e'), 'f', "e.searchParams.get('e')");
-
-    e.href = "http://www.example.net?bar=foo";
-    is(e.searchParams.get('bar'), 'foo', "e.searchParams.get('bar')");
-
-    e.setAttribute('href', "http://www.example.net?bar2=foo2");
-    is(e.searchParams.get('bar2'), 'foo2', "e.searchParams.get('bar2')");
-
     runTest();
   }
 
   function testEncoding() {
     var encoding = [ [ '1', '1' ],
                      [ 'a b', 'a+b' ],
                      [ '<>', '%3C%3E' ],
                      [ '\u0541', '%D5%81'] ];
 
     for (var i = 0; i < encoding.length; ++i) {
-      var a = new URLSearchParams();
-      a.set('a', encoding[i][0]);
-
       var url = new URL('http://www.example.net');
-      url.searchParams = a;
+      url.searchParams.set('a', encoding[i][0]);
       is(url.href, 'http://www.example.net/?a=' + encoding[i][1]);
 
       var url2 = new URL(url.href);
       is(url2.searchParams.get('a'), encoding[i][0], 'a is still there');
     }
 
     runTest();
   }
 
-  function testMultiURL() {
-    var a = new URL('http://www.example.net?a=b&c=d');
-    var b = new URL('http://www.example.net?e=f');
-    var c = document.createElement('a');
-    var d = document.createElement('area');
-    ok(a.searchParams.has('a'), "a.searchParams.has('a')");
-    ok(a.searchParams.has('c'), "a.searchParams.has('c')");
-    ok(b.searchParams.has('e'), "b.searchParams.has('e')");
-    ok(c.searchParams, "c.searchParams");
-    ok(d.searchParams, "d.searchParams");
-
-    var u = new URLSearchParams();
-    a.searchParams = b.searchParams = c.searchParams = d.searchParams = u;
-    is(a.searchParams, u, "a.searchParams === u");
-    is(b.searchParams, u, "b.searchParams === u");
-    is(c.searchParams, u, "c.searchParams === u");
-    is(d.searchParams, u, "d.searchParams === u");
-    ok(!a.searchParams.has('a'), "!a.searchParams.has('a')");
-    ok(!a.searchParams.has('c'), "!a.searchParams.has('c')");
-    ok(!b.searchParams.has('e'), "!b.searchParams.has('e')");
-
-    u.append('foo', 'bar');
-    is(a.searchParams.get('foo'), 'bar', "a has foo=bar");
-    is(b.searchParams.get('foo'), 'bar', "b has foo=bar");
-    is(c.searchParams.get('foo'), 'bar', "c has foo=bar");
-    is(d.searchParams.get('foo'), 'bar', "d has foo=bar");
-    is(a + "", b + "", "stringify a == b");
-    is(c.searchParams + "", b.searchParams + "", "stringify c.searchParams == b.searchParams");
-    is(d.searchParams + "", b.searchParams + "", "stringify d.searchParams == b.searchParams");
-
-    a.search = "?bar=foo";
-    is(a.searchParams.get('bar'), 'foo', "a has bar=foo");
-    is(b.searchParams.get('bar'), 'foo', "b has bar=foo");
-    is(c.searchParams.get('bar'), 'foo', "c has bar=foo");
-    is(d.searchParams.get('bar'), 'foo', "d has bar=foo");
-
-    runTest();
-  }
-
   function testOrdering() {
     var a = new URLSearchParams("a=1&a=2&b=3&c=4&c=5&a=6");
     is(a.toString(), "a=1&a=2&b=3&c=4&c=5&a=6", "Order is correct");
     is(a.getAll('a').length, 3, "Correct length of getAll()");
 
     var b = new URLSearchParams();
     b.append('a', '1');
     b.append('b', '2');
@@ -329,17 +240,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   var tests = [
     testSimpleURLSearchParams,
     testCopyURLSearchParams,
     testParserURLSearchParams,
     testURL,
     function() { testElement(document.getElementById('anchor')) },
     function() { testElement(document.getElementById('area')) },
     testEncoding,
-    testMultiURL,
     testOrdering,
     testDelete,
     testGetNULL,
     testSet
   ];
 
   function runTest() {
     if (!tests.length) {
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -176,22 +176,22 @@ DOMProxyHandler::defineProperty(JSContex
   if (desc.hasGetterObject() && desc.setter() == JS_StrictPropertyStub) {
     return result.failGetterOnly();
   }
 
   if (xpc::WrapperFactory::IsXrayWrapper(proxy)) {
     return result.succeed();
   }
 
-  JSObject* expando = EnsureExpandoObject(cx, proxy);
+  JS::Rooted<JSObject*> expando(cx, EnsureExpandoObject(cx, proxy));
   if (!expando) {
     return false;
   }
 
-  if (!js::DefineOwnProperty(cx, expando, id, desc, result)) {
+  if (!JS_DefinePropertyById(cx, expando, id, desc, result)) {
     return false;
   }
   *defined = true;
   return true;
 }
 
 bool
 DOMProxyHandler::set(JSContext *cx, Handle<JSObject*> proxy, Handle<jsid> id,
--- a/dom/bindings/TypedArray.h
+++ b/dom/bindings/TypedArray.h
@@ -185,16 +185,58 @@ private:
       memcpy(buf, data, length*sizeof(T));
     }
     return obj;
   }
 
   TypedArray(const TypedArray&) = delete;
 };
 
+template<JSObject* UnwrapArray(JSObject*),
+         void GetLengthAndData(JSObject*, uint32_t*, uint8_t**),
+         js::Scalar::Type GetViewType(JSObject*)>
+struct ArrayBufferView_base : public TypedArray_base<uint8_t, UnwrapArray,
+                                                     GetLengthAndData> {
+private:
+  typedef TypedArray_base<uint8_t, UnwrapArray, GetLengthAndData> Base;
+
+public:
+  ArrayBufferView_base()
+    : Base()
+  {
+  }
+
+  explicit ArrayBufferView_base(ArrayBufferView_base&& aOther)
+    : Base(Move(aOther)),
+      mType(aOther.mType)
+  {
+    aOther.mType = js::Scalar::MaxTypedArrayViewType;
+  }
+
+private:
+  js::Scalar::Type mType;
+
+public:
+  inline bool Init(JSObject* obj)
+  {
+    if (!Base::Init(obj)) {
+      return false;
+    }
+
+    mType = GetViewType(this->Obj());
+    return true;
+  }
+
+  inline js::Scalar::Type Type() const
+  {
+    MOZ_ASSERT(this->inited());
+    return mType;
+  }
+};
+
 typedef TypedArray<int8_t, js::UnwrapInt8Array, JS_GetInt8ArrayData,
                    js::GetInt8ArrayLengthAndData, JS_NewInt8Array>
         Int8Array;
 typedef TypedArray<uint8_t, js::UnwrapUint8Array, JS_GetUint8ArrayData,
                    js::GetUint8ArrayLengthAndData, JS_NewUint8Array>
         Uint8Array;
 typedef TypedArray<uint8_t, js::UnwrapUint8ClampedArray, JS_GetUint8ClampedArrayData,
                    js::GetUint8ClampedArrayLengthAndData, JS_NewUint8ClampedArray>
@@ -212,17 +254,19 @@ typedef TypedArray<uint32_t, js::UnwrapU
                    js::GetUint32ArrayLengthAndData, JS_NewUint32Array>
         Uint32Array;
 typedef TypedArray<float, js::UnwrapFloat32Array, JS_GetFloat32ArrayData,
                    js::GetFloat32ArrayLengthAndData, JS_NewFloat32Array>
         Float32Array;
 typedef TypedArray<double, js::UnwrapFloat64Array, JS_GetFloat64ArrayData,
                    js::GetFloat64ArrayLengthAndData, JS_NewFloat64Array>
         Float64Array;
-typedef TypedArray_base<uint8_t, js::UnwrapArrayBufferView, js::GetArrayBufferViewLengthAndData>
+typedef ArrayBufferView_base<js::UnwrapArrayBufferView,
+                             js::GetArrayBufferViewLengthAndData,
+                             JS_GetArrayBufferViewType>
         ArrayBufferView;
 typedef TypedArray<uint8_t, js::UnwrapArrayBuffer, JS_GetArrayBufferData,
                    js::GetArrayBufferLengthAndData, JS_NewArrayBuffer>
         ArrayBuffer;
 
 typedef TypedArray<int8_t, js::UnwrapSharedInt8Array, JS_GetSharedInt8ArrayData,
                    js::GetSharedInt8ArrayLengthAndData, JS_NewSharedInt8Array>
         SharedInt8Array;
@@ -245,17 +289,19 @@ typedef TypedArray<uint32_t, js::UnwrapS
                    js::GetSharedUint32ArrayLengthAndData, JS_NewSharedUint32Array>
         SharedUint32Array;
 typedef TypedArray<float, js::UnwrapSharedFloat32Array, JS_GetSharedFloat32ArrayData,
                    js::GetSharedFloat32ArrayLengthAndData, JS_NewSharedFloat32Array>
         SharedFloat32Array;
 typedef TypedArray<double, js::UnwrapSharedFloat64Array, JS_GetSharedFloat64ArrayData,
                    js::GetSharedFloat64ArrayLengthAndData, JS_NewSharedFloat64Array>
         SharedFloat64Array;
-typedef TypedArray_base<uint8_t, js::UnwrapSharedArrayBufferView, js::GetSharedArrayBufferViewLengthAndData>
+typedef ArrayBufferView_base<js::UnwrapSharedArrayBufferView,
+                             js::GetSharedArrayBufferViewLengthAndData,
+                             JS_GetSharedArrayBufferViewType>
         SharedArrayBufferView;
 typedef TypedArray<uint8_t, js::UnwrapSharedArrayBuffer, JS_GetSharedArrayBufferData,
                    js::GetSharedArrayBufferLengthAndData, JS_NewSharedArrayBuffer>
         SharedArrayBuffer;
 
 // A class for converting an nsTArray to a TypedArray
 // Note: A TypedArrayCreator must not outlive the nsTArray it was created from.
 //       So this is best used to pass from things that understand nsTArray to
--- a/dom/canvas/WebGL2ContextTextures.cpp
+++ b/dom/canvas/WebGL2ContextTextures.cpp
@@ -219,17 +219,17 @@ WebGL2Context::TexImage3D(GLenum target,
         dataLength = 0;
         jsArrayType = js::Scalar::MaxTypedArrayViewType;
     } else {
         const ArrayBufferView& view = pixels.Value();
         view.ComputeLengthAndData();
 
         data = view.Data();
         dataLength = view.Length();
-        jsArrayType = JS_GetArrayBufferViewType(view.Obj());
+        jsArrayType = view.Type();
     }
 
     const WebGLTexImageFunc func = WebGLTexImageFunc::TexImage;
     const WebGLTexDimensions dims = WebGLTexDimensions::Tex3D;
 
     if (!ValidateTexImageTarget(target, func, dims))
         return;
 
@@ -362,17 +362,17 @@ WebGL2Context::TexSubImage3D(GLenum rawT
     {
         return;
     }
 
     if (type != existingType) {
         return ErrorInvalidOperation("texSubImage3D: type differs from that of the existing image");
     }
 
-    js::Scalar::Type jsArrayType = JS_GetArrayBufferViewType(view.Obj());
+    js::Scalar::Type jsArrayType = view.Type();
     void* data = view.Data();
     size_t dataLength = view.Length();
 
     if (!ValidateTexInputData(type, jsArrayType, func, dims))
         return;
 
     const size_t bitsPerTexel = GetBitsPerTexel(existingEffectiveInternalFormat);
     MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here.
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -2028,17 +2028,17 @@ WebGLContext::ReadPixels(GLint x, GLint 
         requiredDataType = js::Scalar::Uint16;
         break;
 
     default:
         MOZ_CRASH("bad `type`");
     }
 
     const ArrayBufferView& pixbuf = pixels.Value();
-    int dataType = JS_GetArrayBufferViewType(pixbuf.Obj());
+    int dataType = pixbuf.Type();
 
     // Check the pixels param type
     if (dataType != requiredDataType)
         return ErrorInvalidOperation("readPixels: Mismatched type/pixels types");
 
     // Check the pixels param size
     CheckedUint32 checked_neededByteLength =
         GetImageSize(height, width, 1, bytesPerPixel, mPixelStorePackAlignment);
@@ -3406,17 +3406,17 @@ WebGLContext::TexImage2D(GLenum rawTarge
         length = 0;
         jsArrayType = js::Scalar::MaxTypedArrayViewType;
     } else {
         const ArrayBufferView& view = pixels.Value();
         view.ComputeLengthAndData();
 
         data = view.Data();
         length = view.Length();
-        jsArrayType = JS_GetArrayBufferViewType(view.Obj());
+        jsArrayType = view.Type();
     }
 
     if (!ValidateTexImageTarget(rawTarget, WebGLTexImageFunc::TexImage, WebGLTexDimensions::Tex2D))
         return;
 
     return TexImage2D_base(rawTarget, level, internalformat, width, height, 0, border, format, type,
                            data, length, jsArrayType,
                            WebGLTexelFormat::Auto, false);
@@ -3605,18 +3605,17 @@ WebGLContext::TexSubImage2D(GLenum rawTa
     const ArrayBufferView& view = pixels.Value();
     view.ComputeLengthAndData();
 
     if (!ValidateTexImageTarget(rawTarget, WebGLTexImageFunc::TexSubImage, WebGLTexDimensions::Tex2D))
         return;
 
     return TexSubImage2D_base(rawTarget, level, xoffset, yoffset,
                               width, height, 0, format, type,
-                              view.Data(), view.Length(),
-                              JS_GetArrayBufferViewType(view.Obj()),
+                              view.Data(), view.Length(), view.Type(),
                               WebGLTexelFormat::Auto, false);
 }
 
 void
 WebGLContext::TexSubImage2D(GLenum target, GLint level,
                             GLint xoffset, GLint yoffset,
                             GLenum format, GLenum type, ImageData* pixels,
                             ErrorResult& rv)
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -1595,18 +1595,18 @@ FetchBody<Derived>::ContinueConsumeBody(
         NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
         bool isValidUrlEncodedMimeType = StringBeginsWith(mMimeType, urlDataMimeType);
 
         if (isValidUrlEncodedMimeType && mMimeType.Length() > urlDataMimeType.Length()) {
           isValidUrlEncodedMimeType = mMimeType[urlDataMimeType.Length()] == ';';
         }
 
         if (isValidUrlEncodedMimeType) {
-          nsRefPtr<URLSearchParams> params = new URLSearchParams();
-          params->ParseInput(data, /* aObserver */ nullptr);
+          nsRefPtr<URLSearchParams> params = new URLSearchParams(nullptr);
+          params->ParseInput(data);
 
           nsRefPtr<nsFormData> fd = new nsFormData(DerivedClass()->GetParentObject());
           FillFormIterator iterator(fd);
           DebugOnly<bool> status = params->ForEach(iterator);
           MOZ_ASSERT(status);
 
           localPromise->MaybeResolve(fd);
         } else {
--- a/dom/html/HTMLAreaElement.h
+++ b/dom/html/HTMLAreaElement.h
@@ -153,17 +153,16 @@ public:
 
   using Link::GetSearch;
   using Link::SetSearch;
 
   using Link::GetHash;
   using Link::SetHash;
 
   // The Link::GetSearchParams is OK for us
-  // The Link::SetSearchParams is OK for us
 
   bool NoHref() const
   {
     return GetBoolAttr(nsGkAtoms::nohref);
   }
 
   void SetNoHref(bool aValue, ErrorResult& aError)
   {
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -15,30 +15,30 @@ interface nsIURI;
 interface nsIServiceWorkerUnregisterCallback : nsISupports
 {
   // aState is true if the unregistration succeded.
   // It's false if this ServiceWorkerRegistration doesn't exist.
   void unregisterSucceeded(in bool aState);
   void unregisterFailed();
 };
 
-[scriptable, builtinclass, uuid(103763c8-53ba-42e4-8b26-e601d5bc4afe)]
+[scriptable, builtinclass, uuid(e633b73b-a734-4d04-a09c-b7779a439f3f)]
 interface nsIServiceWorkerInfo : nsISupports
 {
   readonly attribute nsIPrincipal principal;
 
   readonly attribute DOMString scope;
   readonly attribute DOMString scriptSpec;
   readonly attribute DOMString currentWorkerURL;
 
   readonly attribute DOMString activeCacheName;
   readonly attribute DOMString waitingCacheName;
 };
 
-[scriptable, builtinclass, uuid(aee94712-9adb-4c0b-80a7-a8df34dfa2e8)]
+[scriptable, builtinclass, uuid(e9abb123-0099-4d9e-85db-c8cd0aff19e6)]
 interface nsIServiceWorkerManager : nsISupports
 {
   /**
    * Registers a ServiceWorker with script loaded from `aScriptURI` to act as
    * the ServiceWorker for aScope.  Requires a valid entry settings object on
    * the stack. This means you must call this from content code 'within'
    * a window.
    *
@@ -121,20 +121,20 @@ interface nsIServiceWorkerManager : nsIS
 
   // Note: This is meant to be used only by about:serviceworkers.
   // It calls unregister() in each child process. The callback is used to
   // inform when unregister() is completed on the current process.
   void propagateUnregister(in nsIPrincipal aPrincipal,
                            in nsIServiceWorkerUnregisterCallback aCallback,
                            in DOMString aScope);
 
-  [implicit_jscontext] void sendPushEvent(in jsval aOriginAttributes,
-                                          in ACString aScope,
-                                          in DOMString aData);
-  [implicit_jscontext] void sendPushSubscriptionChangeEvent(in jsval aOriginAttributes,
-                                                            in ACString scope);
+  void sendPushEvent(in ACString aOriginAttributes,
+                     in ACString aScope,
+                     in DOMString aData);
+  void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
+                                       in ACString scope);
 
   void updateAllRegistrations();
 };
 
 %{ C++
 #define SERVICEWORKERMANAGER_CONTRACTID "@mozilla.org/serviceworkers/manager;1"
 %}
--- a/dom/interfaces/push/nsIPushNotificationService.idl
+++ b/dom/interfaces/push/nsIPushNotificationService.idl
@@ -6,17 +6,17 @@
 #include "nsISupports.idl"
 
 /**
  * A service for components to subscribe and receive push messages from web
  * services. This functionality is exposed to content via the Push API, which
  * uses service workers to notify applications. This interface exists to allow
  * privileged code to receive messages without migrating to service workers.
  */
-[scriptable, uuid(3da6a16c-69f8-4843-9149-1e89d58a53e2)]
+[scriptable, uuid(abde228b-7d14-4cab-b1f9-9f87750ede0f)]
 interface nsIPushNotificationService : nsISupports
 {
   /**
    * Creates a push subscription for the given |scope| URL and |pageURL|.
    * Returns a promise for the new subscription record, or the existing
    * record if this |scope| already has a subscription.
    *
    * The |pushEndpoint| property of the subscription record is a URL string
@@ -27,28 +27,28 @@ interface nsIPushNotificationService : n
    * notification, with an `nsIPushObserverNotification` as the subject and
    * the |scope| as the data.
    *
    * If the server drops a subscription, a `push-subscription-change` observer
    * will be fired, with the subject set to `null` and the data set to |scope|.
    * Servers may drop subscriptions at any time, so callers should recreate
    * subscriptions if desired.
    */
-  jsval register(in string scope, [optional] in string pageURL);
+  jsval register(in string scope, in jsval originAttributes);
 
   /**
    * Revokes a push subscription for the given |scope|. Returns a promise
    * for the revoked subscription record, or `null` if the |scope| is not
    * subscribed to receive notifications.
    */
-  jsval unregister(in string scope);
+  jsval unregister(in string scope, in jsval originAttributes);
 
   /**
    * Returns a promise for the subscription record associated with the
    * given |scope|, or `null` if the |scope| does not have a subscription.
    */
-  jsval registration(in string scope);
+  jsval registration(in string scope, in jsval originAttributes);
 
   /**
    * Clear all subscriptions
    */
    jsval clearAll();
 };
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2622,43 +2622,61 @@ ContentParent::RecvSetClipboard(const IP
         rv = dataWrapper->SetData(text);
         NS_ENSURE_SUCCESS(rv, true);
 
         rv = trans->SetTransferData(item.flavor().get(), dataWrapper,
                                     text.Length() * sizeof(char16_t));
 
         NS_ENSURE_SUCCESS(rv, true);
       } else if (item.data().type() == IPCDataTransferData::TnsCString) {
-        const IPCDataTransferImage& imageDetails = item.imageDetails();
-        const gfxIntSize size(imageDetails.width(), imageDetails.height());
-        if (!size.width || !size.height) {
-          return true;
+        if (item.flavor().EqualsLiteral(kJPEGImageMime) ||
+            item.flavor().EqualsLiteral(kJPGImageMime) ||
+            item.flavor().EqualsLiteral(kPNGImageMime) ||
+            item.flavor().EqualsLiteral(kGIFImageMime)) {
+          const IPCDataTransferImage& imageDetails = item.imageDetails();
+          const gfxIntSize size(imageDetails.width(), imageDetails.height());
+          if (!size.width || !size.height) {
+            return true;
+          }
+
+          nsCString text = item.data().get_nsCString();
+          mozilla::RefPtr<gfx::DataSourceSurface> image =
+            new mozilla::gfx::SourceSurfaceRawData();
+          mozilla::gfx::SourceSurfaceRawData* raw =
+            static_cast<mozilla::gfx::SourceSurfaceRawData*>(image.get());
+          raw->InitWrappingData(
+            reinterpret_cast<uint8_t*>(const_cast<nsCString&>(text).BeginWriting()),
+            size, imageDetails.stride(),
+            static_cast<mozilla::gfx::SurfaceFormat>(imageDetails.format()), false);
+          raw->GuaranteePersistance();
+
+          nsRefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, size);
+          nsCOMPtr<imgIContainer> imageContainer(image::ImageOps::CreateFromDrawable(drawable));
+
+          nsCOMPtr<nsISupportsInterfacePointer>
+            imgPtr(do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv));
+
+          rv = imgPtr->SetData(imageContainer);
+          NS_ENSURE_SUCCESS(rv, true);
+
+          trans->SetTransferData(item.flavor().get(), imgPtr, sizeof(nsISupports*));
+        } else {
+          nsCOMPtr<nsISupportsCString> dataWrapper =
+            do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+          NS_ENSURE_SUCCESS(rv, true);
+
+          const nsCString& text = item.data().get_nsCString();
+          rv = dataWrapper->SetData(text);
+          NS_ENSURE_SUCCESS(rv, true);
+
+          rv = trans->SetTransferData(item.flavor().get(), dataWrapper,
+                                      text.Length());
+
+          NS_ENSURE_SUCCESS(rv, true);
         }
-
-        nsCString text = item.data().get_nsCString();
-        mozilla::RefPtr<gfx::DataSourceSurface> image =
-          new mozilla::gfx::SourceSurfaceRawData();
-        mozilla::gfx::SourceSurfaceRawData* raw =
-          static_cast<mozilla::gfx::SourceSurfaceRawData*>(image.get());
-        raw->InitWrappingData(
-          reinterpret_cast<uint8_t*>(const_cast<nsCString&>(text).BeginWriting()),
-          size, imageDetails.stride(),
-          static_cast<mozilla::gfx::SurfaceFormat>(imageDetails.format()), false);
-        raw->GuaranteePersistance();
-
-        nsRefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, size);
-        nsCOMPtr<imgIContainer> imageContainer(image::ImageOps::CreateFromDrawable(drawable));
-
-        nsCOMPtr<nsISupportsInterfacePointer>
-          imgPtr(do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv));
-
-        rv = imgPtr->SetData(imageContainer);
-        NS_ENSURE_SUCCESS(rv, true);
-
-        trans->SetTransferData(item.flavor().get(), imgPtr, sizeof(nsISupports*));
       }
     }
 
     trans->SetIsPrivateData(aIsPrivateData);
 
     clipboard->SetData(trans, nullptr, aWhichClipboard);
     return true;
 }
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -90,17 +90,20 @@ skip-if = buildapp == 'b2g' || buildapp 
 [test_peerConnection_capturedVideo.html]
 tags=capturestream
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_captureStream_canvas_2d.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_captureStream_canvas_webgl.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_close.html]
+[test_peerConnection_closeDuringIce.html]
 [test_peerConnection_errorCallbacks.html]
+[test_peerConnection_iceFailure.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' # Disabling because of test failures on B2G emulator
 [test_peerConnection_forwarding_basicAudioVideoCombined.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_noTrickleAnswer.html]
 skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
 [test_peerConnection_noTrickleOffer.html]
 skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
 [test_peerConnection_noTrickleOfferAnswer.html]
 skip-if = toolkit == 'gonk' # B2G emulator is too slow to handle a two-way audio call reliably
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -400,17 +400,17 @@ var commandsPeerConnectionOfferAnswer = 
     return test.getSignalingMessage("answer").then(message => {
       ok("answer" in message, "Got an answer message");
       test._remote_answer = new mozRTCSessionDescription(message.answer);
       test._answer_constraints = message.answer_constraints;
     });
   },
 
   function PC_LOCAL_SET_REMOTE_DESCRIPTION(test) {
-    test.setRemoteDescription(test.pcLocal, test._remote_answer, STABLE)
+    return test.setRemoteDescription(test.pcLocal, test._remote_answer, STABLE)
       .then(() => {
         is(test.pcLocal.signalingState, STABLE,
            "signalingState after local setRemoteDescription is 'stable'");
       });
   },
   function PC_REMOTE_SANE_LOCAL_SDP(test) {
     test.pcRemote.verifySdp(test._remote_answer, "answer",
                             test._offer_constraints, test._offer_options,
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_closeDuringIce.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "1087629",
+    title: "Close PCs during ICE connectivity check"
+  });
+
+// Test closeDuringIce to simulate problems during peer connections
+
+
+function PC_LOCAL_SETUP_NULL_ICE_HANDLER(test) {
+  test.pcLocal.setupIceCandidateHandler(test, function() {}, function () {});
+}
+function PC_REMOTE_SETUP_NULL_ICE_HANDLER(test) {
+  test.pcRemote.setupIceCandidateHandler(test, function() {}, function () {});
+}
+function PC_REMOTE_ADD_FAKE_ICE_CANDIDATE(test) {
+  var cand = new mozRTCIceCandidate({"candidate":"candidate:0 1 UDP 2130379007 192.0.2.1 12345 typ host","sdpMid":"","sdpMLineIndex":0});
+  test.pcRemote.storeOrAddIceCandidate(cand);
+  info(test.pcRemote + " Stored fake candidate: " + JSON.stringify(cand));
+}
+function PC_LOCAL_ADD_FAKE_ICE_CANDIDATE(test) {
+  var cand = new mozRTCIceCandidate({"candidate":"candidate:0 1 UDP 2130379007 192.0.2.2 56789 typ host","sdpMid":"","sdpMLineIndex":0});
+    test.pcLocal.storeOrAddIceCandidate(cand);
+  info(test.pcLocal + " Stored fake candidate: " + JSON.stringify(cand));
+}
+function PC_LOCAL_CLOSE_DURING_ICE(test) {
+  return test.pcLocal.iceChecking.then(() => {
+    test.pcLocal.onsignalingstatechange = function () {};
+    test.pcLocal.close();
+    });
+}
+function PC_REMOTE_CLOSE_DURING_ICE(test) {
+  return test.pcRemote.iceChecking.then(() => {
+    test.pcRemote.onsignalingstatechange = function () {};
+    test.pcRemote.close();
+    });
+}
+function PC_LOCAL_WAIT_FOR_ICE_CHECKING(test) {
+  var resolveIceChecking;
+  test.pcLocal.iceChecking = new Promise(r => resolveIceChecking = r);
+  test.pcLocal.ice_connection_callbacks.checkIceStatus = () => {
+	if (test.pcLocal._pc.iceConnectionState === "checking") {
+	  resolveIceChecking();
+	}
+  }
+}
+function PC_REMOTE_WAIT_FOR_ICE_CHECKING(test) {
+  var resolveIceChecking;
+  test.pcRemote.iceChecking = new Promise(r => resolveIceChecking = r);
+  test.pcRemote.ice_connection_callbacks.checkIceStatus = () => {
+	if (test.pcRemote._pc.iceConnectionState === "checking") {
+	  resolveIceChecking();
+	}
+  }
+}
+
+runNetworkTest(() => {
+  var test = new PeerConnectionTest();
+  test.setMediaConstraints([{audio: true}], [{audio: true}]);
+  test.chain.replace("PC_LOCAL_SETUP_ICE_HANDLER", PC_LOCAL_SETUP_NULL_ICE_HANDLER);
+  test.chain.replace("PC_REMOTE_SETUP_ICE_HANDLER", PC_REMOTE_SETUP_NULL_ICE_HANDLER);
+  test.chain.insertAfter("PC_REMOTE_SETUP_NULL_ICE_HANDLER", PC_LOCAL_WAIT_FOR_ICE_CHECKING);
+  test.chain.insertAfter("PC_LOCAL_WAIT_FOR_ICE_CHECKING", PC_REMOTE_WAIT_FOR_ICE_CHECKING);
+  test.chain.removeAfter("PC_LOCAL_SET_REMOTE_DESCRIPTION");
+  test.chain.append([PC_REMOTE_ADD_FAKE_ICE_CANDIDATE, PC_LOCAL_ADD_FAKE_ICE_CANDIDATE,
+	PC_LOCAL_CLOSE_DURING_ICE, PC_REMOTE_CLOSE_DURING_ICE]);
+  test.run();
+});
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_iceFailure.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "1087629",
+    title: "Wait for ICE failure"
+  });
+
+// Test iceFailure
+
+function PC_LOCAL_SETUP_NULL_ICE_HANDLER(test) {
+  test.pcLocal.setupIceCandidateHandler(test, function() {}, function () {});
+}
+function PC_REMOTE_SETUP_NULL_ICE_HANDLER(test) {
+  test.pcRemote.setupIceCandidateHandler(test, function() {}, function () {});
+}
+function PC_REMOTE_ADD_FAKE_ICE_CANDIDATE(test) {
+  var cand = new mozRTCIceCandidate({"candidate":"candidate:0 1 UDP 2130379007 192.0.2.1 12345 typ host","sdpMid":"","sdpMLineIndex":0});
+  test.pcRemote.storeOrAddIceCandidate(cand);
+  info(test.pcRemote + " Stored fake candidate: " + JSON.stringify(cand));
+}
+function PC_LOCAL_ADD_FAKE_ICE_CANDIDATE(test) {
+  var cand = new mozRTCIceCandidate({"candidate":"candidate:0 1 UDP 2130379007 192.0.2.2 56789 typ host","sdpMid":"","sdpMLineIndex":0});
+    test.pcLocal.storeOrAddIceCandidate(cand);
+  info(test.pcLocal + " Stored fake candidate: " + JSON.stringify(cand));
+}
+function PC_LOCAL_WAIT_FOR_ICE_FAILURE(test) {
+  return test.pcLocal.iceFailed.then(() => {
+    ok(true, this.pcLocal + " Ice Failure Reached.");
+	});
+}
+function PC_REMOTE_WAIT_FOR_ICE_FAILURE(test) {
+  return test.pcRemote.iceFailed.then(() => {
+    ok(true, this.pcRemote + " Ice Failure Reached.");
+    });
+}
+function PC_LOCAL_WAIT_FOR_ICE_FAILED(test) {
+  var resolveIceFailed;
+  test.pcLocal.iceFailed = new Promise(r => resolveIceFailed = r);
+  test.pcLocal.ice_connection_callbacks.checkIceStatus = () => {
+	if (test.pcLocal._pc.iceConnectionState === "failed") {
+	  resolveIceFailed();
+	}
+  }
+}
+function PC_REMOTE_WAIT_FOR_ICE_FAILED(test) {
+  var resolveIceFailed;
+  test.pcRemote.iceFailed = new Promise(r => resolveIceFailed = r);
+  test.pcRemote.ice_connection_callbacks.checkIceStatus = () => {
+	if (test.pcRemote._pc.iceConnectionState === "failed") {
+	  resolveIceFailed();
+	}
+  }
+}
+
+runNetworkTest(() => {
+  SpecialPowers.pushPrefEnv({
+    'set': [
+      ['media.peerconnection.ice.stun_client_maximum_transmits', 3],
+      ['media.peerconnection.ice.trickle_grace_period', 3000],
+    ]
+  }, function() {
+    var test = new PeerConnectionTest();
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.chain.replace("PC_LOCAL_SETUP_ICE_HANDLER", PC_LOCAL_SETUP_NULL_ICE_HANDLER);
+    test.chain.replace("PC_REMOTE_SETUP_ICE_HANDLER", PC_REMOTE_SETUP_NULL_ICE_HANDLER);
+    test.chain.insertAfter("PC_REMOTE_SETUP_NULL_ICE_HANDLER", PC_LOCAL_WAIT_FOR_ICE_FAILED);
+    test.chain.insertAfter("PC_LOCAL_WAIT_FOR_ICE_FAILED", PC_REMOTE_WAIT_FOR_ICE_FAILED);
+    test.chain.removeAfter("PC_LOCAL_SET_REMOTE_DESCRIPTION");
+    test.chain.append([PC_REMOTE_ADD_FAKE_ICE_CANDIDATE, PC_LOCAL_ADD_FAKE_ICE_CANDIDATE,
+	PC_LOCAL_WAIT_FOR_ICE_FAILURE, PC_REMOTE_WAIT_FOR_ICE_FAILURE]);
+    test.run();
+  });
+});
+</script>
+</pre>
+</body>
+</html>
--- a/dom/messagechannel/MessagePort.cpp
+++ b/dom/messagechannel/MessagePort.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/dom/MessagePortChild.h"
 #include "mozilla/dom/MessagePortList.h"
 #include "mozilla/dom/PMessagePort.h"
 #include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/unused.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsPresContext.h"
 #include "ScriptSettings.h"
 #include "SharedMessagePortMessage.h"
 
 #include "nsIBFCacheEntry.h"
 #include "nsIDocument.h"
@@ -244,16 +245,60 @@ public:
 
 private:
   ~MessagePortFeature()
   {
     MOZ_COUNT_DTOR(MessagePortFeature);
   }
 };
 
+class ForceCloseHelper final : public nsIIPCBackgroundChildCreateCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  static void ForceClose(const MessagePortIdentifier& aIdentifier)
+  {
+    PBackgroundChild* actor =
+      mozilla::ipc::BackgroundChild::GetForCurrentThread();
+    if (actor) {
+      unused << actor->SendMessagePortForceClose(aIdentifier.uuid(),
+                                                 aIdentifier.destinationUuid(),
+                                                 aIdentifier.sequenceId());
+      return;
+    }
+
+    nsRefPtr<ForceCloseHelper> helper = new ForceCloseHelper(aIdentifier);
+    if (NS_WARN_IF(!mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(helper))) {
+      MOZ_CRASH();
+    }
+  }
+
+private:
+  explicit ForceCloseHelper(const MessagePortIdentifier& aIdentifier)
+    : mIdentifier(aIdentifier)
+  {}
+
+  ~ForceCloseHelper() {}
+
+  void ActorFailed() override
+  {
+    MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+  }
+
+  void ActorCreated(mozilla::ipc::PBackgroundChild* aActor) override
+  {
+    ForceClose(mIdentifier);
+  }
+
+  const MessagePortIdentifier mIdentifier;
+};
+
+NS_IMPL_ISUPPORTS(ForceCloseHelper, nsIIPCBackgroundChildCreateCallback)
+
 } // anonymous namespace
 
 MessagePort::MessagePort(nsPIDOMWindow* aWindow)
   : MessagePortBase(aWindow)
   , mInnerID(0)
   , mMessageQueueEnabled(false)
   , mIsKeptAlive(false)
 {
@@ -858,10 +903,16 @@ MessagePort::RemoveDocFromBFCache()
   nsCOMPtr<nsIBFCacheEntry> bfCacheEntry = doc->GetBFCacheEntry();
   if (!bfCacheEntry) {
     return;
   }
 
   bfCacheEntry->RemoveFromBFCacheSync();
 }
 
+/* static */ void
+MessagePort::ForceClose(const MessagePortIdentifier& aIdentifier)
+{
+  ForceCloseHelper::ForceClose(aIdentifier);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/messagechannel/MessagePort.h
+++ b/dom/messagechannel/MessagePort.h
@@ -81,16 +81,19 @@ public:
   static already_AddRefed<MessagePort>
   Create(nsPIDOMWindow* aWindow, const nsID& aUUID,
          const nsID& aDestinationUUID, ErrorResult& aRv);
 
   static already_AddRefed<MessagePort>
   Create(nsPIDOMWindow* aWindow, const MessagePortIdentifier& aIdentifier,
          ErrorResult& aRv);
 
+  static void
+  ForceClose(const MessagePortIdentifier& aIdentifier);
+
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   virtual void
   PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
               const Optional<Sequence<JS::Value>>& aTransferable,
               ErrorResult& aRv) override;
 
--- a/dom/messagechannel/MessagePortParent.cpp
+++ b/dom/messagechannel/MessagePortParent.cpp
@@ -154,10 +154,24 @@ MessagePortParent::CloseAndDelete()
 
 void
 MessagePortParent::Close()
 {
   mService = nullptr;
   mEntangled = false;
 }
 
+/* static */ bool
+MessagePortParent::ForceClose(const nsID& aUUID,
+                              const nsID& aDestinationUUID,
+                              const uint32_t& aSequenceID)
+{
+  MessagePortService* service =  MessagePortService::Get();
+  if (!service) {
+    NS_WARNING("The service must exist if we want to close an existing MessagePort.");
+    return false;
+  }
+
+  return service->ForceClose(aUUID, aDestinationUUID, aSequenceID);
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/messagechannel/MessagePortParent.h
+++ b/dom/messagechannel/MessagePortParent.h
@@ -31,16 +31,20 @@ public:
     return mCanSendData;
   }
 
   const nsID& ID() const
   {
     return mUUID;
   }
 
+  static bool ForceClose(const nsID& aUUID,
+                         const nsID& aDestinationUUID,
+                         const uint32_t& aSequenceID);
+
 private:
   virtual bool RecvPostMessages(nsTArray<MessagePortMessage>&& aMessages)
                                                                        override;
 
   virtual bool RecvDisentangle(nsTArray<MessagePortMessage>&& aMessages)
                                                                        override;
 
   virtual bool RecvStopSendingData() override;
--- a/dom/messagechannel/MessagePortService.cpp
+++ b/dom/messagechannel/MessagePortService.cpp
@@ -1,26 +1,37 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MessagePortService.h"
 #include "MessagePortParent.h"
 #include "SharedMessagePortMessage.h"
+#include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 #include "nsDataHashtable.h"
 #include "nsTArray.h"
 
+using mozilla::ipc::AssertIsOnBackgroundThread;
+
 namespace mozilla {
 namespace dom {
 
 namespace {
+
 StaticRefPtr<MessagePortService> gInstance;
+
+void
+AssertIsInMainProcess()
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+}
+
 } // anonymous namespace
 
 class MessagePortService::MessagePortServiceData final
 {
 public:
   explicit MessagePortServiceData(const nsID& aDestinationUUID)
     : mDestinationUUID(aDestinationUUID)
     , mSequenceID(1)
@@ -49,18 +60,30 @@ public:
     MessagePortParent* mParent;
   };
 
   FallibleTArray<NextParent> mNextParents;
   FallibleTArray<nsRefPtr<SharedMessagePortMessage>> mMessages;
 };
 
 /* static */ MessagePortService*
+MessagePortService::Get()
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
+  return gInstance;
+}
+
+/* static */ MessagePortService*
 MessagePortService::GetOrCreate()
 {
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
   if (!gInstance) {
     gInstance = new MessagePortService();
   }
 
   return gInstance;
 }
 
 bool
@@ -243,16 +266,22 @@ MessagePortService::CloseAll(const nsID&
     parent.mParent->CloseAndDelete();
   }
 
   nsID destinationUUID = data->mDestinationUUID;
   mPorts.Remove(aUUID);
 
   CloseAll(destinationUUID);
 
+  // CloseAll calls itself recursively and it can happen that it deletes
+  // itself. Before continuing we must check if we are still alive.
+  if (!gInstance) {
+    return;
+  }
+
 #ifdef DEBUG
   mPorts.EnumerateRead(CloseAllDebugCheck, const_cast<nsID*>(&aUUID));
 #endif
 
   MaybeShutdown();
 }
 
 // This service can be dismissed when there are not active ports.
@@ -319,10 +348,31 @@ MessagePortService::ParentDestroy(Messag
        break;
       }
     }
   }
 
   CloseAll(aParent->ID());
 }
 
+bool
+MessagePortService::ForceClose(const nsID& aUUID,
+                               const nsID& aDestinationUUID,
+                               const uint32_t& aSequenceID)
+{
+  MessagePortServiceData* data;
+  if (!mPorts.Get(aUUID, &data)) {
+    NS_WARNING("Unknown MessagePort in ForceClose()");
+    return true;
+  }
+
+  if (!data->mDestinationUUID.Equals(aDestinationUUID) ||
+      data->mSequenceID != aSequenceID) {
+    NS_WARNING("DestinationUUID and/or sequenceID do not match.");
+    return false;
+  }
+
+  CloseAll(aUUID);
+  return true;
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/messagechannel/MessagePortService.h
+++ b/dom/messagechannel/MessagePortService.h
@@ -15,16 +15,17 @@ namespace dom {
 class MessagePortParent;
 class SharedMessagePortMessage;
 
 class MessagePortService final
 {
 public:
   NS_INLINE_DECL_REFCOUNTING(MessagePortService)
 
+  static MessagePortService* Get();
   static MessagePortService* GetOrCreate();
 
   bool RequestEntangling(MessagePortParent* aParent,
                          const nsID& aDestinationUUID,
                          const uint32_t& aSequenceID);
 
   bool DisentanglePort(
                  MessagePortParent* aParent,
@@ -33,16 +34,20 @@ public:
   bool ClosePort(MessagePortParent* aParent);
 
   bool PostMessages(
                  MessagePortParent* aParent,
                  FallibleTArray<nsRefPtr<SharedMessagePortMessage>>& aMessages);
 
   void ParentDestroy(MessagePortParent* aParent);
 
+  bool ForceClose(const nsID& aUUID,
+                  const nsID& aDestinationUUID,
+                  const uint32_t& aSequenceID);
+
 private:
   ~MessagePortService() {}
 
   void CloseAll(const nsID& aUUID);
   void MaybeShutdown();
 
   class MessagePortServiceData;
 
--- a/dom/messagechannel/MessagePortUtils.cpp
+++ b/dom/messagechannel/MessagePortUtils.cpp
@@ -136,17 +136,17 @@ ReadTransfer(JSContext* aCx, JSStructure
   }
 
   MOZ_ASSERT(aContent == 0);
   MOZ_ASSERT(aExtraData < closure->mClosure.mMessagePortIdentifiers.Length());
 
   ErrorResult rv;
   nsRefPtr<MessagePort> port =
     MessagePort::Create(closure->mWindow,
-                        closure->mClosure.mMessagePortIdentifiers[(uint32_t)aExtraData],
+                        closure->mClosure.mMessagePortIdentifiers[aExtraData],
                         rv);
   if (NS_WARN_IF(rv.Failed())) {
     return false;
   }
 
   closure->mMessagePorts.AppendElement(port);
 
   JS::Rooted<JS::Value> value(aCx);
@@ -190,23 +190,37 @@ WriteTransfer(JSContext* aCx, JS::Handle
   *aTag = SCTAG_DOM_MAP_MESSAGEPORT;
   *aOwnership = JS::SCTAG_TMO_CUSTOM;
   *aContent = nullptr;
   *aExtraData = closure->mClosure.mMessagePortIdentifiers.Length() - 1;
 
   return true;
 }
 
+void
+FreeTransfer(uint32_t aTag, JS::TransferableOwnership aOwnership,
+             void* aContent, uint64_t aExtraData, void* aClosure)
+{
+  MOZ_ASSERT(aClosure);
+  auto* closure = static_cast<StructuredCloneClosureInternal*>(aClosure);
+
+  if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
+    MOZ_ASSERT(!aContent);
+    MOZ_ASSERT(aExtraData < closure->mClosure.mMessagePortIdentifiers.Length());
+    MessagePort::ForceClose(closure->mClosure.mMessagePortIdentifiers[(uint32_t)aExtraData]);
+  }
+}
+
 const JSStructuredCloneCallbacks gCallbacks = {
   Read,
   Write,
   Error,
   ReadTransfer,
   WriteTransfer,
-  nullptr
+  FreeTransfer,
 };
 
 } // anonymous namespace
 
 bool
 ReadStructuredCloneWithTransfer(JSContext* aCx, nsTArray<uint8_t>& aData,
                                 const StructuredCloneClosure& aClosure,
                                 JS::MutableHandle<JS::Value> aClone,
--- a/dom/messagechannel/tests/mochitest.ini
+++ b/dom/messagechannel/tests/mochitest.ini
@@ -18,8 +18,9 @@ support-files =
 [test_messageChannel_start.html]
 [test_messageChannel_transferable.html]
 [test_messageChannel_unshipped.html]
 [test_messageChannel_worker.html]
 [test_messageChannel_selfTransferring.html]
 [test_messageChannel_sharedWorker.html]
 [test_messageChannel_sharedWorker2.html]
 [test_messageChannel_any.html]
+[test_messageChannel_forceClose.html]
new file mode 100644
--- /dev/null
+++ b/dom/messagechannel/tests/test_messageChannel_forceClose.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1176034
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1176034 - start/close</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=1176034">Mozilla Bug 1176034</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+  <script type="application/javascript">
+
+  function runTests() {
+    var mc = new MessageChannel();
+
+    try {
+      postMessage(42, "*", [ mc.port1, window ]);
+      ok(false, "Something went wrong.");
+    } catch(e) {
+      ok(true, "PostMessage should fail and we should not leak.");
+    }
+
+    SimpleTest.finish();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [["dom.messageChannel.enabled", true]]}, runTests);
+  </script>
+</body>
+</html>
--- a/dom/push/Push.js
+++ b/dom/push/Push.js
@@ -18,21 +18,21 @@ const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 
 const PUSH_SUBSCRIPTION_CID = Components.ID("{CA86B665-BEDA-4212-8D0F-5C9F65270B58}");
 
-function PushSubscription(pushEndpoint, scope, pageURL) {
+function PushSubscription(pushEndpoint, scope, principal) {
   debug("PushSubscription Constructor");
   this._pushEndpoint = pushEndpoint;
   this._scope = scope;
-  this._pageURL = pageURL;
+  this._principal = principal;
 }
 
 PushSubscription.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   contractID: "@mozilla.org/push/PushSubscription;1",
 
   classID : PUSH_SUBSCRIPTION_CID,
@@ -48,59 +48,58 @@ PushSubscription.prototype = {
       "PushService:Unregister:OK",
       "PushService:Unregister:KO",
     ]);
 
     this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
                    .getService(Ci.nsISyncMessageSender);
   },
 
-  __init: function(endpoint, scope, pageURL) {
+  __init: function(endpoint, scope, principal) {
     this._pushEndpoint = endpoint;
     this._scope = scope;
-    this._pageURL = pageURL;
+    this._principal = principal;
   },
 
   get endpoint() {
     return this._pushEndpoint;
   },
 
   unsubscribe: function() {
     debug("unsubscribe! ")
 
     let promiseInit = function(resolve, reject) {
       let resolverId = this.getPromiseResolverId({resolve: resolve,
                                                   reject: reject });
 
       this._cpmm.sendAsyncMessage("Push:Unregister", {
-                                  pageURL: this._pageURL,
                                   scope: this._scope,
                                   pushEndpoint: this._pushEndpoint,
                                   requestID: resolverId
-                                });
+                                }, null, this._principal);
     }.bind(this);
 
     return this.createPromise(promiseInit);
   },
 
   receiveMessage: function(aMessage) {
     debug("push subscription receiveMessage(): " + JSON.stringify(aMessage))
 
     let json = aMessage.data;
     let resolver = this.takePromiseResolver(json.requestID);
     if (resolver == null) {
       return;
     }
 
     switch (aMessage.name) {
       case "PushService:Unregister:OK":
-        resolver.resolve(false);
+        resolver.resolve(true);
         break;
       case "PushService:Unregister:KO":
-        resolver.reject(true);
+        resolver.resolve(false);
         break;
       default:
         debug("NOT IMPLEMENTED! receiveMessage for " + aMessage.name);
     }
   },
 
 };
 
@@ -128,45 +127,43 @@ Push.prototype = {
 
   init: function(aWindow) {
     // Set debug first so that all debugging actually works.
     // NOTE: We don't add an observer here like in PushService. Flipping the
     // pref will require a reload of the app/page, which seems acceptable.
     gDebuggingEnabled = Services.prefs.getBoolPref("dom.push.debug");
     debug("init()");
 
-    this._pageURL = aWindow.document.nodePrincipal.URI;
     this._window = aWindow;
 
     this.initDOMRequestHelper(aWindow, [
       "PushService:Register:OK",
       "PushService:Register:KO",
       "PushService:Registration:OK",
       "PushService:Registration:KO"
     ]);
 
     this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
                    .getService(Ci.nsISyncMessageSender);
+    this._principal = aWindow.document.nodePrincipal;
   },
 
   setScope: function(scope){
     debug('setScope ' + scope);
     this._scope = scope;
   },
 
   askPermission: function (aAllowCallback, aCancelCallback) {
     debug("askPermission");
 
     let principal = this._window.document.nodePrincipal;
     let type = "push";
     let permValue =
       Services.perms.testExactPermissionFromPrincipal(principal, type);
 
-    debug("Existing permission " + permValue);
-
     if (permValue == Ci.nsIPermissionManager.ALLOW_ACTION) {
         aAllowCallback();
       return;
     }
 
     if (permValue == Ci.nsIPermissionManager.DENY_ACTION) {
       aCancelCallback();
       return;
@@ -214,31 +211,32 @@ Push.prototype = {
 
     if (!resolver) {
       return;
     }
 
     switch (aMessage.name) {
       case "PushService:Register:OK":
       {
-        let subscription = new this._window.PushSubscription(json.pushEndpoint,
-                                                             this._scope,
-                                                             this._pageURL.spec);
+        let subscription =
+          new this._window.PushSubscription(json.pushEndpoint, this._scope,
+                                            this._principal);
         resolver.resolve(subscription);
         break;
       }
       case "PushService:Register:KO":
         resolver.reject(null);
         break;
       case "PushService:Registration:OK":
       {
         let subscription = null;
         try {
-          subscription = new this._window.PushSubscription(json.registration.pushEndpoint,
-                                                          this._scope, this._pageURL.spec);
+          subscription =
+            new this._window.PushSubscription(json.registration.pushEndpoint,
+                                              this._scope, this._principal);
         } catch(error) {
         }
         resolver.resolve(subscription);
         break;
       }
       case "PushService:Registration:KO":
         resolver.reject(null);
         break;
@@ -250,20 +248,19 @@ Push.prototype = {
   subscribe: function() {
     debug("subscribe()");
     let p = this.createPromise(function(resolve, reject) {
       let resolverId = this.getPromiseResolverId({ resolve: resolve, reject: reject });
 
       this.askPermission(
         function() {
           this._cpmm.sendAsyncMessage("Push:Register", {
-                                      pageURL: this._pageURL.spec,
                                       scope: this._scope,
                                       requestID: resolverId
-                                    });
+                                    }, null, this._principal);
         }.bind(this),
 
         function() {
           reject("PermissionDeniedError");
         }
       );
     }.bind(this));
     return p;
@@ -274,36 +271,38 @@ Push.prototype = {
 
     let p = this.createPromise(function(resolve, reject) {
 
       let resolverId = this.getPromiseResolverId({ resolve: resolve, reject: reject });
 
       this.askPermission(
         function() {
           this._cpmm.sendAsyncMessage("Push:Registration", {
-                                      pageURL: this._pageURL.spec,
                                       scope: this._scope,
                                       requestID: resolverId
-                                    });
+                                    }, null, this._principal);
         }.bind(this),
 
         function() {
           reject("PermissionDeniedError");
         }
       );
     }.bind(this));
     return p;
   },
 
   hasPermission: function() {
-    debug("getSubscription()" + this._scope);
+    debug("hasPermission()" + this._scope);
 
     let p = this.createPromise(function(resolve, reject) {
-      let permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
-      let permission = permissionManager.testExactPermission(this._pageURL, "push");
+      let permissionManager = Cc["@mozilla.org/permissionmanager;1"]
+                              .getService(Ci.nsIPermissionManager);
+      let permission =
+        permissionManager.testExactPermissionFromPrincipal(this._principal,
+                                                           "push");
 
       let pushPermissionStatus = "default";
       if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
         pushPermissionStatus = "granted";
       } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
         pushPermissionStatus = "denied";
       }
       resolve(pushPermissionStatus);
--- a/dom/push/PushDB.jsm
+++ b/dom/push/PushDB.jsm
@@ -142,47 +142,85 @@ this.PushDB.prototype = {
           };
         },
         resolve,
         reject
       )
     );
   },
 
-
-  getByScope: function(aScope) {
-    debug("getByScope() " + aScope);
+  // Perform a unique match against { scope, originAttributes }
+  getByIdentifiers: function(aPageRecord) {
+    debug("getByIdentifiers() { " + aPageRecord.scope + ", " +
+          JSON.stringify(aPageRecord.originAttributes) + " }");
+    if (!aPageRecord.scope || aPageRecord.originAttributes == undefined) {
+      return Promise.reject(
+               new TypeError("Scope and originAttributes are required! " +
+                             JSON.stringify(aPageRecord)));
+    }
 
     return new Promise((resolve, reject) =>
       this.newTxn(
         "readonly",
         this._dbStoreName,
         function txnCb(aTxn, aStore) {
           aTxn.result = undefined;
 
-          let index = aStore.index("scope");
-          index.get(aScope).onsuccess = function setTxnResult(aEvent) {
+          let index = aStore.index("identifiers");
+          let request = index.get(IDBKeyRange.only([aPageRecord.scope, aPageRecord.originAttributes]));
+          request.onsuccess = function setTxnResult(aEvent) {
             aTxn.result = aEvent.target.result;
-            debug("Fetch successful " + aEvent.target.result);
-          };
+          }
         },
         resolve,
         reject
       )
     );
   },
 
+  _getAllByKey: function(aKeyName, aKeyValue) {
+    return new Promise((resolve, reject) =>
+      this.newTxn(
+        "readonly",
+        this._dbStoreName,
+        function txnCb(aTxn, aStore) {
+          aTxn.result = undefined;
+
+          let index = aStore.index(aKeyName);
+          // It seems ok to use getAll here, since unlike contacts or other
+          // high storage APIs, we don't expect more than a handful of
+          // registrations per domain, and usually only one.
+          let getAllReq = index.mozGetAll(aKeyValue);
+          getAllReq.onsuccess = function setTxnResult(aEvent) {
+            aTxn.result = aEvent.target.result;
+          }
+        },
+        resolve,
+        reject
+      )
+    );
+  },
+
+  // aOriginAttributes must be a string!
+  getAllByOriginAttributes: function(aOriginAttributes) {
+    if (typeof aOriginAttributes !== "string") {
+      return Promise.reject("Expected string!");
+    }
+    return this._getAllByKey("originAttributes", aOriginAttributes);
+  },
+
   getAllKeyIDs: function() {
     debug("getAllKeyIDs()");
 
     return new Promise((resolve, reject) =>
       this.newTxn(
         "readonly",
         this._dbStoreName,
         function txnCb(aTxn, aStore) {
+          aTxn.result = undefined;
           aStore.mozGetAll().onsuccess = function(event) {
             aTxn.result = event.target.result;
           };
         },
         resolve,
         reject
       )
     );
--- a/dom/push/PushNotificationService.js
+++ b/dom/push/PushNotificationService.js
@@ -33,26 +33,26 @@ PushNotificationService.prototype = {
 
   contractID: "@mozilla.org/push/NotificationService;1",
 
   _xpcom_factory: XPCOMUtils.generateSingletonFactory(PushNotificationService),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsIPushNotificationService]),
 
-  register: function register(scope, pageURL) {
-    return PushService._register({scope, pageURL});
+  register: function register(scope, originAttributes) {
+    return PushService._register({scope, originAttributes});
   },
 
-  unregister: function unregister(scope) {
-    return PushService._unregister({scope});
+  unregister: function unregister(scope, originAttributes) {
+    return PushService._unregister({scope, originAttributes});
   },
 
-  registration: function registration(scope) {
-    return PushService._registration({scope});
+  registration: function registration(scope, originAttributes) {
+    return PushService._registration({scope, originAttributes});
   },
 
   clearAll: function clearAll() {
     return PushService._clearAll();
   },
 
   observe: function observe(subject, topic, data) {
     switch (topic) {
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -111,16 +111,36 @@ this.PushService = {
     } else {
       return (this._activated) ? this._activated :
                                  this._activated = new Promise((res, rej) =>
                                    this._notifyActivated = {resolve: res,
                                                             reject: rej});
     }
   },
 
+  _makePendingKey: function(aPageRecord) {
+    return aPageRecord.scope + "|" + aPageRecord.originAttributes;
+  },
+
+  _lookupOrPutPendingRequest: function(aPageRecord) {
+    let key = this._makePendingKey(aPageRecord);
+    if (this._pendingRegisterRequest[key]) {
+      return this._pendingRegisterRequest[key];
+    }
+
+    return this._pendingRegisterRequest[key] = this._registerWithServer(aPageRecord);
+  },
+
+  _deletePendingRequest: function(aPageRecord) {
+    let key = this._makePendingKey(aPageRecord);
+    if (this._pendingRegisterRequest[key]) {
+      delete this._pendingRegisterRequest[key];
+    }
+  },
+
   _setState: function(aNewState) {
     debug("new state: " + aNewState + " old state: " + this._state);
 
     if (this._state == aNewState) {
       return;
     }
 
     if (this._state == PUSH_SERVICE_ACTIVATING) {
@@ -228,34 +248,51 @@ this.PushService = {
         let data = aSubject
                      .QueryInterface(Ci.mozIApplicationClearPrivateDataParams);
         if (!data) {
           debug("webapps-clear-data: Failed to get information about " +
                 "application");
           return;
         }
 
-        // TODO 1149274.  We should support site permissions as well as a way to
-        // go from manifest url to 'all scopes registered for push in this app'
-        let appsService = Cc["@mozilla.org/AppsService;1"]
-                            .getService(Ci.nsIAppsService);
-        let scope = appsService.getScopeByLocalId(data.appId);
-        if (!scope) {
-          debug("webapps-clear-data: No scope found for " + data.appId);
-          return;
-        }
+        var originAttributes =
+          ChromeUtils.originAttributesToSuffix({ appId: data.appId,
+                                                 inBrowser: data.browserOnly });
+        this._db.getAllByOriginAttributes(originAttributes)
+          .then(records => {
+            records.forEach(record => {
+              this._db.delete(this._service.getKeyFromRecord(record))
+                .then(_ => {
+                  // courtesy, but don't establish a connection
+                  // just for it
+                  if (this._ws) {
+                    debug("Had a connection, so telling the server");
+                    this._sendRequest("unregister", {channelID: records.channelID})
+                        .catch(function(e) {
+                          debug("Unregister errored " + e);
+                        });
+                  }
+                }, err => {
+                  debug("webapps-clear-data: " + scope +
+                        " Could not delete entry " + records.channelID);
 
-        this._db.getByScope(scope)
-          .then(record =>
-            Promise.all([
-              this._db.delete(this._service.getKeyFromRecord(record)),
-              this._sendRequest("unregister", record)
-            ])
-          ).catch(_ => {
-            debug("webapps-clear-data: Error in getByScope(" + scope + ")");
+                  // courtesy, but don't establish a connection
+                  // just for it
+                  if (this._ws) {
+                    debug("Had a connection, so telling the server");
+                    this._sendRequest("unregister", {channelID: records.channelID})
+                        .catch(function(e) {
+                          debug("Unregister errored " + e);
+                        });
+                  }
+                  throw "Database error";
+                });
+            });
+          }, _ => {
+            debug("webapps-clear-data: Error in getAllByOriginAttributes(" + originAttributes + ")");
           });
 
         break;
     }
   },
 
   // utility function used to add/remove observers in startObservers() and
   // stopObservers()
@@ -618,33 +655,29 @@ this.PushService = {
 
   // Fires a push-register system message to all applications that have
   // registration.
   _notifyAllAppsRegister: function() {
     debug("notifyAllAppsRegister()");
     // records are objects describing the registration as stored in IndexedDB.
     return this._db.getAllKeyIDs()
       .then(records => {
-        let scopes = new Set();
-        for (let record of records) {
-          scopes.add(record.scope);
-        }
         let globalMM = Cc['@mozilla.org/globalmessagemanager;1']
                          .getService(Ci.nsIMessageListenerManager);
-        for (let scope of scopes) {
+        for (let record of records) {
           // Notify XPCOM observers.
           Services.obs.notifyObservers(
             null,
             "push-subscription-change",
             scope
           );
 
           let data = {
-            originAttributes: {}, // TODO bug 1166350
-            scope: scope
+            originAttributes: record.originAttributes,
+            scope: record.scope
           };
 
           globalMM.broadcastAsyncMessage('pushsubscriptionchange', data);
         }
       });
   },
 
   dropRegistrationAndNotifyApp: function(aKeyId) {
@@ -654,17 +687,17 @@ this.PushService = {
                          .getService(Ci.nsIMessageListenerManager);
         Services.obs.notifyObservers(
           null,
           "push-subscription-change",
           record.scope
         );
 
         let data = {
-          originAttributes: {}, // TODO bug 1166350
+          originAttributes: record.originAttributes,
           scope: record.scope
         };
 
         globalMM.broadcastAsyncMessage('pushsubscriptionchange', data);
       })
       .then(_ => this._db.delete(aKeyId));
   },
 
@@ -676,31 +709,32 @@ this.PushService = {
                            .getService(Ci.nsIMessageListenerManager);
           Services.obs.notifyObservers(
             null,
             "push-subscription-change",
             record.scope
           );
 
           let data = {
-            originAttributes: {}, // TODO bug 1166350
+            originAttributes: record.originAttributes,
             scope: record.scope
           };
 
           globalMM.broadcastAsyncMessage('pushsubscriptionchange', data);
         }));
   },
 
   receivedPushMessage: function(aPushRecord, message) {
     this._db.put(aPushRecord)
       .then(_ => this._notifyApp(aPushRecord, message));
   },
 
   _notifyApp: function(aPushRecord, message) {
-    if (!aPushRecord || !aPushRecord.scope) {
+    if (!aPushRecord || !aPushRecord.scope ||
+        aPushRecord.originAttributes === undefined) {
       debug("notifyApp() something is undefined.  Dropping notification: " +
         JSON.stringify(aPushRecord) );
       return;
     }
 
     debug("notifyApp() " + aPushRecord.scope);
     let scopeURI = Services.io.newURI(aPushRecord.scope, null, null);
     // Notify XPCOM observers.
@@ -723,78 +757,76 @@ this.PushService = {
         Ci.nsIPermissionManager.ALLOW_ACTION) {
       debug("Does not have permission for push.");
       return;
     }
 
     // TODO data.
     let data = {
       payload: message,
-      originAttributes: {}, // TODO bug 1166350
+      originAttributes: aPushRecord.originAttributes,
       scope: aPushRecord.scope
     };
 
     let globalMM = Cc['@mozilla.org/globalmessagemanager;1']
                  .getService(Ci.nsIMessageListenerManager);
     globalMM.broadcastAsyncMessage('push', data);
   },
 
   getByKeyID: function(aKeyID) {
     return this._db.getByKeyID(aKeyID);
   },
 
   getAllKeyIDs: function() {
     return this._db.getAllKeyIDs();
   },
 
-  _sendRequest(action, aRecord) {
+  _sendRequest: function(action, aRecord) {
     if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) {
       return Promise.reject({state: 0, error: "Service not active"});
     } else if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) {
       return Promise.reject({state: 0, error: "NetworkError"});
     }
     return this._service.request(action, aRecord);
   },
 
   /**
    * Called on message from the child process. aPageRecord is an object sent by
    * navigator.push, identifying the sending page and other fields.
    */
   _registerWithServer: function(aPageRecord) {
-    debug("registerWithServer()");
+    debug("registerWithServer()" + JSON.stringify(aPageRecord));
 
     return this._sendRequest("register", aPageRecord)
       .then(pushRecord => this._onRegisterSuccess(pushRecord),
             err => this._onRegisterError(err))
       .then(pushRecord => {
-        if (this._pendingRegisterRequest[aPageRecord.scope]) {
-          delete this._pendingRegisterRequest[aPageRecord.scope];
-        }
+        this._deletePendingRequest(aPageRecord);
         return pushRecord;
       }, err => {
-        if (this._pendingRegisterRequest[aPageRecord.scope]) {
-          delete this._pendingRegisterRequest[aPageRecord.scope];
-        }
+        this._deletePendingRequest(aPageRecord);
         throw err;
      });
   },
 
   _register: function(aPageRecord) {
+    debug("_register()");
+    if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) {
+      return Promise.reject({state: 0, error: "NotFoundError"});
+    }
+
     return this._checkActivated()
-      .then(_ => this._db.getByScope(aPageRecord.scope))
+      .then(_ => this._db.getByIdentifiers(aPageRecord))
       .then(pushRecord => {
         if (pushRecord === undefined) {
-          if (this._pendingRegisterRequest[aPageRecord.scope]) {
-            return this._pendingRegisterRequest[aPageRecord.scope];
-          }
-          return this._pendingRegisterRequest[aPageRecord.scope] = this._registerWithServer(aPageRecord);
+          return this._lookupOrPutPendingRequest(aPageRecord);
         }
         return pushRecord;
       }, error => {
-        debug("getByScope failed");
+        debug("getByIdentifiers failed");
         throw error;
       });
   },
 
   /**
    * Exceptions thrown in _onRegisterSuccess are caught by the promise obtained
    * from _service.request, causing the promise to be rejected instead.
    */
@@ -828,20 +860,49 @@ this.PushService = {
   receiveMessage: function(aMessage) {
     debug("receiveMessage(): " + aMessage.name);
 
     if (kCHILD_PROCESS_MESSAGES.indexOf(aMessage.name) == -1) {
       debug("Invalid message from child " + aMessage.name);
       return;
     }
 
+    if (!aMessage.target.assertPermission("push")) {
+      debug("Got message from a child process that does not have 'push' permission.");
+      return null;
+    }
+
     let mm = aMessage.target.QueryInterface(Ci.nsIMessageSender);
-    let json = aMessage.data;
+    let pageRecord = aMessage.data;
+
+    let principal = aMessage.principal;
+    if (!principal) {
+      debug("No principal passed!");
+      let message = {
+        requestID: aPageRecord.requestID,
+        error: "SecurityError"
+      };
+      mm.sendAsyncMessage("PushService:Register:KO", message);
+      return;
+    }
 
-    this[aMessage.name.slice("Push:".length).toLowerCase()](json, mm);
+    pageRecord.originAttributes =
+      ChromeUtils.originAttributesToSuffix(principal.originAttributes);
+
+    if (!pageRecord.scope || pageRecord.originAttributes === undefined) {
+      debug("Incorrect identifier values set! " + JSON.stringify(pageRecord));
+      let message = {
+        requestID: aPageRecord.requestID,
+        error: "SecurityError"
+      };
+      mm.sendAsyncMessage("PushService:Register:KO", message);
+      return;
+    }
+
+    this[aMessage.name.slice("Push:".length).toLowerCase()](pageRecord, mm);
   },
 
   register: function(aPageRecord, aMessageManager) {
     debug("register(): " + JSON.stringify(aPageRecord));
 
     this._register(aPageRecord)
       .then(pushRecord => {
         let message = this._service.prepareRegister(pushRecord);
@@ -878,23 +939,22 @@ this.PushService = {
    * reason at which point the server will say it does not know of this
    * unregistration.  We'll have to make the registration/unregistration phases
    * have retries and attempts to resend messages from the server, and have the
    * client acknowledge. On a server, data is cheap, reliable notification is
    * not.
    */
   _unregister: function(aPageRecord) {
     debug("_unregister()");
-
-    if (!aPageRecord.scope) {
+    if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) {
       return Promise.reject({state: 0, error: "NotFoundError"});
     }
 
     return this._checkActivated()
-      .then(_ => this._db.getByScope(aPageRecord.scope))
+      .then(_ => this._db.getByIdentifiers(aPageRecord))
       .then(record => {
         // If the endpoint didn't exist, let's just fail.
         if (record === undefined) {
           throw "NotFoundError";
         }
 
         return Promise.all([
           this._sendRequest("unregister", record),
@@ -927,22 +987,22 @@ this.PushService = {
       });
   },
 
   /**
    * Called on message from the child process
    */
   _registration: function(aPageRecord) {
     debug("_registration()");
-    if (!aPageRecord.scope) {
-      return Promise.reject({state: 0, error: "Database error"});
+    if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) {
+      return Promise.reject({state: 0, error: "NotFoundError"});
     }
 
     return this._checkActivated()
-      .then(_ => this._db.getByScope(aPageRecord.scope))
+      .then(_ => this._db.getByIdentifiers(aPageRecord))
       .then(pushRecord => {
         if (!pushRecord) {
           return null;
         }
         return this._service.prepareRegistration(pushRecord);
       });
   },
 
--- a/dom/push/PushServiceHttp2.jsm
+++ b/dom/push/PushServiceHttp2.jsm
@@ -28,17 +28,17 @@ var gDebuggingEnabled = prefs.get("debug
 
 function debug(s) {
   if (gDebuggingEnabled) {
     dump("-*- PushServiceHttp2.jsm: " + s + "\n");
   }
 }
 
 const kPUSHHTTP2DB_DB_NAME = "pushHttp2";
-const kPUSHHTTP2DB_DB_VERSION = 1; // Change this if the IndexedDB format changes
+const kPUSHHTTP2DB_DB_VERSION = 3; // Change this if the IndexedDB format changes
 const kPUSHHTTP2DB_STORE_NAME = "pushHttp2";
 
 /**
  * A proxy between the PushService and connections listening for incoming push
  * messages. The PushService can silence messages from the connections by
  * setting PushSubscriptionListener._pushService to null. This is required
  * because it can happen that there is an outstanding push message that will
  * be send on OnStopRequest but the PushService may not be interested in these.
@@ -286,16 +286,17 @@ SubscriptionListener.prototype = {
     }
 
     var reply = {
       subscriptionUri: subscriptionUri,
       pushEndpoint: linkParserResult.pushEndpoint,
       pushReceiptEndpoint: linkParserResult.pushReceiptEndpoint,
       pageURL: this._subInfo.record.pageURL,
       scope: this._subInfo.record.scope,
+      originAttributes: this._subInfo.record.originAttributes,
       pushCount: 0,
       lastPush: 0
     };
     this._subInfo.resolve(reply);
   },
 };
 
 function retryAfterParser(aRequest) {
@@ -380,25 +381,43 @@ this.PushServiceHttp2 = {
 
   upgradeSchema: function(aTransaction,
                           aDb,
                           aOldVersion,
                           aNewVersion,
                           aDbInstance) {
     debug("upgradeSchemaHttp2()");
 
+    //XXXnsm We haven't shipped Push during this upgrade, so I'm just going to throw old
+    //registrations away without even informing the app.
+    if (aNewVersion != aOldVersion) {
+      try {
+        aDb.deleteObjectStore(aDbInstance._dbStoreName);
+      } catch (e) {
+        if (e.name === "NotFoundError") {
+          debug("No existing object store found");
+        } else {
+          throw e;
+        }
+      }
+    }
+
     let objectStore = aDb.createObjectStore(aDbInstance._dbStoreName,
                                             { keyPath: "subscriptionUri" });
 
     // index to fetch records based on endpoints. used by unregister
     objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true });
 
-    // index to fetch records per scope, so we can identify endpoints
-    // associated with an app.
-    objectStore.createIndex("scope", "scope", { unique: true });
+    // index to fetch records by identifiers.
+    // In the current security model, the originAttributes distinguish between
+    // different 'apps' on the same origin. Since ServiceWorkers are
+    // same-origin to the scope they are registered for, the attributes and
+    // scope are enough to reconstruct a valid principal.
+    objectStore.createIndex("identifiers", ["scope", "originAttributes"], { unique: true });
+    objectStore.createIndex("originAttributes", "originAttributes", { unique: false });
   },
 
   getKeyFromRecord: function(aRecord) {
     return aRecord.subscriptionUri;
   },
 
   newPushDB: function() {
     return new PushDB(kPUSHHTTP2DB_DB_NAME,
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -27,17 +27,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "@mozilla.org/power/powermanagerservice;1",
                                    "nsIPowerManagerService");
 #endif
 
 var threadManager = Cc["@mozilla.org/thread-manager;1"]
                       .getService(Ci.nsIThreadManager);
 
 const kPUSHWSDB_DB_NAME = "pushapi";
-const kPUSHWSDB_DB_VERSION = 1; // Change this if the IndexedDB format changes
+const kPUSHWSDB_DB_VERSION = 3; // Change this if the IndexedDB format changes
 const kPUSHWSDB_STORE_NAME = "pushapi";
 
 const kUDP_WAKEUP_WS_STATUS_CODE = 4774;  // WebSocket Close status code sent
                                           // by server to signal that it can
                                           // wake client up using UDP.
 
 const kWS_MAX_WENTDOWN = 2;
 
@@ -127,25 +127,43 @@ this.PushServiceWebSocket = {
 
   upgradeSchema: function(aTransaction,
                           aDb,
                           aOldVersion,
                           aNewVersion,
                           aDbInstance) {
     debug("upgradeSchemaWS()");
 
+    //XXXnsm We haven't shipped Push during this upgrade, so I'm just going to throw old
+    //registrations away without even informing the app.
+    if (aNewVersion != aOldVersion) {
+      try {
+        aDb.deleteObjectStore(aDbInstance._dbStoreName);
+      } catch (e) {
+        if (e.name === "NotFoundError") {
+          debug("No existing object store found");
+        } else {
+          throw e;
+        }
+      }
+    }
+
     let objectStore = aDb.createObjectStore(aDbInstance._dbStoreName,
                                             { keyPath: "channelID" });
 
     // index to fetch records based on endpoints. used by unregister
     objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true });
 
-    // index to fetch records per scope, so we can identify endpoints
-    // associated with an app.
-    objectStore.createIndex("scope", "scope", { unique: true });
+    // index to fetch records by identifiers.
+    // In the current security model, the originAttributes distinguish between
+    // different 'apps' on the same origin. Since ServiceWorkers are
+    // same-origin to the scope they are registered for, the attributes and
+    // scope are enough to reconstruct a valid principal.
+    objectStore.createIndex("identifiers", ["scope", "originAttributes"], { unique: true });
+    objectStore.createIndex("originAttributes", "originAttributes", { unique: false });
   },
 
   getKeyFromRecord: function(aRecord) {
     return aRecord.channelID;
   },
 
   newPushDB: function() {
     return new PushDB(kPUSHWSDB_DB_NAME,
@@ -865,20 +883,22 @@ this.PushServiceWebSocket = {
         return;
       }
 
       let record = {
         channelID: reply.channelID,
         pushEndpoint: reply.pushEndpoint,
         pageURL: tmp.record.pageURL,
         scope: tmp.record.scope,
+        originAttributes: tmp.record.originAttributes,
         pushCount: 0,
         lastPush: 0,
         version: null
       };
+      dump("PushWebSocket " +  JSON.stringify(record));
       tmp.resolve(record);
     } else {
       tmp.reject(reply);
     }
   },
 
   /**
    * Protocol handler invoked by server message.
--- a/dom/push/test/mochitest.ini
+++ b/dom/push/test/mochitest.ini
@@ -10,12 +10,14 @@ skip-if = os == "android" || toolkit == 
 [test_permissions.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_register.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register_during_service_activation.html]
 skip-if = os == "android" || toolkit == "gonk"
+[test_unregister.html]
+skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register_different_scope.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_try_registering_offline_disabled.html]
 skip-if = os == "android" || toolkit == "gonk"
--- a/dom/push/test/test_multiple_register.html
+++ b/dom/push/test/test_multiple_register.html
@@ -115,16 +115,17 @@ http://creativecommons.org/licenses/publ
     .then(unregister)
     .catch(function(e) {
       ok(false, "Some test failed with error " + e);
     }).then(SimpleTest.finish);
   }
 
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.push.enabled", true],
+    ["dom.push.debug", true],
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
     ]}, runTest);
   SpecialPowers.addPermission('push', true, document);
   SimpleTest.waitForExplicitFinish();
 </script>
 </body>
new file mode 100644
--- /dev/null
+++ b/dom/push/test/test_unregister.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1170817: Push tests.
+
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/licenses/publicdomain/
+
+-->
+<head>
+  <title>Test for Bug 1170817</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+</head>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1170817">Mozilla Bug 1170817</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="text/javascript">
+
+  var registration;
+
+  function start() {
+    return navigator.serviceWorker.register("worker.js" + "?" + (Math.random()), {scope: "."})
+    .then(swr => { registration = swr; return swr; });
+  }
+
+  function unregisterSW() {
+    return registration.unregister().then(function(result) {
+      ok(result, "Unregister should return true.");
+    }, function(e) {
+      dump("Unregistering the SW failed with " + e + "\n");
+    });
+  }
+
+  function setupPushNotification(swr) {
+    return swr.pushManager.subscribe().then(
+      pushSubscription => {
+        ok(true, "successful registered for push notification");
+        return pushSubscription;
+      }, error => {
+        ok(false, "could not register for push notification");
+      });
+  }
+
+  function unregisterPushNotification(pushSubscription) {
+    return pushSubscription.unsubscribe().then(
+      result => {
+      ok(result, "unsubscribe() on existing subscription should return true.");
+      return pushSubscription;
+    }, error => {
+      ok(false, "unsubscribe() should never fail.");
+    });
+  }
+
+  function unregisterAgain(pushSubscription) {
+    return pushSubscription.unsubscribe().then(
+      result => {
+      ok(!result, "unsubscribe() on previously unsubscribed subscription should return false.");
+      return pushSubscription;
+    }, error => {
+      ok(false, "unsubscribe() should never fail.");
+    });
+  }
+
+  function runTest() {
+    start()
+    .then(setupPushNotification)
+    .then(unregisterPushNotification)
+    .then(unregisterAgain)
+    .then(unregisterSW)
+    .catch(function(e) {
+      ok(false, "Some test failed with error " + e);
+    }).then(SimpleTest.finish);
+  }
+
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.push.enabled", true],
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+    ]}, runTest);
+  SpecialPowers.addPermission('push', true, document);
+  SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
+
--- a/dom/push/test/xpcshell/test_notification_ack.js
+++ b/dom/push/test/xpcshell/test_notification_ack.js
@@ -20,26 +20,29 @@ function run_test() {
 
 add_task(function* test_notification_ack() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
   let records = [{
     channelID: '21668e05-6da8-42c9-b8ab-9cc3f4d5630c',
     pushEndpoint: 'https://example.com/update/1',
     scope: 'https://example.org/1',
+    originAttributes: '',
     version: 1
   }, {
     channelID: '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305',
     pushEndpoint: 'https://example.com/update/2',
     scope: 'https://example.org/2',
+    originAttributes: '',
     version: 2
   }, {
     channelID: '5477bfda-22db-45d4-9614-fee369630260',
     pushEndpoint: 'https://example.com/update/3',
     scope: 'https://example.org/3',
+    originAttributes: '',
     version: 3
   }];
   for (let record of records) {
     yield db.put(record);
   }
 
   let notifyPromise = Promise.all([
     promiseObserverNotification('push-notification'),
--- a/dom/push/test/xpcshell/test_notification_duplicate.js
+++ b/dom/push/test/xpcshell/test_notification_duplicate.js
@@ -18,21 +18,23 @@ function run_test() {
 // Should acknowledge duplicate notifications, but not notify apps.
 add_task(function* test_notification_duplicate() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
   let records = [{
     channelID: '8d2d9400-3597-4c5a-8a38-c546b0043bcc',
     pushEndpoint: 'https://example.org/update/1',
     scope: 'https://example.com/1',
+    originAttributes: "",
     version: 2
   }, {
     channelID: '27d1e393-03ef-4c72-a5e6-9e890dfccad0',
     pushEndpoint: 'https://example.org/update/2',
     scope: 'https://example.com/2',
+    originAttributes: "",
     version: 2
   }];
   for (let record of records) {
     yield db.put(record);
   }
 
   let notifyPromise = promiseObserverNotification('push-notification');
 
--- a/dom/push/test/xpcshell/test_notification_error.js
+++ b/dom/push/test/xpcshell/test_notification_error.js
@@ -14,30 +14,35 @@ function run_test() {
     'https://example.com/c'
   );
   run_next_test();
 }
 
 add_task(function* test_notification_error() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
+
+  let originAttributes = '';
   let records = [{
     channelID: 'f04f1e46-9139-4826-b2d1-9411b0821283',
     pushEndpoint: 'https://example.org/update/success-1',
     scope: 'https://example.com/a',
+    originAttributes: originAttributes,
     version: 1
   }, {
     channelID: '3c3930ba-44de-40dc-a7ca-8a133ec1a866',
     pushEndpoint: 'https://example.org/update/error',
     scope: 'https://example.com/b',
+    originAttributes: originAttributes,
     version: 2
   }, {
     channelID: 'b63f7bef-0a0d-4236-b41e-086a69dfd316',
     pushEndpoint: 'https://example.org/update/success-2',
     scope: 'https://example.com/c',
+    originAttributes: originAttributes,
     version: 3
   }];
   for (let record of records) {
     yield db.put(record);
   }
 
   let notifyPromise = Promise.all([
     promiseObserverNotification(
@@ -102,26 +107,29 @@ add_task(function* test_notification_err
   let cPush = c.subject.QueryInterface(Ci.nsIPushObserverNotification);
   equal(cPush.pushEndpoint, 'https://example.org/update/success-2',
     'Wrong endpoint for notification C');
   equal(cPush.version, 4, 'Wrong version for notification C');
 
   yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
     'Timed out waiting for acknowledgements');
 
-  let aRecord = yield db.getByScope('https://example.com/a');
+  let aRecord = yield db.getByIdentifiers({scope: 'https://example.com/a',
+                                           originAttributes: originAttributes });
   equal(aRecord.channelID, 'f04f1e46-9139-4826-b2d1-9411b0821283',
     'Wrong channel ID for record A');
   strictEqual(aRecord.version, 2,
     'Should return the new version for record A');
 
-  let bRecord = yield db.getByScope('https://example.com/b');
+  let bRecord = yield db.getByIdentifiers({scope: 'https://example.com/b',
+                                           originAttributes: originAttributes });
   equal(bRecord.channelID, '3c3930ba-44de-40dc-a7ca-8a133ec1a866',
     'Wrong channel ID for record B');
   strictEqual(bRecord.version, 2,
     'Should return the previous version for record B');
 
-  let cRecord = yield db.getByScope('https://example.com/c');
+  let cRecord = yield db.getByIdentifiers({scope: 'https://example.com/c',
+                                           originAttributes: originAttributes });
   equal(cRecord.channelID, 'b63f7bef-0a0d-4236-b41e-086a69dfd316',
     'Wrong channel ID for record C');
   strictEqual(cRecord.version, 4,
     'Should return the new version for record C');
 });
--- a/dom/push/test/xpcshell/test_notification_http2.js
+++ b/dom/push/test/xpcshell/test_notification_http2.js
@@ -52,27 +52,30 @@ add_task(function* test_pushNotification
   });
 
   var serverURL = "https://localhost:" + serverPort;
 
   let records = [{
     subscriptionUri: serverURL + '/pushNotifications/subscription1',
     pushEndpoint: serverURL + '/pushEndpoint1',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint1',
-    scope: 'https://example.com/page/1'
+    scope: 'https://example.com/page/1',
+    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
   }, {
     subscriptionUri: serverURL + '/pushNotifications/subscription2',
     pushEndpoint: serverURL + '/pushEndpoint2',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint2',
-    scope: 'https://example.com/page/2'
+    scope: 'https://example.com/page/2',
+    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
   }, {
     subscriptionUri: serverURL + '/pushNotifications/subscription3',
     pushEndpoint: serverURL + '/pushEndpoint3',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint3',
-    scope: 'https://example.com/page/3'
+    scope: 'https://example.com/page/3',
+    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
   }];
 
   for (let record of records) {
     yield db.put(record);
   }
 
   let notifyPromise = Promise.all([
     promiseObserverNotification('push-notification'),
--- a/dom/push/test/xpcshell/test_notification_version_string.js
+++ b/dom/push/test/xpcshell/test_notification_version_string.js
@@ -16,16 +16,17 @@ function run_test() {
 
 add_task(function* test_notification_version_string() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
   yield db.put({
     channelID: '6ff97d56-d0c0-43bc-8f5b-61b855e1d93b',
     pushEndpoint: 'https://example.org/updates/1',
     scope: 'https://example.com/page/1',
+    originAttributes: '',
     version: 2
   });
 
   let notifyPromise = promiseObserverNotification('push-notification');
 
   let ackDefer = Promise.defer();
   PushService.init({
     serverURI: "wss://push.example.org/",
--- a/dom/push/test/xpcshell/test_register_5xxCode_http2.js
+++ b/dom/push/test/xpcshell/test_register_5xxCode_http2.js
@@ -79,17 +79,18 @@ add_task(function* test1() {
 
   PushService.init({
     serverURI: serverURL + "/subscribe5xxCode",
     service: PushServiceHttp2,
     db
   });
 
   let newRecord = yield PushNotificationService.register(
-    'https://example.com/retry5xxCode'
+    'https://example.com/retry5xxCode',
+    { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }
   );
 
   var subscriptionUri = serverURL + '/subscription';
   var pushEndpoint = serverURL + '/pushEndpoint';
   var pushReceiptEndpoint = serverURL + '/receiptPushEndpoint';
   equal(newRecord.subscriptionUri, subscriptionUri,
     'Wrong subscription ID in registration record');
   equal(newRecord.pushEndpoint, pushEndpoint,
--- a/dom/push/test/xpcshell/test_register_case.js
+++ b/dom/push/test/xpcshell/test_register_case.js
@@ -42,17 +42,18 @@ add_task(function* test_register_case() 
             pushEndpoint: 'https://example.com/update/case'
           }));
         }
       });
     }
   });
 
   let newRecord = yield waitForPromise(
-    PushNotificationService.register('https://example.net/case'),
+    PushNotificationService.register('https://example.net/case',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     DEFAULT_TIMEOUT,
     'Mixed-case register response timed out'
   );
   equal(newRecord.pushEndpoint, 'https://example.com/update/case',
     'Wrong push endpoint in registration record');
   equal(newRecord.scope, 'https://example.net/case',
     'Wrong scope in registration record');
 
--- a/dom/push/test/xpcshell/test_register_error_http2.js
+++ b/dom/push/test/xpcshell/test_register_error_http2.js
@@ -42,17 +42,18 @@ add_task(function* test_pushSubscription
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionNoConnection/subscribe",
     db
   });
 
   yield rejects(
     PushNotificationService.register(
-      'https://example.net/page/invalid-response'),
+      'https://example.net/page/invalid-response',
+      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
     function(error) {
       return error && error.includes("Error");
     },
     'Wrong error for not being able to establish connecion.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, "Should not store records when connection couldn't be established.");
@@ -82,17 +83,18 @@ add_task(function* test_pushSubscription
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionMissingLocation/subscribe",
     db
   });
 
   yield rejects(
     PushNotificationService.register(
-      'https://example.net/page/invalid-response'),
+      'https://example.net/page/invalid-response',
+      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
     function(error) {
       return error && error.includes("Return code 201, but the answer is bogus");
     },
     'Wrong error for the missing location header.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when the location header is missing.');
@@ -108,17 +110,18 @@ add_task(function* test_pushSubscription
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionMissingLink/subscribe",
     db
   });
 
   yield rejects(
     PushNotificationService.register(
-      'https://example.net/page/invalid-response'),
+      'https://example.net/page/invalid-response',
+      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
     function(error) {
       return error && error.includes("Return code 201, but the answer is bogus");
     },
     'Wrong error for the missing link header.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when a link header is missing.');
@@ -134,17 +137,18 @@ add_task(function* test_pushSubscription
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionMissingLink1/subscribe",
     db
   });
 
   yield rejects(
     PushNotificationService.register(
-      'https://example.net/page/invalid-response'),
+      'https://example.net/page/invalid-response',
+      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
     function(error) {
       return error && error.includes("Return code 201, but the answer is bogus");
     },
     'Wrong error for the missing push endpoint.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when the push endpoint is missing.');
@@ -160,17 +164,18 @@ add_task(function* test_pushSubscription
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionLocationBogus/subscribe",
     db
   });
 
   yield rejects(
     PushNotificationService.register(
-      'https://example.net/page/invalid-response'),
+      'https://example.net/page/invalid-response',
+      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
     function(error) {
       return error && error.includes("Return code 201, but URI is bogus.");
     },
     'Wrong error for the bogus location'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when location header is bogus.');
@@ -186,17 +191,18 @@ add_task(function* test_pushSubscription
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionNot201Code/subscribe",
     db
   });
 
   yield rejects(
     PushNotificationService.register(
-      'https://example.net/page/invalid-response'),
+      'https://example.net/page/invalid-response',
+      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
     function(error) {
       return error && error.includes("Error");
     },
     'Wrong error for not 201 responce code.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when respons code is not 201.');
--- a/dom/push/test/xpcshell/test_register_flush.js
+++ b/dom/push/test/xpcshell/test_register_flush.js
@@ -24,16 +24,17 @@ function run_test() {
 
 add_task(function* test_register_flush() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
   let record = {
     channelID: '9bcc7efb-86c7-4457-93ea-e24e6eb59b74',
     pushEndpoint: 'https://example.org/update/1',
     scope: 'https://example.com/page/1',
+    originAttributes: '',
     version: 2
   };
   yield db.put(record);
 
   let notifyPromise = promiseObserverNotification('push-notification');
 
   let ackDefer = Promise.defer();
   let ackDone = after(2, ackDefer.resolve);
@@ -70,18 +71,17 @@ add_task(function* test_register_flush()
           }));
         },
         onACK: ackDone
       });
     }
   });
 
   let newRecord = yield PushNotificationService.register(
-    'https://example.com/page/2'
-  );
+    'https://example.com/page/2', '');
   equal(newRecord.pushEndpoint, 'https://example.org/update/2',
     'Wrong push endpoint in record');
   equal(newRecord.scope, 'https://example.com/page/2',
     'Wrong scope in record');
 
   let {data: scope} = yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
     'Timed out waiting for notification');
   equal(scope, 'https://example.com/page/1', 'Wrong notification scope');
--- a/dom/push/test/xpcshell/test_register_invalid_channel.js
+++ b/dom/push/test/xpcshell/test_register_invalid_channel.js
@@ -43,17 +43,18 @@ add_task(function* test_register_invalid
             error: 'Invalid channel ID'
           }));
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.com/invalid-channel'),
+    PushNotificationService.register('https://example.com/invalid-channel',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     function(error) {
       return error == 'Invalid channel ID';
     },
     'Wrong error for invalid channel ID'
   );
 
   let record = yield db.getByKeyID(channelID);
   ok(!record, 'Should not store records for error responses');
--- a/dom/push/test/xpcshell/test_register_invalid_endpoint.js
+++ b/dom/push/test/xpcshell/test_register_invalid_endpoint.js
@@ -45,17 +45,18 @@ add_task(function* test_register_invalid
           }));
         }
       });
     }
   });
 
   yield rejects(
     PushNotificationService.register(
-      'https://example.net/page/invalid-endpoint'),
+      'https://example.net/page/invalid-endpoint',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     function(error) {
       return error && error.includes('Invalid pushEndpoint');
     },
     'Wrong error for invalid endpoint'
   );
 
   let record = yield db.getByKeyID(channelID);
   ok(!record, 'Should not store records with invalid endpoints');
--- a/dom/push/test/xpcshell/test_register_invalid_json.js
+++ b/dom/push/test/xpcshell/test_register_invalid_json.js
@@ -44,17 +44,18 @@ add_task(function* test_register_invalid
           this.serverSendMsg(');alert(1);(');
           registers++;
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.net/page/invalid-json'),
+    PushNotificationService.register('https://example.net/page/invalid-json',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     function(error) {
       return error == 'TimeoutError';
     },
     'Wrong error for invalid JSON response'
   );
 
   yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
     'Reconnect after invalid JSON response timed out');
--- a/dom/push/test/xpcshell/test_register_no_id.js
+++ b/dom/push/test/xpcshell/test_register_no_id.js
@@ -48,17 +48,18 @@ add_task(function* test_register_no_id()
             status: 200
           }));
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.com/incomplete'),
+    PushNotificationService.register('https://example.com/incomplete',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     function(error) {
       return error == 'TimeoutError';
     },
     'Wrong error for incomplete register response'
   );
 
   yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
     'Reconnect after incomplete register response timed out');
--- a/dom/push/test/xpcshell/test_register_request_queue.js
+++ b/dom/push/test/xpcshell/test_register_request_queue.js
@@ -40,20 +40,22 @@ add_task(function* test_register_request
         onRegister() {
           ok(false, 'Should cancel timed-out requests');
         }
       });
     }
   });
 
   let firstRegister = PushNotificationService.register(
-    'https://example.com/page/1'
+    'https://example.com/page/1',
+    { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }
   );
   let secondRegister = PushNotificationService.register(
-    'https://example.com/page/1'
+    'https://example.com/page/1',
+    { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }
   );
 
   yield waitForPromise(Promise.all([
     rejects(firstRegister, function(error) {
       return error == 'TimeoutError';
     }, 'Should time out the first request'),
     rejects(secondRegister, function(error) {
       return error == 'TimeoutError';
--- a/dom/push/test/xpcshell/test_register_rollback.js
+++ b/dom/push/test/xpcshell/test_register_rollback.js
@@ -69,17 +69,18 @@ add_task(function* test_register_rollbac
           unregisterDefer.resolve();
         }
       });
     }
   });
 
   // Should return a rejected promise if storage fails.
   yield rejects(
-    PushNotificationService.register('https://example.com/storage-error'),
+    PushNotificationService.register('https://example.com/storage-error',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     function(error) {
       return error == 'universe has imploded';
     },
     'Wrong error for unregister database failure'
   );
 
   // Should send an out-of-band unregister request.
   yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
--- a/dom/push/test/xpcshell/test_register_success.js
+++ b/dom/push/test/xpcshell/test_register_success.js
@@ -52,17 +52,18 @@ add_task(function* test_register_success
             pushEndpoint: 'https://example.com/update/1',
           }));
         }
       });
     }
   });
 
   let newRecord = yield PushNotificationService.register(
-    'https://example.org/1'
+    'https://example.org/1',
+    { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }
   );
   equal(newRecord.channelID, channelID,
     'Wrong channel ID in registration record');
   equal(newRecord.pushEndpoint, 'https://example.com/update/1',
     'Wrong push endpoint in registration record');
   equal(newRecord.scope, 'https://example.org/1',
     'Wrong scope in registration record');
 
--- a/dom/push/test/xpcshell/test_register_success_http2.js
+++ b/dom/push/test/xpcshell/test_register_success_http2.js
@@ -52,17 +52,18 @@ add_task(function* test_pushSubscription
   });
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionSuccess/subscribe",
     db
   });
 
   let newRecord = yield PushNotificationService.register(
-    'https://example.org/1'
+    'https://example.org/1',
+    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
   );
 
   var subscriptionUri = serverURL + '/pushSubscriptionSuccesss';
   var pushEndpoint = serverURL + '/pushEndpointSuccess';
   var pushReceiptEndpoint = serverURL + '/receiptPushEndpointSuccess';
   equal(newRecord.subscriptionUri, subscriptionUri,
     'Wrong subscription ID in registration record');
   equal(newRecord.pushEndpoint, pushEndpoint,
@@ -94,17 +95,18 @@ add_task(function* test_pushSubscription
   });
 
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionMissingLink2/subscribe",
     db
   });
 
   let newRecord = yield PushNotificationService.register(
-    'https://example.org/no_receiptEndpoint'
+    'https://example.org/no_receiptEndpoint',
+    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
   );
 
   var subscriptionUri = serverURL + '/subscriptionMissingLink2';
   var pushEndpoint = serverURL + '/pushEndpointMissingLink2';
   var pushReceiptEndpoint = '';
   equal(newRecord.subscriptionUri, subscriptionUri,
     'Wrong subscription ID in registration record');
   equal(newRecord.pushEndpoint, pushEndpoint,
--- a/dom/push/test/xpcshell/test_register_timeout.js
+++ b/dom/push/test/xpcshell/test_register_timeout.js
@@ -78,17 +78,18 @@ add_task(function* test_register_timeout
           }, 2000);
           registers++;
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.net/page/timeout'),
+    PushNotificationService.register('https://example.net/page/timeout',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     function(error) {
       return error == 'TimeoutError';
     },
     'Wrong error for request timeout'
   );
 
   let record = yield db.getByKeyID(channelID);
   ok(!record, 'Should not store records for timed-out responses');
--- a/dom/push/test/xpcshell/test_register_wrong_id.js
+++ b/dom/push/test/xpcshell/test_register_wrong_id.js
@@ -54,17 +54,18 @@ add_task(function* test_register_wrong_i
             channelID: serverChannelID
           }));
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.register('https://example.com/mismatched'),
+    PushNotificationService.register('https://example.com/mismatched',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     function(error) {
       return error == 'TimeoutError';
     },
     'Wrong error for mismatched register reply'
   );
 
   yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
     'Reconnect after mismatched register reply timed out');
--- a/dom/push/test/xpcshell/test_register_wrong_type.js
+++ b/dom/push/test/xpcshell/test_register_wrong_type.js
@@ -50,17 +50,18 @@ add_task(function* test_register_wrong_t
         }
       });
     }
   });
 
   let promise =
 
   yield rejects(
-    PushNotificationService.register('https://example.com/mistyped'),
+    PushNotificationService.register('https://example.com/mistyped',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     function(error) {
       return error == 'TimeoutError';
     },
     'Wrong error for non-string channel ID'
   );
 
   yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
     'Reconnect after sending non-string channel ID timed out');
--- a/dom/push/test/xpcshell/test_registration_error.js
+++ b/dom/push/test/xpcshell/test_registration_error.js
@@ -16,25 +16,26 @@ function run_test() {
 add_task(function* test_registrations_error() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
 
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     db: makeStub(db, {
-      getByScope(prev, scope) {
+      getByIdentifiers(prev, scope) {
         return Promise.reject('Database error');
       }
     }),
     makeWebSocket(uri) {
       return new MockWebSocket(uri);
     }
   });
 
   yield rejects(
-    PushNotificationService.registration('https://example.net/1'),
+    PushNotificationService.registration('https://example.net/1',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     function(error) {
       return error == 'Database error';
     },
     'Wrong message'
   );
 });
--- a/dom/push/test/xpcshell/test_registration_error_http2.js
+++ b/dom/push/test/xpcshell/test_registration_error_http2.js
@@ -13,22 +13,23 @@ function run_test() {
 add_task(function* test_registrations_error() {
   let db = PushServiceHttp2.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
 
   PushService.init({
     serverURI: "https://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     db: makeStub(db, {
-      getByScope(prev, scope) {
+      getByIdentifiers() {
         return Promise.reject('Database error');
       }
     }),
   });
 
   yield rejects(
-    PushNotificationService.registration('https://example.net/1'),
+    PushNotificationService.registration('https://example.net/1',
+      ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
     function(error) {
       return error == 'Database error';
     },
     'Wrong message'
   );
 });
--- a/dom/push/test/xpcshell/test_registration_missing_scope.js
+++ b/dom/push/test/xpcshell/test_registration_missing_scope.js
@@ -15,15 +15,15 @@ add_task(function* test_registration_mis
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     makeWebSocket(uri) {
       return new MockWebSocket(uri);
     }
   });
   yield rejects(
-    PushNotificationService.registration(''),
+    PushNotificationService.registration('', ''),
     function(error) {
-      return error.error == 'Database error';
+      return error.error == 'NotFoundError';
     },
     'Record missing page and manifest URLs'
   );
 });
--- a/dom/push/test/xpcshell/test_registration_none.js
+++ b/dom/push/test/xpcshell/test_registration_none.js
@@ -19,11 +19,12 @@ add_task(function* test_registration_non
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     makeWebSocket(uri) {
       return new MockWebSocket(uri);
     }
   });
 
   let registration = yield PushNotificationService.registration(
-    'https://example.net/1');
+    'https://example.net/1',
+    { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false });
   ok(!registration, 'Should not open a connection without registration');
 });
--- a/dom/push/test/xpcshell/test_registration_success.js
+++ b/dom/push/test/xpcshell/test_registration_success.js
@@ -15,26 +15,29 @@ function run_test() {
 
 add_task(function* test_registration_success() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
   let records = [{
     channelID: 'bf001fe0-2684-42f2-bc4d-a3e14b11dd5b',
     pushEndpoint: 'https://example.com/update/same-manifest/1',
     scope: 'https://example.net/a',
+    originAttributes: '',
     version: 5
   }, {
     channelID: 'f6edfbcd-79d6-49b8-9766-48b9dcfeff0f',
     pushEndpoint: 'https://example.com/update/same-manifest/2',
     scope: 'https://example.net/b',
+    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: 42 }),
     version: 10
   }, {
     channelID: 'b1cf38c9-6836-4d29-8a30-a3e98d59b728',
     pushEndpoint: 'https://example.org/update/different-manifest',
     scope: 'https://example.org/c',
+    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: 42, inBrowser: true }),
     version: 15
   }];
   for (let record of records) {
     yield db.put(record);
   }
 
   PushService.init({
     serverURI: "wss://push.example.org/",
@@ -54,16 +57,16 @@ add_task(function* test_registration_suc
             uaid: userAgentID
           }));
         }
       });
     }
   });
 
   let registration = yield PushNotificationService.registration(
-    'https://example.net/a');
+    'https://example.net/a', '');
   equal(
     registration.pushEndpoint,
     'https://example.com/update/same-manifest/1',
     'Wrong push endpoint for scope'
   );
   equal(registration.version, 5, 'Wrong version for scope');
 });
--- a/dom/push/test/xpcshell/test_registration_success_http2.js
+++ b/dom/push/test/xpcshell/test_registration_success_http2.js
@@ -36,38 +36,42 @@ add_task(function* test_pushNotification
   });
 
   var serverURL = "https://localhost:" + serverPort;
 
   let records = [{
     subscriptionUri: serverURL + '/subscriptionA',
     pushEndpoint: serverURL + '/pushEndpointA',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpointA',
-    scope: 'https://example.net/a'
+    scope: 'https://example.net/a',
+    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
   }, {
     subscriptionUri: serverURL + '/subscriptionB',
     pushEndpoint: serverURL + '/pushEndpointB',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpointB',
-    scope: 'https://example.net/b'
+    scope: 'https://example.net/b',
+    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
   }, {
     subscriptionUri: serverURL + '/subscriptionC',
     pushEndpoint: serverURL + '/pushEndpointC',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpointC',
-    scope: 'https://example.net/c'
+    scope: 'https://example.net/c',
+    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
   }];
 
   for (let record of records) {
     yield db.put(record);
   }
 
   PushService.init({
     serverURI: serverURL,
     db
   });
 
   let registration = yield PushNotificationService.registration(
-    'https://example.net/a');
+    'https://example.net/a',
+    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }));
   equal(
     registration.pushEndpoint,
     serverURL + '/pushEndpointA',
     'Wrong push endpoint for scope'
   );
 });
--- a/dom/push/test/xpcshell/test_unregister_empty_scope.js
+++ b/dom/push/test/xpcshell/test_unregister_empty_scope.js
@@ -24,15 +24,16 @@ add_task(function* test_unregister_empty
             uaid: '5619557c-86fe-4711-8078-d1fd6987aef7'
           }));
         }
       });
     }
   });
 
   yield rejects(
-    PushNotificationService.unregister(''),
+    PushNotificationService.unregister('',
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
     function(error) {
       return error.error == 'NotFoundError';
     },
     'Wrong error for empty endpoint'
   );
 });
--- a/dom/push/test/xpcshell/test_unregister_error.js
+++ b/dom/push/test/xpcshell/test_unregister_error.js
@@ -15,16 +15,17 @@ function run_test() {
 
 add_task(function* test_unregister_error() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
   yield db.put({
     channelID: channelID,
     pushEndpoint: 'https://example.org/update/failure',
     scope: 'https://example.net/page/failure',
+    originAttributes: '',
     version: 1
   });
 
   let unregisterDefer = Promise.defer();
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     db,
@@ -49,17 +50,17 @@ add_task(function* test_unregister_error
           }));
           unregisterDefer.resolve();
         }
       });
     }
   });
 
   yield PushNotificationService.unregister(
-    'https://example.net/page/failure');
+    'https://example.net/page/failure', '');
 
   let result = yield db.getByKeyID(channelID);
   ok(!result, 'Deleted push record exists');
 
   // Make sure we send a request to the server.
   yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
     'Timed out waiting for unregister');
 });
--- a/dom/push/test/xpcshell/test_unregister_invalid_json.js
+++ b/dom/push/test/xpcshell/test_unregister_invalid_json.js
@@ -19,21 +19,23 @@ function run_test() {
 
 add_task(function* test_unregister_invalid_json() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
   let records = [{
     channelID: '87902e90-c57e-4d18-8354-013f4a556559',
     pushEndpoint: 'https://example.org/update/1',
     scope: 'https://example.edu/page/1',
+    originAttributes: '',
     version: 1
   }, {
     channelID: '057caa8f-9b99-47ff-891c-adad18ce603e',
     pushEndpoint: 'https://example.com/update/2',
     scope: 'https://example.net/page/1',
+    originAttributes: '',
     version: 1
   }];
   for (let record of records) {
     yield db.put(record);
   }
 
   let unregisterDefer = Promise.defer();
   let unregisterDone = after(2, unregisterDefer.resolve);
@@ -56,23 +58,23 @@ add_task(function* test_unregister_inval
         }
       });
     }
   });
 
   // "unregister" is fire-and-forget: it's sent via _send(), not
   // _sendRequest().
   yield PushNotificationService.unregister(
-    'https://example.edu/page/1');
+    'https://example.edu/page/1', '');
   let record = yield db.getByKeyID(
     '87902e90-c57e-4d18-8354-013f4a556559');
   ok(!record, 'Failed to delete unregistered record');
 
   yield PushNotificationService.unregister(
-    'https://example.net/page/1');
+    'https://example.net/page/1', '');
   record = yield db.getByKeyID(
     '057caa8f-9b99-47ff-891c-adad18ce603e');
   ok(!record,
     'Failed to delete unregistered record after receiving invalid JSON');
 
   yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
     'Timed out waiting for unregister');
 });
--- a/dom/push/test/xpcshell/test_unregister_not_found.js
+++ b/dom/push/test/xpcshell/test_unregister_not_found.js
@@ -24,13 +24,14 @@ add_task(function* test_unregister_not_f
             uaid: 'f074ed80-d479-44fa-ba65-792104a79ea9'
           }));
         }
       });
     }
   });
 
   let promise = PushNotificationService.unregister(
-    'https://example.net/nonexistent');
+    'https://example.net/nonexistent',
+    { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false });
   yield rejects(promise, function(error) {
     return error == 'NotFoundError';
   }, 'Wrong error for nonexistent scope');
 });
--- a/dom/push/test/xpcshell/test_unregister_success.js
+++ b/dom/push/test/xpcshell/test_unregister_success.js
@@ -15,16 +15,17 @@ function run_test() {
 
 add_task(function* test_unregister_success() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
   yield db.put({
     channelID,
     pushEndpoint: 'https://example.org/update/unregister-success',
     scope: 'https://example.com/page/unregister-success',
+    originAttributes: '',
     version: 1
   });
 
   let unregisterDefer = Promise.defer();
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     db,
@@ -46,15 +47,15 @@ add_task(function* test_unregister_succe
           }));
           unregisterDefer.resolve();
         }
       });
     }
   });
 
   yield PushNotificationService.unregister(
-    'https://example.com/page/unregister-success');
+    'https://example.com/page/unregister-success', '');
   let record = yield db.getByKeyID(channelID);
   ok(!record, 'Unregister did not remove record');
 
   yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
     'Timed out waiting for unregister');
 });
--- a/dom/push/test/xpcshell/test_unregister_success_http2.js
+++ b/dom/push/test/xpcshell/test_unregister_success_http2.js
@@ -48,26 +48,28 @@ add_task(function* test_pushUnsubscripti
   });
 
   var serverURL = "https://localhost:" + serverPort;
 
   yield db.put({
     subscriptionUri: serverURL + '/subscriptionUnsubscriptionSuccess',
     pushEndpoint: serverURL + '/pushEndpointUnsubscriptionSuccess',
     pushReceiptEndpoint: serverURL + '/receiptPushEndpointUnsubscriptionSuccess',
-    scope: 'https://example.com/page/unregister-success'
+    scope: 'https://example.com/page/unregister-success',
+    originAttributes: ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
   });
 
   PushService.init({
     serverURI: serverURL,
     db
   });
 
   yield PushNotificationService.unregister(
-    'https://example.com/page/unregister-success');
+    'https://example.com/page/unregister-success',
+    ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }));
   let record = yield db.getByKeyID(serverURL + '/subscriptionUnsubscriptionSuccess');
   ok(!record, 'Unregister did not remove record');
 
 });
 
 add_task(function* test_complete() {
   prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlsProfile);
 });
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_webapps_cleardata.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
+
+const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f';
+const channelIDs = ['0ef2ad4a-6c49-41ad-af6e-95d2425276bf', '4818b54a-97c5-4277-ad5d-0bfe630e4e50'];
+var channelIDCounter = 0;
+
+function run_test() {
+  do_get_profile();
+  setPrefs({
+    userAgentID,
+    requestTimeout: 1000,
+    retryBaseInterval: 150
+  });
+  disableServiceWorkerEvents(
+    'https://example.org/1'
+  );
+  run_next_test();
+}
+
+add_task(function* test_webapps_cleardata() {
+  let db = PushServiceWebSocket.newPushDB();
+  do_register_cleanup(() => {return db.drop().then(_ => db.close());});
+
+  PushService._generateID = () => channelID;
+  PushService.init({
+    serverURI: "wss://push.example.org",
+    networkInfo: new MockDesktopNetworkInfo(),
+    db,
+    makeWebSocket(uri) {
+      return new MockWebSocket(uri, {
+        onHello(data) {
+          equal(data.messageType, 'hello', 'Handshake: wrong message type');
+          equal(data.uaid, userAgentID, 'Handshake: wrong device ID');
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'hello',
+            status: 200,
+            uaid: userAgentID
+          }));
+        },
+        onRegister(data) {
+          equal(data.messageType, 'register', 'Register: wrong message type');
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'register',
+            status: 200,
+            channelID: data.channelID,
+            uaid: userAgentID,
+            pushEndpoint: 'https://example.com/update/' + Math.random(),
+          }));
+        }
+      });
+    }
+  });
+
+  let registers = yield Promise.all([
+    PushNotificationService.register(
+      'https://example.org/1',
+      ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: false })),
+    PushNotificationService.register(
+      'https://example.org/1',
+      ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: true })),
+  ]);
+
+  Services.obs.notifyObservers(
+      { appId: 1, browserOnly: false,
+        QueryInterface: XPCOMUtils.generateQI([Ci.mozIApplicationClearPrivateDataParams])},
+      "webapps-clear-data", "");
+
+  let waitAWhile = new Promise(function(res) {
+    setTimeout(res, 2000);
+  });
+  yield waitAWhile;
+
+  let registration;
+  registration = yield PushNotificationService.registration(
+    'https://example.org/1',
+    ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: false }));
+  ok(!registration, 'Registration for { 1, false } should not exist.');
+
+  registration = yield PushNotificationService.registration(
+    'https://example.org/1',
+    ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: true }));
+  ok(registration, 'Registration for { 1, true } should still exist.');
+});
+
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -25,16 +25,17 @@ skip-if = toolkit == 'android'
 [test_registration_missing_scope.js]
 [test_registration_none.js]
 [test_registration_success.js]
 [test_unregister_empty_scope.js]
 [test_unregister_error.js]
 [test_unregister_invalid_json.js]
 [test_unregister_not_found.js]
 [test_unregister_success.js]
+[test_webapps_cleardata.js]
 #http2 test
 [test_resubscribe_4xxCode_http2.js]
 [test_resubscribe_5xxCode_http2.js]
 [test_resubscribe_listening_for_msg_error_http2.js]
 [test_register_5xxCode_http2.js]
 [test_register_success_http2.js]
 skip-if = !hasNode
 run-sequentially = node server exceptions dont replay well
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ChromeUtils.webidl
@@ -13,16 +13,24 @@ interface ChromeUtils : ThreadSafeChrome
   /**
    * A helper that converts OriginAttributesDictionary to cookie jar opaque
    * identfier.
    *
    * @param originAttrs       The originAttributes from the caller.
    */
   static ByteString
   originAttributesToCookieJar(optional OriginAttributesDictionary originAttrs);
+
+  /**
+   * A helper that converts OriginAttributesDictionary to a opaque suffix string.
+   *
+   * @param originAttrs       The originAttributes from the caller.
+   */
+  static ByteString
+  originAttributesToSuffix(optional OriginAttributesDictionary originAttrs);
 };
 
 /**
  * Used by principals and the script security manager to represent origin
  * attributes.
  *
  * IMPORTANT: If you add any members here, you need to update the
  * methods on mozilla::OriginAttributes, and bump the CIDs of all
--- a/dom/webidl/Location.webidl
+++ b/dom/webidl/Location.webidl
@@ -16,11 +16,10 @@ interface Location {
   [Throws, UnsafeInPrerendering]
   void assign(DOMString url);
   [Throws, CrossOriginCallable, UnsafeInPrerendering]
   void replace(DOMString url);
   // XXXbz there is no forceget argument in the spec!  See bug 1037721.
   [Throws, UnsafeInPrerendering]
   void reload(optional boolean forceget = false);
 };
-// No support for .searchParams on Location yet.  See bug 1082734.
 
 Location implements URLUtils;
--- a/dom/webidl/PushSubscription.webidl
+++ b/dom/webidl/PushSubscription.webidl
@@ -2,16 +2,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/.
 *
 * The origin of this IDL file is
 * https://w3c.github.io/push-api/
 */
 
+interface Principal;
+
 [JSImplementation="@mozilla.org/push/PushSubscription;1",
- Constructor(DOMString pushEndpoint, DOMString scope, DOMString pageURL), ChromeOnly]
+ Constructor(DOMString pushEndpoint, DOMString scope, Principal principal), ChromeOnly]
 interface PushSubscription
 {
     readonly attribute USVString endpoint;
     Promise<boolean> unsubscribe();
     jsonifier;
 };
--- a/dom/webidl/URLUtils.webidl
+++ b/dom/webidl/URLUtils.webidl
@@ -46,10 +46,10 @@ interface URLUtils {
   // Bug 824857 should remove this.
   [Throws]
   stringifier;
 };
 
 [NoInterfaceObject,
  Exposed=(Window, Worker)]
 interface URLUtilsSearchParams {
-           attribute URLSearchParams searchParams;
+ readonly attribute URLSearchParams searchParams;
 };
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -279,17 +279,17 @@ public:
   }
 
 private:
   ~TeardownRunnable() {}
 
   nsRefPtr<ServiceWorkerManagerChild> mActor;
 };
 
-} // Anonymous namespace
+} // anonymous namespace
 
 NS_IMPL_ISUPPORTS0(ServiceWorkerJob)
 NS_IMPL_ISUPPORTS0(ServiceWorkerRegistrationInfo)
 
 void
 ServiceWorkerJob::Done(nsresult aStatus)
 {
   if (NS_WARN_IF(NS_FAILED(aStatus))) {
@@ -1582,16 +1582,84 @@ ServiceWorkerManager::AppendPendingOpera
   MOZ_ASSERT(aRunnable);
 
   if (!mShuttingDown) {
     PendingOperation* opt = mPendingOperations.AppendElement();
     opt->mRunnable = aRunnable;
   }
 }
 
+namespace {
+// Just holds a ref to a ServiceWorker until the Promise is fulfilled.
+class KeepAliveHandler final : public PromiseNativeHandler
+{
+  nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
+
+  virtual ~KeepAliveHandler()
+  {}
+
+public:
+  explicit KeepAliveHandler(const nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker)
+    : mServiceWorker(aServiceWorker)
+  {}
+
+  void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+#ifdef DEBUG
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(workerPrivate);
+    workerPrivate->AssertIsOnWorkerThread();
+#endif
+  }
+
+  void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+#ifdef DEBUG
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(workerPrivate);
+    workerPrivate->AssertIsOnWorkerThread();
+#endif
+  }
+};
+
+// Returns a Promise if the event was successfully dispatched and no exceptions
+// were raised, otherwise returns null.
+already_AddRefed<Promise>
+DispatchExtendableEventOnWorkerScope(JSContext* aCx,
+                                     WorkerGlobalScope* aWorkerScope,
+                                     ExtendableEvent* aEvent)
+{
+  MOZ_ASSERT(aWorkerScope);
+  MOZ_ASSERT(aEvent);
+  nsCOMPtr<nsIGlobalObject> sgo = aWorkerScope;
+  WidgetEvent* internalEvent = aEvent->GetInternalNSEvent();
+
+  ErrorResult result;
+  result = aWorkerScope->DispatchDOMEvent(nullptr, aEvent, nullptr, nullptr);
+  if (result.Failed() || internalEvent->mFlags.mExceptionHasBeenRisen) {
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> waitUntilPromise = aEvent->GetPromise();
+  if (!waitUntilPromise) {
+    ErrorResult result;
+    waitUntilPromise =
+      Promise::Resolve(sgo, aCx, JS::UndefinedHandleValue, result);
+    if (NS_WARN_IF(result.Failed())) {
+      return nullptr;
+    }
+  }
+
+  MOZ_ASSERT(waitUntilPromise);
+  return waitUntilPromise.forget();
+}
+}; // anonymous namespace
+
 /*
  * Used to handle ExtendableEvent::waitUntil() and proceed with
  * installation/activation.
  */
 class LifecycleEventPromiseHandler final : public PromiseNativeHandler
 {
   nsMainThreadPtrHandle<ContinueLifecycleTask> mTask;
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
@@ -1669,47 +1737,32 @@ LifecycleEventWorkerRunnable::DispatchLi
     init.mCancelable = true;
     event = ExtendableEvent::Constructor(target, mEventName, init);
   } else {
     MOZ_CRASH("Unexpected lifecycle event");
   }
 
   event->SetTrusted(true);
 
-  nsRefPtr<Promise> waitUntilPromise;
-
-  ErrorResult result;
-  result = target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
-
-  nsCOMPtr<nsIGlobalObject> sgo = aWorkerPrivate->GlobalScope();
-  WidgetEvent* internalEvent = event->GetInternalNSEvent();
-  if (!result.Failed() && !internalEvent->mFlags.mExceptionHasBeenRisen) {
-    waitUntilPromise = event->GetPromise();
-    if (!waitUntilPromise) {
-      ErrorResult result;
-      waitUntilPromise =
-        Promise::Resolve(sgo, aCx, JS::UndefinedHandleValue, result);
-      if (NS_WARN_IF(result.Failed())) {
-        return true;
-      }
-    }
+  nsRefPtr<Promise> waitUntilPromise =
+    DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(), event);
+  if (waitUntilPromise) {
+    nsRefPtr<LifecycleEventPromiseHandler> handler =
+      new LifecycleEventPromiseHandler(mTask, mServiceWorker, false /* activateImmediately */);
+    waitUntilPromise->AppendNativeHandler(handler);
   } else {
     // Continue with a canceled install.
     // Although the spec has different routines to deal with popping stuff
     // off it's internal queues, we can reuse the ContinueAfterInstallEvent()
     // logic.
     nsRefPtr<ContinueLifecycleRunnable> r =
       new ContinueLifecycleRunnable(mTask, false /* success */, false /* activate immediately */);
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
-    return true;
-  }
-
-  nsRefPtr<LifecycleEventPromiseHandler> handler =
-    new LifecycleEventPromiseHandler(mTask, mServiceWorker, false /* activateImmediately */);
-  waitUntilPromise->AppendNativeHandler(handler);
+  }
+
   return true;
 }
 
 void
 ServiceWorkerRegistrationInfo::TryToActivate()
 {
   if (!IsControllingDocuments() || mWaitingWorker->SkipWaitingFlag()) {
     Activate();
@@ -2086,18 +2139,23 @@ public:
       PushEvent::Constructor(globalObj, NS_LITERAL_STRING("push"), pei, result);
     if (NS_WARN_IF(result.Failed())) {
       return false;
     }
 
     event->SetTrusted(true);
     event->PostInit(mServiceWorker);
 
-    nsRefPtr<EventTarget> target = do_QueryObject(aWorkerPrivate->GlobalScope());
-    target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+    nsRefPtr<Promise> waitUntilPromise =
+      DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(), event);
+    if (waitUntilPromise) {
+      nsRefPtr<KeepAliveHandler> handler = new KeepAliveHandler(mServiceWorker);
+      waitUntilPromise->AppendNativeHandler(handler);
+    }
+
     return true;
   }
 };
 
 class SendPushSubscriptionChangeEventRunnable final : public WorkerRunnable
 {
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
 
@@ -2137,76 +2195,78 @@ public:
     globalScope->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
     return true;
   }
 };
 
 #endif /* ! MOZ_SIMPLEPUSH */
 
 NS_IMETHODIMP
-ServiceWorkerManager::SendPushEvent(JS::Handle<JS::Value> aOriginAttributes,
+ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
                                     const nsACString& aScope,
-                                    const nsAString& aData,
-                                    JSContext* aCx)
+                                    const nsAString& aData)
 {
 #ifdef MOZ_SIMPLEPUSH
   return NS_ERROR_NOT_AVAILABLE;
 #else
   OriginAttributes attrs;
-  if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+  if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsRefPtr<ServiceWorker> serviceWorker =
     CreateServiceWorkerForScope(attrs, aScope);
   if (!serviceWorker) {
     return NS_ERROR_FAILURE;
   }
 
   nsMainThreadPtrHandle<ServiceWorker> serviceWorkerHandle(
     new nsMainThreadPtrHolder<ServiceWorker>(serviceWorker));
 
   nsRefPtr<SendPushEventRunnable> r =
     new SendPushEventRunnable(serviceWorker->GetWorkerPrivate(), aData,
                               serviceWorkerHandle);
 
-  if (NS_WARN_IF(!r->Dispatch(aCx))) {
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 #endif
 }
 
 NS_IMETHODIMP
-ServiceWorkerManager::SendPushSubscriptionChangeEvent(JS::Handle<JS::Value> aOriginAttributes,
-                                                      const nsACString& aScope,
-                                                      JSContext* aCx)
+ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginAttributes,
+                                                      const nsACString& aScope)
 {
 #ifdef MOZ_SIMPLEPUSH
   return NS_ERROR_NOT_AVAILABLE;
 #else
   OriginAttributes attrs;
-  if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+  if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsRefPtr<ServiceWorker> serviceWorker =
     CreateServiceWorkerForScope(attrs, aScope);
   if (!serviceWorker) {
     return NS_ERROR_FAILURE;
   }
   nsMainThreadPtrHandle<ServiceWorker> serviceWorkerHandle(
     new nsMainThreadPtrHolder<ServiceWorker>(serviceWorker));
 
   nsRefPtr<SendPushSubscriptionChangeEventRunnable> r =
     new SendPushSubscriptionChangeEventRunnable(
       serviceWorker->GetWorkerPrivate(), serviceWorkerHandle);
 
-  if (NS_WARN_IF(!r->Dispatch(aCx))) {
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 #endif
 }
 
 NS_IMETHODIMP
@@ -3291,17 +3351,17 @@ public:
     uint32_t loadFlags;
     rv = channel->GetLoadFlags(&loadFlags);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsILoadInfo> loadInfo;
     rv = channel->GetLoadInfo(getter_AddRefs(loadInfo));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mContentPolicyType = loadInfo->GetContentPolicyType();
+    mContentPolicyType = loadInfo->InternalContentPolicyType();
 
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
     if (httpChannel) {
       rv = httpChannel->GetRequestMethod(mMethod);
       NS_ENSURE_SUCCESS(rv, rv);
 
       nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
       NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
--- a/dom/workers/URL.cpp
+++ b/dom/workers/URL.cpp
@@ -834,31 +834,16 @@ URL::SetSearchInternal(const nsAString& 
 mozilla::dom::URLSearchParams*
 URL::SearchParams()
 {
   CreateSearchParamsIfNeeded();
   return mSearchParams;
 }
 
 void
-URL::SetSearchParams(URLSearchParams& aSearchParams)
-{
-  if (mSearchParams) {
-    mSearchParams->RemoveObserver(this);
-  }
-
-  mSearchParams = &aSearchParams;
-  mSearchParams->AddObserver(this);
-
-  nsAutoString search;
-  mSearchParams->Serialize(search);
-  SetSearchInternal(search);
-}
-
-void
 URL::GetHash(nsAString& aHash, ErrorResult& aRv) const
 {
   nsRefPtr<GetterRunnable> runnable =
     new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterHash, aHash,
                        mURLProxy);
 
   if (!runnable->Dispatch(mWorkerPrivate->GetJSContext())) {
     JS_ReportPendingException(mWorkerPrivate->GetJSContext());
@@ -954,23 +939,22 @@ URL::URLSearchParamsUpdated(URLSearchPar
 
 void
 URL::UpdateURLSearchParams()
 {
   if (mSearchParams) {
     nsAutoString search;
     ErrorResult rv;
     GetSearch(search, rv);
-    mSearchParams->ParseInput(NS_ConvertUTF16toUTF8(Substring(search, 1)), this);
+    mSearchParams->ParseInput(NS_ConvertUTF16toUTF8(Substring(search, 1)));
   }
 }
 
 void
 URL::CreateSearchParamsIfNeeded()
 {
   if (!mSearchParams) {
-    mSearchParams = new URLSearchParams();
-    mSearchParams->AddObserver(this);
+    mSearchParams = new URLSearchParams(this);
     UpdateURLSearchParams();
   }
 }
 
 END_WORKERS_NAMESPACE
--- a/dom/workers/URL.h
+++ b/dom/workers/URL.h
@@ -103,18 +103,16 @@ public:
   void SetPathname(const nsAString& aPathname, ErrorResult& aRv);
 
   void GetSearch(nsAString& aSearch, ErrorResult& aRv) const;
 
   void SetSearch(const nsAString& aSearch, ErrorResult& aRv);
 
   URLSearchParams* SearchParams();
 
-  void SetSearchParams(URLSearchParams& aSearchParams);
-
   void GetHash(nsAString& aHost, ErrorResult& aRv) const;
 
   void SetHash(const nsAString& aHash, ErrorResult& aRv);
 
   void Stringify(nsAString& aRetval, ErrorResult& aRv) const
   {
     GetHref(aRetval, aRv);
   }
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -758,17 +758,24 @@ struct WorkerStructuredCloneCallbacks
 
     return false;
   }
 
   static void
   FreeTransfer(uint32_t aTag, JS::TransferableOwnership aOwnership,
                void *aContent, uint64_t aExtraData, void* aClosure)
   {
-    // Nothing to do.
+    if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
+      MOZ_ASSERT(aClosure);
+      MOZ_ASSERT(!aContent);
+      auto* closure = static_cast<WorkerStructuredCloneClosure*>(aClosure);
+
+      MOZ_ASSERT(aExtraData < closure->mMessagePortIdentifiers.Length());
+      dom::MessagePort::ForceClose(closure->mMessagePortIdentifiers[aExtraData]);
+    }
   }
 };
 
 const JSStructuredCloneCallbacks gWorkerStructuredCloneCallbacks = {
   WorkerStructuredCloneCallbacks::Read,
   WorkerStructuredCloneCallbacks::Write,
   WorkerStructuredCloneCallbacks::Error,
   WorkerStructuredCloneCallbacks::ReadTransfer,
--- a/dom/workers/test/urlSearchParams_worker.js
+++ b/dom/workers/test/urlSearchParams_worker.js
@@ -111,95 +111,43 @@ onmessage = function() {
     ok(url.searchParams.has('a'), "URL.searchParams.has('a')");
     is(url.searchParams.get('a'), 'b', "URL.searchParams.get('a')");
     ok(url.searchParams.has('c'), "URL.searchParams.has('c')");
     is(url.searchParams.get('c'), 'd', "URL.searchParams.get('c')");
 
     url.searchParams.set('e', 'f');
     ok(url.href.indexOf('e=f') != 1, 'URL right');
 
-    var u = new URLSearchParams();
-    u.append('foo', 'bar');
-    url.searchParams = u;
-    is(url.searchParams, u, "URL.searchParams is the same object");
-    is(url.searchParams.get('foo'), 'bar', "URL.searchParams.get('foo')");
-    is(url.href, 'http://www.example.net/?foo=bar', 'URL right');
-
-    try {
-      url.searchParams = null;
-      ok(false, "URLSearchParams is not nullable");
-    } catch(e) {
-      ok(true, "URLSearchParams is not nullable");
-    }
-
-    var url2 = new URL('http://www.example.net?e=f');
-    url.searchParams = url2.searchParams;
-    is(url.searchParams, url2.searchParams, "URL.searchParams is not the same object");
-    is(url.searchParams.get('e'), 'f', "URL.searchParams.get('e')");
-
-    url.href = "http://www.example.net?bar=foo";
-    is(url.searchParams.get('bar'), 'foo', "URL.searchParams.get('bar')");
-
     runTest();
   }
 
   function testEncoding() {
     var encoding = [ [ '1', '1' ],
                      [ 'a b', 'a+b' ],
                      [ '<>', '%3C%3E' ],
                      [ '\u0541', '%D5%81'] ];
 
     for (var i = 0; i < encoding.length; ++i) {
-      var a = new URLSearchParams();
-      a.set('a', encoding[i][0]);
-
       var url = new URL('http://www.example.net');
-      url.searchParams = a;
+      url.searchParams.set('a', encoding[i][0]);
       is(url.href, 'http://www.example.net/?a=' + encoding[i][1]);
 
       var url2 = new URL(url.href);
       is(url2.searchParams.get('a'), encoding[i][0], 'a is still there');
     }
 
     runTest();
   }
 
-  function testMultiURL() {
-    var a = new URL('http://www.example.net?a=b&c=d');
-    var b = new URL('http://www.example.net?e=f');
-    ok(a.searchParams.has('a'), "a.searchParams.has('a')");
-    ok(a.searchParams.has('c'), "a.searchParams.has('c')");
-    ok(b.searchParams.has('e'), "b.searchParams.has('e')");
-
-    var u = new URLSearchParams();
-    a.searchParams = b.searchParams = u;
-    is(a.searchParams, u, "a.searchParams === u");
-    is(b.searchParams, u, "b.searchParams === u");
-    ok(!a.searchParams.has('a'), "!a.searchParams.has('a')");
-    ok(!a.searchParams.has('c'), "!a.searchParams.has('c')");
-    ok(!b.searchParams.has('e'), "!b.searchParams.has('e')");
-
-    u.append('foo', 'bar');
-    is(a.searchParams.get('foo'), 'bar', "a has foo=bar");
-    is(b.searchParams.get('foo'), 'bar', "b has foo=bar");
-    is(a + "", b + "", "stringify a == b");
-
-    a.search = "?bar=foo";
-    is(a.searchParams.get('bar'), 'foo', "a has bar=foo");
-    is(b.searchParams.get('bar'), 'foo', "b has bar=foo");
-
-    runTest();
-  }
   var tests = [
     testSimpleURLSearchParams,
     testCopyURLSearchParams,
     testParserURLSearchParams,
     testURL,
     testEncoding,
-    testMultiURL
   ];
 
   function runTest() {
     if (!tests.length) {
       postMessage({type: 'finish' });
       return;
     }
 
--- a/dom/xbl/nsXBLMaybeCompiled.h
+++ b/dom/xbl/nsXBLMaybeCompiled.h
@@ -87,29 +87,29 @@ namespace js {
 
 template <class UncompiledT>
 struct GCMethods<nsXBLMaybeCompiled<UncompiledT> >
 {
   typedef struct GCMethods<JSObject *> Base;
 
   static nsXBLMaybeCompiled<UncompiledT> initial() { return nsXBLMaybeCompiled<UncompiledT>(); }
 
-  static void postBarrier(nsXBLMaybeCompiled<UncompiledT>* functionp,
-                          nsXBLMaybeCompiled<UncompiledT> prev,
-                          nsXBLMaybeCompiled<UncompiledT> next)
+  static bool needsPostBarrier(nsXBLMaybeCompiled<UncompiledT> function)
+  {
+    return function.IsCompiled() && Base::needsPostBarrier(function.GetJSFunction());
+  }
+
+  static void postBarrier(nsXBLMaybeCompiled<UncompiledT>* functionp)
   {
-    if (next.IsCompiled()) {
-      Base::postBarrier(&functionp->UnsafeGetJSFunction(),
-                        prev.IsCompiled() ? prev.UnsafeGetJSFunction() : nullptr,
-                        next.UnsafeGetJSFunction());
-    } else if (prev.IsCompiled()) {
-      Base::postBarrier(&prev.UnsafeGetJSFunction(),
-                        prev.UnsafeGetJSFunction(),
-                        nullptr);
-    }
+    Base::postBarrier(&functionp->UnsafeGetJSFunction());
+  }
+
+  static void relocate(nsXBLMaybeCompiled<UncompiledT>* functionp)
+  {
+    Base::relocate(&functionp->UnsafeGetJSFunction());
   }
 };
 
 template <class UncompiledT>
 class HeapBase<nsXBLMaybeCompiled<UncompiledT> >
 {
   const JS::Heap<nsXBLMaybeCompiled<UncompiledT> >& wrapper() const {
     return *static_cast<const JS::Heap<nsXBLMaybeCompiled<UncompiledT> >*>(this);
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -452,25 +452,38 @@ private:
     cairo_surface_set_device_offset(mSurface, 0, 0);
   }
 
   cairo_surface_t* mSurface;
   double mX;
   double mY;
 };
 
+static inline void
+CairoPatternAddGradientStop(cairo_pattern_t* aPattern,
+                            const GradientStop &aStop,
+                            Float aNudge = 0)
+{
+  cairo_pattern_add_color_stop_rgba(aPattern, aStop.offset + aNudge,
+                                    aStop.color.r, aStop.color.g, aStop.color.b,
+                                    aStop.color.a);
+
+}
+
 // Never returns nullptr. As such, you must always pass in Cairo-compatible
 // patterns, most notably gradients with a GradientStopCairo.
 // The pattern returned must have cairo_pattern_destroy() called on it by the
 // caller.
 // As the cairo_pattern_t returned may depend on the Pattern passed in, the
 // lifetime of the cairo_pattern_t returned must not exceed the lifetime of the
 // Pattern passed in.
 static cairo_pattern_t*
-GfxPatternToCairoPattern(const Pattern& aPattern, Float aAlpha)
+GfxPatternToCairoPattern(const Pattern& aPattern,
+                         Float aAlpha,
+                         const Matrix& aTransform)
 {
   cairo_pattern_t* pat;
   const Matrix* matrix = nullptr;
 
   switch (aPattern.GetType())
   {
     case PatternType::COLOR:
     {
@@ -507,21 +520,35 @@ GfxPatternToCairoPattern(const Pattern& 
 
       MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO);
       GradientStopsCairo* cairoStops = static_cast<GradientStopsCairo*>(pattern.mStops.get());
       cairo_pattern_set_extend(pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode()));
 
       matrix = &pattern.mMatrix;
 
       const std::vector<GradientStop>& stops = cairoStops->GetStops();
+      if (stops.size() >= 2 && stops.front().offset == stops.back().offset) {
+        // Certain Cairo backends that use pixman to implement gradients can have jagged
+        // edges occur with hard stops. Such hard stops are used for implementing certain
+        // types of CSS borders. Work around this by turning these hard-stops into half-pixel
+        // gradients to anti-alias them. See bug 1033375
+        Matrix patternToDevice = aTransform * pattern.mMatrix;
+        Float gradLength = (patternToDevice * pattern.mEnd - patternToDevice * pattern.mBegin).Length();
+        if (gradLength > 0) {
+          Float aaOffset = 0.25 / gradLength;
+          CairoPatternAddGradientStop(pat, stops.front(), -aaOffset);
+          for (size_t i = 1; i < stops.size()-1; ++i) {
+            CairoPatternAddGradientStop(pat, stops[i]);
+          }
+          CairoPatternAddGradientStop(pat, stops.back(), aaOffset);
+          break;
+        }
+      }
       for (size_t i = 0; i < stops.size(); ++i) {
-        const GradientStop& stop = stops[i];
-        cairo_pattern_add_color_stop_rgba(pat, stop.offset, stop.color.r,
-                                          stop.color.g, stop.color.b,
-                                          stop.color.a);
+        CairoPatternAddGradientStop(pat, stops[i]);
       }
 
       break;
     }
     case PatternType::RADIAL_GRADIENT:
     {
       const RadialGradientPattern& pattern = static_cast<const RadialGradientPattern&>(aPattern);
 
@@ -531,20 +558,17 @@ GfxPatternToCairoPattern(const Pattern& 
       MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO);
       GradientStopsCairo* cairoStops = static_cast<GradientStopsCairo*>(pattern.mStops.get());
       cairo_pattern_set_extend(pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode()));
 
       matrix = &pattern.mMatrix;
 
       const std::vector<GradientStop>& stops = cairoStops->GetStops();
       for (size_t i = 0; i < stops.size(); ++i) {
-        const GradientStop& stop = stops[i];
-        cairo_pattern_add_color_stop_rgba(pat, stop.offset, stop.color.r,
-                                          stop.color.g, stop.color.b,
-                                          stop.color.a);
+        CairoPatternAddGradientStop(pat, stops[i]);
       }
 
       break;
     }
     default:
     {
       // We should support all pattern types!
       MOZ_ASSERT(false);
@@ -887,17 +911,17 @@ DrawTargetCairo::DrawPattern(const Patte
                              bool aPathBoundsClip)
 {
   if (!PatternIsCompatible(aPattern)) {
     return;
   }
 
   AutoClearDeviceOffset clear(aPattern);
 
-  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha);
+  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
   if (!pat) {
     return;
   }
   if (cairo_pattern_status(pat)) {
     cairo_pattern_destroy(pat);
     gfxWarning() << "Invalid pattern";
     return;
   }
@@ -1169,17 +1193,17 @@ DrawTargetCairo::FillGlyphs(ScaledFont *
                             const GlyphRenderingOptions*)
 {
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clear(aPattern);
 
   ScaledFontBase* scaledFont = static_cast<ScaledFontBase*>(aFont);
   cairo_set_scaled_font(mContext, scaledFont->GetCairoScaledFont());
 
-  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha);
+  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
   if (!pat)
     return;
 
   cairo_set_source(mContext, pat);
   cairo_pattern_destroy(pat);
 
   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 
@@ -1208,22 +1232,22 @@ DrawTargetCairo::Mask(const Pattern &aSo
                       const DrawOptions &aOptions /* = DrawOptions() */)
 {
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clearSource(aSource);
   AutoClearDeviceOffset clearMask(aMask);
 
   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 
-  cairo_pattern_t* source = GfxPatternToCairoPattern(aSource, aOptions.mAlpha);
+  cairo_pattern_t* source = GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
   if (!source) {
     return;
   }
 
-  cairo_pattern_t* mask = GfxPatternToCairoPattern(aMask, aOptions.mAlpha);
+  cairo_pattern_t* mask = GfxPatternToCairoPattern(aMask, aOptions.mAlpha, GetTransform());
   if (!mask) {
     cairo_pattern_destroy(source);
     return;
   }
 
   if (cairo_pattern_status(source) || cairo_pattern_status(mask)) {
     cairo_pattern_destroy(source);
     cairo_pattern_destroy(mask);
@@ -1249,17 +1273,17 @@ DrawTargetCairo::MaskSurface(const Patte
   AutoClearDeviceOffset clearMask(aMask);
 
   if (!PatternIsCompatible(aSource)) {
     return;
   }
 
   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 
-  cairo_pattern_t* pat = GfxPatternToCairoPattern(aSource, aOptions.mAlpha);
+  cairo_pattern_t* pat = GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
   if (!pat) {
     return;
   }
 
   if (cairo_pattern_status(pat)) {
     cairo_pattern_destroy(pat);
     gfxWarning() << "Invalid pattern";
     return;
--- a/gfx/gl/GLContextEGL.h
+++ b/gfx/gl/GLContextEGL.h
@@ -5,20 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GLCONTEXTEGL_H_
 #define GLCONTEXTEGL_H_
 
 #include "GLContext.h"
 #include "GLLibraryEGL.h"
 
-#ifdef MOZ_WIDGET_GONK
-#include "HwcComposer2D.h"
-#endif
-
 class nsIWidget;
 
 namespace mozilla {
 namespace gl {
 
 class GLContextEGL : public GLContext
 {
     friend class TextureImageEGL;
@@ -123,19 +119,16 @@ protected:
     EGLContext mContext;
     nsRefPtr<gfxASurface> mThebesSurface;
     bool mBound;
 
     bool mIsPBuffer;
     bool mIsDoubleBuffered;
     bool mCanBindToTexture;
     bool mShareWithEGLImage;
-#ifdef MOZ_WIDGET_GONK
-    nsRefPtr<HwcComposer2D> mHwc;
-#endif
     bool mOwnsContext;
 
     static EGLSurface CreatePBufferSurfaceTryingPowerOfTwo(EGLConfig config,
                                                            EGLenum bindToTextureFormat,
                                                            gfx::IntSize& pbsize);
 };
 
 }
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -12,17 +12,16 @@
 #ifdef MOZ_WIDGET_GTK
 #include <gdk/gdkx.h>
 // we're using default display for now
 #define GET_NATIVE_WINDOW(aWidget) (EGLNativeWindowType)GDK_WINDOW_XID((GdkWindow *) aWidget->GetNativeData(NS_NATIVE_WINDOW))
 #elif defined(MOZ_WIDGET_QT)
 #define GET_NATIVE_WINDOW(aWidget) (EGLNativeWindowType)(aWidget->GetNativeData(NS_NATIVE_SHAREABLE_WINDOW))
 #elif defined(MOZ_WIDGET_GONK)
 #define GET_NATIVE_WINDOW(aWidget) ((EGLNativeWindowType)aWidget->GetNativeData(NS_NATIVE_WINDOW))
-#include "HwcComposer2D.h"
 #include "libdisplay/GonkDisplay.h"
 #include "nsWindow.h"
 #include "nsScreenManagerGonk.h"
 #endif
 
 #if defined(ANDROID)
 /* from widget */
 #if defined(MOZ_WIDGET_ANDROID)
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -178,16 +178,19 @@ AppendToString(std::stringstream& aStrea
   AppendToString(aStream, m.GetDisplayPort(), "] [dp=");
   AppendToString(aStream, m.GetCriticalDisplayPort(), "] [cdp=");
   AppendToString(aStream, m.GetBackgroundColor(), "] [color=");
   if (!detailed) {
     AppendToString(aStream, m.GetScrollId(), "] [scrollId=");
     if (m.GetScrollParentId() != FrameMetrics::NULL_SCROLL_ID) {
       AppendToString(aStream, m.GetScrollParentId(), "] [scrollParent=");
     }
+    if (m.IsRootContent()) {
+      aStream << "] [rcd";
+    }
     if (m.HasClipRect()) {
       AppendToString(aStream, m.ClipRect(), "] [clip=");
     }
     AppendToString(aStream, m.GetZoom(), "] [z=", "] }");
   } else {
     AppendToString(aStream, m.GetDisplayPortMargins(), " [dpm=");
     aStream << nsPrintfCString("] um=%d", m.GetUseDisplayPortMargins()).get();
     AppendToString(aStream, m.GetRootCompositionSize(), "] [rcs=");
@@ -197,18 +200,18 @@ AppendToString(std::stringstream& aStrea
             m.GetPresShellResolution()).get();
     AppendToString(aStream, m.GetCumulativeResolution(), " cr=");
     AppendToString(aStream, m.GetZoom(), " z=");
     AppendToString(aStream, m.GetExtraResolution(), " er=");
     aStream << nsPrintfCString(")] [u=(%d %d %lu)",
             m.GetScrollOffsetUpdated(), m.GetDoSmoothScroll(),
             m.GetScrollGeneration()).get();
     AppendToString(aStream, m.GetScrollParentId(), "] [p=");
-    aStream << nsPrintfCString("] [i=(%ld %lld)] }",
-            m.GetPresShellId(), m.GetScrollId()).get();
+    aStream << nsPrintfCString("] [i=(%ld %lld %d)] }",
+            m.GetPresShellId(), m.GetScrollId(), m.IsRootContent()).get();
   }
   aStream << sfx;
 }
 
 void
 AppendToString(std::stringstream& aStream, const ScrollableLayerGuid& s,
                const char* pfx, const char* sfx)
 {
--- a/image/DecodePool.cpp
+++ b/image/DecodePool.cpp
@@ -212,21 +212,16 @@ public:
   }
 
   /// Pops a new work item, blocking if necessary.
   Work PopWork()
   {
     MonitorAutoLock lock(mMonitor);
 
     do {
-      // XXX(seth): The queue popping code below is NOT efficient, obviously,
-      // since we're removing an element from the front of the array. However,
-      // it's not worth implementing something better right now, because we are
-      // replacing this FIFO behavior with LIFO behavior very soon.
-
       // Prioritize size decodes over full decodes.
       if (!mSizeDecodeQueue.IsEmpty()) {
         return PopWorkFromQueue(mSizeDecodeQueue);
       }
 
       if (!mFullDecodeQueue.IsEmpty()) {
         return PopWorkFromQueue(mFullDecodeQueue);
       }
@@ -244,18 +239,18 @@ public:
 
 private:
   ~DecodePoolImpl() { }
 
   Work PopWorkFromQueue(nsTArray<nsRefPtr<Decoder>>& aQueue)
   {
     Work work;
     work.mType = Work::Type::DECODE;
-    work.mDecoder = aQueue.ElementAt(0);
-    aQueue.RemoveElementAt(0);
+    work.mDecoder = aQueue.LastElement();
+    aQueue.RemoveElementAt(aQueue.Length() - 1);
 
     return work;
   }
 
   nsThreadPoolNaming mThreadNaming;
 
   // mMonitor guards mQueue and mShuttingDown.
   Monitor mMonitor;
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -583,16 +583,27 @@ BackgroundParentImpl::DeallocPMessagePor
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   delete static_cast<MessagePortParent*>(aActor);
   return true;
 }
 
+bool
+BackgroundParentImpl::RecvMessagePortForceClose(const nsID& aUUID,
+                                                const nsID& aDestinationUUID,
+                                                const uint32_t& aSequenceID)
+{
+  AssertIsInMainProcess();
+  AssertIsOnBackgroundThread();
+
+  return MessagePortParent::ForceClose(aUUID, aDestinationUUID, aSequenceID);
+}
+
 } // namespace ipc
 } // namespace mozilla
 
 void
 TestParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -132,14 +132,19 @@ protected:
   virtual bool
   RecvPMessagePortConstructor(PMessagePortParent* aActor,
                               const nsID& aUUID,
                               const nsID& aDestinationUUID,
                               const uint32_t& aSequenceID) override;
 
   virtual bool
   DeallocPMessagePortParent(PMessagePortParent* aActor) override;
+
+  virtual bool
+  RecvMessagePortForceClose(const nsID& aUUID,
+                            const nsID& aDestinationUUID,
+                            const uint32_t& aSequenceID) override;
 };
 
 } // namespace ipc
 } // namespace mozilla
 
 #endif // mozilla_ipc_backgroundparentimpl_h__
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -55,16 +55,18 @@ parent:
   PServiceWorkerManager();
 
   ShutdownServiceWorkerRegistrar();
 
   PCacheStorage(Namespace aNamespace, PrincipalInfo aPrincipalInfo);
 
   PMessagePort(nsID uuid, nsID destinationUuid, uint32_t sequenceId);
 
+  MessagePortForceClose(nsID uuid, nsID destinationUuid, uint32_t sequenceId);
+
 child:
   PCache();
   PCacheStreamControl();
 
 both:
   PBlob(BlobConstructorParams params);
 
   PFileDescriptorSet(FileDescriptor fd);
--- a/js/ipc/WrapperAnswer.cpp
+++ b/js/ipc/WrapperAnswer.cpp
@@ -184,17 +184,17 @@ WrapperAnswer::RecvDefineProperty(const 
     if (!fromJSIDVariant(cx, idVar, &id))
         return fail(jsapi, rs);
 
     Rooted<JSPropertyDescriptor> desc(cx);
     if (!toDescriptor(cx, descriptor, &desc))
         return fail(jsapi, rs);
 
     ObjectOpResult success;
-    if (!js::DefineOwnProperty(cx, obj, id, desc, success))
+    if (!JS_DefinePropertyById(cx, obj, id, desc, success))
         return fail(jsapi, rs);
     return ok(rs, success);
 }
 
 bool
 WrapperAnswer::RecvDelete(const ObjectId& objId, const JSIDVariant& idVar, ReturnStatus* rs)
 {
     AutoJSAPI jsapi;
--- a/js/public/Id.h
+++ b/js/public/Id.h
@@ -166,17 +166,19 @@ extern JS_PUBLIC_DATA(const jsid) JSID_E
 extern JS_PUBLIC_DATA(const JS::HandleId) JSID_VOIDHANDLE;
 extern JS_PUBLIC_DATA(const JS::HandleId) JSID_EMPTYHANDLE;
 
 namespace js {
 
 template <> struct GCMethods<jsid>
 {
     static jsid initial() { return JSID_VOID; }
-    static void postBarrier(jsid* idp, jsid prev, jsid next) {}
+    static bool needsPostBarrier(jsid id) { return false; }
+    static void postBarrier(jsid* idp) {}
+    static void relocate(jsid* idp) {}
 };
 
 // If the jsid is a GC pointer type, convert to that type and call |f| with
 // the pointer. If the jsid is not a GC type, calls F::defaultValue.
 template <typename F, typename... Args>
 auto
 DispatchIdTyped(F f, jsid& id, Args&&... args)
   -> decltype(f(static_cast<JSString*>(nullptr), mozilla::Forward<Args>(args)...))
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -169,17 +169,18 @@ struct PersistentRootedMarker;
 namespace JS {
 
 template <typename T> class Rooted;
 template <typename T> class PersistentRooted;
 
 /* This is exposing internal state of the GC for inlining purposes. */
 JS_FRIEND_API(bool) isGCEnabled();
 
-JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp, JSObject* prev, JSObject* next);
+JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp);
+JS_FRIEND_API(void) HeapObjectRelocate(JSObject** objp);
 
 #ifdef JS_DEBUG
 /*
  * For generational GC, assert that an object is in the tenured generation as
  * opposed to being in the nursery.
  */
 extern JS_FRIEND_API(void)
 AssertGCThingMustBeTenured(JSObject* obj);
@@ -226,17 +227,18 @@ class Heap : public js::HeapBase<T>
      * For Heap, move semantics are equivalent to copy semantics. In C++, a
      * copy constructor taking const-ref is the way to get a single function
      * that will be used for both lvalue and rvalue copies, so we can simply
      * omit the rvalue variant.
      */
     explicit Heap(const Heap<T>& p) { init(p.ptr); }
 
     ~Heap() {
-        post(ptr, js::GCMethods<T>::initial());
+        if (js::GCMethods<T>::needsPostBarrier(ptr))
+            relocate();
     }
 
     DECLARE_POINTER_CONSTREF_OPS(T);
     DECLARE_POINTER_ASSIGN_OPS(Heap, T);
     DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr);
 
     T* unsafeGet() { return &ptr; }
 
@@ -250,27 +252,39 @@ class Heap : public js::HeapBase<T>
 
     bool isSetToCrashOnTouch() {
         return ptr == crashOnTouchPointer;
     }
 
   private:
     void init(T newPtr) {
         ptr = newPtr;
-        post(js::GCMethods<T>::initial(), ptr);
+        if (js::GCMethods<T>::needsPostBarrier(ptr))
+            post();
     }
 
     void set(T newPtr) {
-        T tmp = ptr;
-        ptr = newPtr;
-        post(tmp, ptr);
+        if (js::GCMethods<T>::needsPostBarrier(newPtr)) {
+            ptr = newPtr;
+            post();
+        } else if (js::GCMethods<T>::needsPostBarrier(ptr)) {
+            relocate();  /* Called before overwriting ptr. */
+            ptr = newPtr;
+        } else {
+            ptr = newPtr;
+        }
     }
 
-    void post(const T& prev, const T& next) {
-        js::GCMethods<T>::postBarrier(&ptr, prev, next);
+    void post() {
+        MOZ_ASSERT(js::GCMethods<T>::needsPostBarrier(ptr));
+        js::GCMethods<T>::postBarrier(&ptr);
+    }
+
+    void relocate() {
+        js::GCMethods<T>::relocate(&ptr);
     }
 
     enum {
         crashOnTouchPointer = 1
     };
 
     T ptr;
 };
@@ -585,46 +599,57 @@ struct RootKind<T*>
 {
     static ThingRootKind rootKind() { return T::rootKind(); }
 };
 
 template <typename T>
 struct GCMethods<T*>
 {
     static T* initial() { return nullptr; }
-    static void postBarrier(T** vp, T* prev, T* next) {
-        if (next)
-            JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast<js::gc::Cell*>(next));
+    static bool needsPostBarrier(T* v) { return false; }
+    static void postBarrier(T** vp) {
+        if (vp)
+            JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast<js::gc::Cell*>(vp));
     }
     static void relocate(T** vp) {}
 };
 
 template <>
 struct GCMethods<JSObject*>
 {
     static JSObject* initial() { return nullptr; }
     static gc::Cell* asGCThingOrNull(JSObject* v) {
         if (!v)
             return nullptr;
         MOZ_ASSERT(uintptr_t(v) > 32);
         return reinterpret_cast<gc::Cell*>(v);
     }
-    static void postBarrier(JSObject** vp, JSObject* prev, JSObject* next) {
-        JS::HeapObjectPostBarrier(vp, prev, next);
+    static bool needsPostBarrier(JSObject* v) {
+        return v != nullptr && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(v));
+    }
+    static void postBarrier(JSObject** vp) {
+        JS::HeapObjectPostBarrier(vp);
+    }
+    static void relocate(JSObject** vp) {
+        JS::HeapObjectRelocate(vp);
     }
 };
 
 template <>
 struct GCMethods<JSFunction*>
 {
     static JSFunction* initial() { return nullptr; }
-    static void postBarrier(JSFunction** vp, JSFunction* prev, JSFunction* next) {
-        JS::HeapObjectPostBarrier(reinterpret_cast<JSObject**>(vp),
-                                  reinterpret_cast<JSObject*>(prev),
-                                  reinterpret_cast<JSObject*>(next));
+    static bool needsPostBarrier(JSFunction* v) {
+        return v != nullptr && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(v));
+    }
+    static void postBarrier(JSFunction** vp) {
+        JS::HeapObjectPostBarrier(reinterpret_cast<JSObject**>(vp));
+    }
+    static void relocate(JSFunction** vp) {
+        JS::HeapObjectRelocate(reinterpret_cast<JSObject**>(vp));
     }
 };
 
 } /* namespace js */
 
 namespace JS {
 
 /*
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1624,35 +1624,38 @@ SameType(const Value& lhs, const Value& 
     return JSVAL_SAME_TYPE_IMPL(lhs.data, rhs.data);
 }
 
 } // namespace JS
 
 /************************************************************************/
 
 namespace JS {
-JS_PUBLIC_API(void) HeapValuePostBarrier(Value* valuep, const Value& prev, const Value& next);
+JS_PUBLIC_API(void) HeapValuePostBarrier(Value* valuep);
+JS_PUBLIC_API(void) HeapValueRelocate(Value* valuep);
 }
 
 namespace js {
 
 template <> struct GCMethods<const JS::Value>
 {
     static JS::Value initial() { return JS::UndefinedValue(); }
 };
 
 template <> struct GCMethods<JS::Value>
 {
     static JS::Value initial() { return JS::UndefinedValue(); }
     static gc::Cell* asGCThingOrNull(const JS::Value& v) {
         return v.isMarkable() ? v.toGCThing() : nullptr;
     }
-    static void postBarrier(JS::Value* v, const JS::Value& prev, const JS::Value& next) {
-        JS::HeapValuePostBarrier(v, prev, next);
+    static bool needsPostBarrier(const JS::Value& v) {
+        return v.isObject() && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(&v.toObject()));
     }
+    static void postBarrier(JS::Value* v) { JS::HeapValuePostBarrier(v); }
+    static void relocate(JS::Value* v) { JS::HeapValueRelocate(v); }
 };
 
 template <class Outer> class MutableValueOperations;
 
 /*
  * A class designed for CRTP use in implementing the non-mutating parts of the
  * Value interface in Value-like classes.  Outer must be a class inheriting
  * ValueOperations<Outer> with a visible extract() method returning the
--- a/js/src/asmjs/AsmJSModule.h
+++ b/js/src/asmjs/AsmJSModule.h
@@ -846,17 +846,17 @@ class AsmJSModule
     ScriptSource *                        scriptSource_;
     PropertyName *                        globalArgumentName_;
     PropertyName *                        importArgumentName_;
     PropertyName *                        bufferArgumentName_;
     uint8_t *                             code_;
     uint8_t *                             interruptExit_;
     uint8_t *                             outOfBoundsExit_;
     StaticLinkData                        staticLinkData_;
-    HeapPtrArrayBufferObjectMaybeShared   maybeHeap_;
+    RelocatablePtrArrayBufferObjectMaybeShared maybeHeap_;
     AsmJSModule **                        prevLinked_;
     AsmJSModule *                         nextLinked_;
     bool                                  dynamicallyLinked_;
     bool                                  loadedFromCache_;
     bool                                  profilingEnabled_;
     bool                                  interrupted_;
 
     void restoreHeapToInitialState(ArrayBufferObjectMaybeShared* maybePrevBuffer);
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -1194,17 +1194,17 @@ MapObject::set(JSContext* cx, HandleObje
     ValueMap* map = obj->as<MapObject>().getData();
     if (!map)
         return false;
 
     AutoHashableValueRooter key(cx);
     if (!key.setValue(cx, k))
         return false;
 
-    HeapValue rval(v);
+    RelocatableValue rval(v);
     if (!map->put(key, rval)) {
         ReportOutOfMemory(cx);
         return false;
     }
     WriteBarrierPost(cx->runtime(), map, key.get());
     return true;
 }
 
@@ -1289,17 +1289,17 @@ MapObject::construct(JSContext* cx, unsi
             RootedValue val(cx);
             if (!GetElement(cx, pairObj, pairObj, 1, &val))
                 return false;
 
             if (isOriginalAdder) {
                 if (!hkey.setValue(cx, key))
                     return false;
 
-                HeapValue rval(val);
+                RelocatableValue rval(val);
                 if (!map->put(hkey, rval)) {
                     ReportOutOfMemory(cx);
                     return false;
                 }
                 WriteBarrierPost(cx->runtime(), map, key);
             } else {
                 if (!args2.init(2))
                     return false;
@@ -1446,17 +1446,17 @@ MapObject::has(JSContext* cx, unsigned a
 
 bool
 MapObject::set_impl(JSContext* cx, CallArgs args)
 {
     MOZ_ASSERT(MapObject::is(args.thisv()));
 
     ValueMap& map = extract(args);
     ARG0_KEY(cx, args, key);
-    HeapValue rval(args.get(1));
+    RelocatableValue rval(args.get(1));
     if (!map.put(key, rval)) {
         ReportOutOfMemory(cx);
         return false;
     }
     WriteBarrierPost(cx->runtime(), &map, key.get());
     args.rval().set(args.thisv());
     return true;
 }
@@ -1467,24 +1467,24 @@ MapObject::set(JSContext* cx, unsigned a
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<MapObject::is, MapObject::set_impl>(cx, args);
 }
 
 bool
 MapObject::delete_impl(JSContext* cx, CallArgs args)
 {
     // MapObject::mark does not mark deleted entries. Incremental GC therefore
-    // requires that no HeapValue objects pointing to heap values be left alive
-    // in the ValueMap.
+    // requires that no RelocatableValue objects pointing to heap values be
+    // left alive in the ValueMap.
     //
     // OrderedHashMap::remove() doesn't destroy the removed entry. It merely
     // calls OrderedHashMap::MapOps::makeEmpty. But that is sufficient, because
     // makeEmpty clears the value by doing e->value = Value(), and in the case
-    // of a ValueMap, Value() means HeapValue(), which is the same as
-    // HeapValue(UndefinedValue()).
+    // of a ValueMap, Value() means RelocatableValue(), which is the same as
+    // RelocatableValue(UndefinedValue()).
     MOZ_ASSERT(MapObject::is(args.thisv()));
 
     ValueMap& map = extract(args);
     ARG0_KEY(cx, args, key);
     bool found;
     if (!map.remove(key, &found)) {
         ReportOutOfMemory(cx);
         return false;
--- a/js/src/builtin/MapObject.h
+++ b/js/src/builtin/MapObject.h
@@ -72,17 +72,17 @@ class AutoHashableValueRooter : private 
 
 template <class Key, class Value, class OrderedHashPolicy, class AllocPolicy>
 class OrderedHashMap;
 
 template <class T, class OrderedHashPolicy, class AllocPolicy>
 class OrderedHashSet;
 
 typedef OrderedHashMap<HashableValue,
-                       HeapValue,
+                       RelocatableValue,
                        HashableValue::Hasher,
                        RuntimeAllocPolicy> ValueMap;
 
 typedef OrderedHashSet<HashableValue,
                        HashableValue::Hasher,
                        RuntimeAllocPolicy> ValueSet;
 
 class MapObject : public NativeObject {
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -827,17 +827,17 @@ js::obj_defineProperty(JSContext* cx, un
         return false;
 
     // Steps 4-5.
     Rooted<PropertyDescriptor> desc(cx);
     if (!ToPropertyDescriptor(cx, args.get(2), true, &desc))
         return false;
 
     // Steps 6-8.
-    if (!StandardDefineProperty(cx, obj, id, desc))
+    if (!DefineProperty(cx, obj, id, desc))
         return false;
     args.rval().setObject(*obj);
     return true;
 }
 
 /* ES5 15.2.3.7: Object.defineProperties(O, Properties) */
 static bool
 obj_defineProperties(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/gc/Barrier.cpp
+++ b/js/src/gc/Barrier.cpp
@@ -85,20 +85,36 @@ js::PreBarrierFunctor<S>::operator()(T* 
 }
 template void js::PreBarrierFunctor<JS::Value>::operator()<JS::Symbol>(JS::Symbol*);
 template void js::PreBarrierFunctor<JS::Value>::operator()<JSObject>(JSObject*);
 template void js::PreBarrierFunctor<JS::Value>::operator()<JSString>(JSString*);
 template void js::PreBarrierFunctor<jsid>::operator()<JS::Symbol>(JS::Symbol*);
 template void js::PreBarrierFunctor<jsid>::operator()<JSString>(JSString*);
 
 JS_PUBLIC_API(void)
-JS::HeapObjectPostBarrier(JSObject** objp, JSObject* prev, JSObject* next)
+JS::HeapObjectPostBarrier(JSObject** objp)
 {
     MOZ_ASSERT(objp);
-    js::InternalGCMethods<JSObject*>::postBarrier(objp, prev, next);
+    MOZ_ASSERT(*objp);
+    js::InternalGCMethods<JSObject*>::postBarrierRelocate(objp);
 }
 
 JS_PUBLIC_API(void)
-JS::HeapValuePostBarrier(JS::Value* valuep, const Value& prev, const Value& next)
+JS::HeapObjectRelocate(JSObject** objp)
+{
+    MOZ_ASSERT(objp);
+    MOZ_ASSERT(*objp);
+    js::InternalGCMethods<JSObject*>::postBarrierRemove(objp);
+}
+
+JS_PUBLIC_API(void)
+JS::HeapValuePostBarrier(JS::Value* valuep)
 {
     MOZ_ASSERT(valuep);
-    js::InternalGCMethods<JS::Value>::postBarrier(valuep, prev, next);
+    js::InternalGCMethods<JS::Value>::postBarrierRelocate(valuep);
 }
+
+JS_PUBLIC_API(void)
+JS::HeapValueRelocate(JS::Value* valuep)
+{
+    MOZ_ASSERT(valuep);
+    js::InternalGCMethods<JS::Value>::postBarrierRemove(valuep);
+}
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -128,36 +128,38 @@
  * and jsid, respectively.
  *
  * One additional note: not all object writes need to be pre-barriered. Writes
  * to newly allocated objects do not need a pre-barrier. In these cases, we use
  * the "obj->field.init(value)" method instead of "obj->field = value". We use
  * the init naming idiom in many places to signify that a field is being
  * assigned for the first time.
  *
- * This file implements three classes, illustrated here:
+ * This file implements four classes, illustrated here:
  *
- * BarrieredBase         abstract base class which provides common operations
- *    |  |
- *    | PreBarriered     provides pre-barriers only
- *    |
- *   HeapPtr             provides pre- and post-barriers
+ * BarrieredBase          abstract base class which provides common operations
+ *  |  |  |
+ *  |  | PreBarriered     provides pre-barriers only
+ *  |  |
+ *  | HeapPtr             provides pre- and post-barriers
+ *  |
+ * RelocatablePtr         provides pre- and post-barriers and is relocatable
  *
  * The implementation of the barrier logic is implemented on T::writeBarrier.*,
  * via:
  *
  * BarrieredBase<T>::pre
  *  -> InternalGCMethods<T*>::preBarrier
  *      -> T::writeBarrierPre
  *  -> InternalGCMethods<Value>::preBarrier
  *  -> InternalGCMethods<jsid>::preBarrier
  *      -> InternalGCMethods<T*>::preBarrier
  *          -> T::writeBarrierPre
  *
- * HeapPtr<T>::post
+ * HeapPtr<T>::post and RelocatablePtr<T>::post
  *  -> InternalGCMethods<T*>::postBarrier
  *      -> T::writeBarrierPost
  *  -> InternalGCMethods<Value>::postBarrier
  *      -> StoreBuffer::put
  *
  * These classes are designed to be used by the internals of the JS engine.
  * Barriers designed to be used externally are provided in js/RootingAPI.h.
  * These external barriers call into the same post-barrier implementations at
@@ -232,19 +234,19 @@ struct InternalGCMethods {};
 
 template <typename T>
 struct InternalGCMethods<T*>
 {
     static bool isMarkable(T* v) { return v != nullptr; }
 
     static void preBarrier(T* v) { T::writeBarrierPre(v); }
 
-    static void postBarrier(T** vp, T* prior, T* next) {
-        return T::writeBarrierPost(vp, prior, next);
-    }
+    static void postBarrier(T** vp) { T::writeBarrierPost(*vp, vp); }
+    static void postBarrierRelocate(T** vp) { T::writeBarrierPostRelocate(*vp, vp); }
+    static void postBarrierRemove(T** vp) { T::writeBarrierPostRemove(*vp, vp); }
 
     static void readBarrier(T* v) { T::readBarrier(v); }
 };
 
 template <typename S> struct PreBarrierFunctor : VoidDefaultAdaptor<S> {
     template <typename T> void operator()(T* t);
 };
 
@@ -256,49 +258,57 @@ template <>
 struct InternalGCMethods<Value>
 {
     static bool isMarkable(Value v) { return v.isMarkable(); }
 
     static void preBarrier(Value v) {
         DispatchValueTyped(PreBarrierFunctor<Value>(), v);
     }
 
-    static void postBarrier(Value* vp, const Value& prev, const Value& next) {
+    static void postBarrier(Value* vp) {
         MOZ_ASSERT(!CurrentThreadIsIonCompiling());
-        MOZ_ASSERT(vp);
+        if (vp->isObject()) {
+            gc::StoreBuffer* sb = reinterpret_cast<gc::Cell*>(&vp->toObject())->storeBuffer();
+            if (sb)
+                sb->putValueFromAnyThread(vp);
+        }
+    }
 
-        // If the target needs an entry, add it.
-        js::gc::StoreBuffer* sb;
-        if (next.isObject() && (sb = reinterpret_cast<gc::Cell*>(&next.toObject())->storeBuffer())) {
-            // If we know that the prev has already inserted an entry, we can skip
-            // doing the lookup to add the new entry.
-            if (prev.isObject() && reinterpret_cast<gc::Cell*>(&prev.toObject())->storeBuffer()) {
-                sb->assertHasValueEdge(vp);
-                return;
-            }
-            sb->putValueFromAnyThread(vp);
-            return;
+    static void postBarrierRelocate(Value* vp) {
+        MOZ_ASSERT(!CurrentThreadIsIonCompiling());
+        if (vp->isObject()) {
+            gc::StoreBuffer* sb = reinterpret_cast<gc::Cell*>(&vp->toObject())->storeBuffer();
+            if (sb)
+                sb->putRelocatableValueFromAnyThread(vp);
         }
-        // Remove the prev entry if the new value does not need it.
-        if (prev.isObject() && (sb = reinterpret_cast<gc::Cell*>(&prev.toObject())->storeBuffer()))
-            sb->unputValueFromAnyThread(vp);
+    }
+
+    static void postBarrierRemove(Value* vp) {
+        MOZ_ASSERT(vp);
+        MOZ_ASSERT(vp->isMarkable());
+        MOZ_ASSERT(!CurrentThreadIsIonCompiling());
+        JSRuntime* rt = static_cast<js::gc::Cell*>(vp->toGCThing())->runtimeFromAnyThread();
+        JS::shadow::Runtime* shadowRuntime = JS::shadow::Runtime::asShadowRuntime(rt);
+        shadowRuntime->gcStoreBufferPtr()->removeRelocatableValueFromAnyThread(vp);
     }
 
     static void readBarrier(const Value& v) {
         DispatchValueTyped(ReadBarrierFunctor<Value>(), v);
     }
 };
 
 template <>
 struct InternalGCMethods<jsid>
 {
     static bool isMarkable(jsid id) { return JSID_IS_STRING(id) || JSID_IS_SYMBOL(id); }
 
     static void preBarrier(jsid id) { DispatchIdTyped(PreBarrierFunctor<jsid>(), id); }
-    static void postBarrier(jsid* idp, jsid prev, jsid next) {}
+    static void postBarrier(jsid* idp) {}
+    static void postBarrierRelocate(jsid* idp) {}
+    static void postBarrierRemove(jsid* idp) {}
 };
 
 template <typename T>
 class BarrieredBaseMixins {};
 
 /*
  * Base class for barriered pointer types.
  */
@@ -326,35 +336,36 @@ class BarrieredBase : public BarrieredBa
      * Obviously this is dangerous unless you know the barrier is not needed.
      */
     T* unsafeGet() { return &value; }
     const T* unsafeGet() const { return &value; }
     void unsafeSet(T v) { value = v; }
 
     /* For users who need to manually barrier the raw types. */
     static void writeBarrierPre(const T& v) { InternalGCMethods<T>::preBarrier(v); }
+    static void writeBarrierPost(const T& v, T* vp) { InternalGCMethods<T>::postBarrier(vp); }
 
   protected:
     void pre() { InternalGCMethods<T>::preBarrier(value); }
 };
 
 template <>
 class BarrieredBaseMixins<JS::Value> : public ValueOperations<BarrieredBase<JS::Value> >
 {
     friend class ValueOperations<BarrieredBase<JS::Value> >;
     const JS::Value * extract() const {
         return static_cast<const BarrieredBase<JS::Value>*>(this)->unsafeGet();
     }
 };
 
 /*
- * PreBarriered only automatically handles pre-barriers. Post-barriers must be
- * manually implemented when using this class. HeapPtr should be used in all
- * cases that do not require explicit low-level control of moving behavior,
- * e.g. for HashMap keys.
+ * PreBarriered only automatically handles pre-barriers. Post-barriers must
+ * be manually implemented when using this class. HeapPtr and RelocatablePtr
+ * should be used in all cases that do not require explicit low-level control
+ * of moving behavior, e.g. for HashMap keys.
  */
 template <class T>
 class PreBarriered : public BarrieredBase<T>
 {
   public:
     PreBarriered() : BarrieredBase<T>(GCMethods<T>::initial()) {}
     /*
      * Allow implicit construction for use in generic contexts, such as DebuggerWeakMap::markKeys.
@@ -377,90 +388,185 @@ class PreBarriered : public BarrieredBas
         this->pre();
         this->value = v;
     }
 };
 
 /*
  * A pre- and post-barriered heap pointer, for use inside the JS engine.
  *
- * Use of this class makes a heap-stored pointer safe for both incremental and
- * generational GC. Note, however, that this class does *not* create a new
- * root: HeapPtr references must still be traced like any other pointer in the
- * system.
+ * It must only be stored in memory that has GC lifetime. HeapPtr must not be
+ * used in contexts where it may be implicitly moved or deleted, e.g. most
+ * containers.
  *
- * This class should not to be confused with JS::Heap<T>. This is a different
- * class from the external interface and implements substantially different
- * semantics. Specifically, it implements pre-barriers, which the external
- * interface does not.
+ * Not to be confused with JS::Heap<T>. This is a different class from the
+ * external interface and implements substantially different semantics.
+ *
+ * The post-barriers implemented by this class are faster than those
+ * implemented by RelocatablePtr<T> or JS::Heap<T> at the cost of not
+ * automatically handling deletion or movement.
  */
 template <class T>
 class HeapPtr : public BarrieredBase<T>
 {
   public:
     HeapPtr() : BarrieredBase<T>(GCMethods<T>::initial()) {}
-    explicit HeapPtr(T v) : BarrieredBase<T>(v) {
-        post(GCMethods<T>::initial(), this->value);
+    explicit HeapPtr(T v) : BarrieredBase<T>(v) { post(); }
+    explicit HeapPtr(const HeapPtr<T>& v) : BarrieredBase<T>(v) { post(); }
+#ifdef DEBUG
+    ~HeapPtr() {
+        // No prebarrier necessary as this only happens when we are sweeping or
+        // before the containing obect becomes part of the GC graph.
+        MOZ_ASSERT(CurrentThreadIsGCSweeping() || CurrentThreadIsHandlingInitFailure());
+    }
+#endif
+
+    void init(T v) {
+        this->value = v;
+        post();
+    }
+
+    DECLARE_POINTER_ASSIGN_OPS(HeapPtr, T);
+
+  protected:
+    void post() { InternalGCMethods<T>::postBarrier(&this->value); }
+
+  private:
+    void set(const T& v) {
+        this->pre();
+        this->value = v;
+        post();
     }
 
     /*
-     * For HeapPtr, move semantics are equivalent to copy semantics. In
+     * Unlike RelocatablePtr<T>, HeapPtr<T> must be managed with GC lifetimes.
+     * Specifically, the memory used by the pointer itself must be live until
+     * at least the next minor GC. For that reason, move semantics are invalid
+     * and are deleted here. Please note that not all containers support move
+     * semantics, so this does not completely prevent invalid uses.
+     */
+    HeapPtr(HeapPtr<T>&&) = delete;
+    HeapPtr<T>& operator=(HeapPtr<T>&&) = delete;
+};
+
+/*
+ * ImmutableTenuredPtr is designed for one very narrow case: replacing
+ * immutable raw pointers to GC-managed things, implicitly converting to a
+ * handle type for ease of use. Pointers encapsulated by this type must:
+ *
+ *   be immutable (no incremental write barriers),
+ *   never point into the nursery (no generational write barriers), and
+ *   be traced via MarkRuntime (we use fromMarkedLocation).
+ *
+ * In short: you *really* need to know what you're doing before you use this
+ * class!
+ */
+template <typename T>
+class ImmutableTenuredPtr
+{
+    T value;
+
+  public:
+    operator T() const { return value; }
+    T operator->() const { return value; }
+
+    operator Handle<T>() const {
+        return Handle<T>::fromMarkedLocation(&value);
+    }
+
+    void init(T ptr) {
+        MOZ_ASSERT(ptr->isTenured());
+        value = ptr;
+    }
+
+    T get() const { return value; }
+    const T* address() { return &value; }
+};
+
+/*
+ * A pre- and post-barriered heap pointer, for use inside the JS engine.
+ *
+ * Unlike HeapPtr<T>, it can be used in memory that is not managed by the GC,
+ * i.e. in C++ containers.  It is, however, somewhat slower, so should only be
+ * used in contexts where this ability is necessary.
+ */
+template <class T>
+class RelocatablePtr : public BarrieredBase<T>
+{
+  public:
+    RelocatablePtr() : BarrieredBase<T>(GCMethods<T>::initial()) {}
+    explicit RelocatablePtr(T v) : BarrieredBase<T>(v) {
+        if (GCMethods<T>::needsPostBarrier(v))
+            post();
+    }
+
+    /*
+     * For RelocatablePtr, move semantics are equivalent to copy semantics. In
      * C++, a copy constructor taking const-ref is the way to get a single
      * function that will be used for both lvalue and rvalue copies, so we can
      * simply omit the rvalue variant.
      */
-    HeapPtr(const HeapPtr<T>& v) : BarrieredBase<T>(v) {
-        post(GCMethods<T>::initial(), this->value);
+    RelocatablePtr(const RelocatablePtr<T>& v) : BarrieredBase<T>(v) {
+        if (GCMethods<T>::needsPostBarrier(this->value))
+            post();
     }
 
-    ~HeapPtr() {
+    ~RelocatablePtr() {
         this->pre();
-        post(this->value, GCMethods<T>::initial());
+        if (GCMethods<T>::needsPostBarrier(this->value))
+            relocate();
     }
 
-    void init(T v) {
-        this->value = v;
-        post(GCMethods<T>::initial(), this->value);
-    }
-
-    DECLARE_POINTER_ASSIGN_OPS(HeapPtr, T);
+    DECLARE_POINTER_ASSIGN_OPS(RelocatablePtr, T);
 
     /* Make this friend so it can access pre() and post(). */
     template <class T1, class T2>
     friend inline void
     BarrieredSetPair(Zone* zone,
-                     HeapPtr<T1*>& v1, T1* val1,
-                     HeapPtr<T2*>& v2, T2* val2);
+                     RelocatablePtr<T1*>& v1, T1* val1,
+                     RelocatablePtr<T2*>& v2, T2* val2);
 
   protected:
     void set(const T& v) {
         this->pre();
         postBarrieredSet(v);
     }
 
     void postBarrieredSet(const T& v) {
-        T tmp = this->value;
-        this->value = v;
-        post(tmp, this->value);
+        if (GCMethods<T>::needsPostBarrier(v)) {
+            this->value = v;
+            post();
+        } else if (GCMethods<T>::needsPostBarrier(this->value)) {
+            relocate();
+            this->value = v;
+        } else {
+            this->value = v;
+        }
     }
 
-    void post(T prev, T next) {
-        InternalGCMethods<T>::postBarrier(&this->value, prev, next);
+    void post() {
+        MOZ_ASSERT(GCMethods<T>::needsPostBarrier(this->value));
+        InternalGCMethods<T>::postBarrierRelocate(&this->value);
+    }
+
+    void relocate() {
+        MOZ_ASSERT(GCMethods<T>::needsPostBarrier(this->value));
+        InternalGCMethods<T>::postBarrierRemove(&this->value);
     }
 };
 
 /*
  * This is a hack for RegExpStatics::updateFromMatch. It allows us to do two
  * barriers with only one branch to check if we're in an incremental GC.
  */
 template <class T1, class T2>
 static inline void
 BarrieredSetPair(Zone* zone,
-                 HeapPtr<T1*>& v1, T1* val1,
-                 HeapPtr<T2*>& v2, T2* val2)
+                 RelocatablePtr<T1*>& v1, T1* val1,
+                 RelocatablePtr<T2*>& v2, T2* val2)
 {
     if (T1::needWriteBarrierPre(zone)) {
         v1.pre();
         v2.pre();
     }
     v1.postBarrieredSet(val1);
     v2.postBarrieredSet(val2);
 }
@@ -548,50 +654,16 @@ struct ReadBarrieredHasher
     static bool match(const Key& k, Lookup l) { return k.get() == l; }
     static void rekey(Key& k, const Key& newKey) { k.set(newKey); }
 };
 
 /* Specialized hashing policy for ReadBarriereds. */
 template <class T>
 struct DefaultHasher<ReadBarriered<T>> : ReadBarrieredHasher<T> { };
 
-/*
- * ImmutableTenuredPtr is designed for one very narrow case: replacing
- * immutable raw pointers to GC-managed things, implicitly converting to a
- * handle type for ease of use. Pointers encapsulated by this type must:
- *
- *   be immutable (no incremental write barriers),
- *   never point into the nursery (no generational write barriers), and
- *   be traced via MarkRuntime (we use fromMarkedLocation).
- *
- * In short: you *really* need to know what you're doing before you use this
- * class!
- */
-template <typename T>
-class ImmutableTenuredPtr
-{
-    T value;
-
-  public:
-    operator T() const { return value; }
-    T operator->() const { return value; }
-
-    operator Handle<T>() const {
-        return Handle<T>::fromMarkedLocation(&value);
-    }
-
-    void init(T ptr) {
-        MOZ_ASSERT(ptr->isTenured());
-        value = ptr;
-    }
-
-    T get() const { return value; }
-    const T* address() { return &value; }
-};
-
 class ArrayObject;
 class ArrayBufferObject;
 class NestedScopeObject;
 class DebugScopeObject;
 class GlobalObject;
 class ScriptSourceObject;
 class Shape;
 class BaseShape;
@@ -601,16 +673,30 @@ class JitCode;
 }
 
 typedef PreBarriered<JSObject*> PreBarrieredObject;
 typedef PreBarriered<JSScript*> PreBarrieredScript;
 typedef PreBarriered<jit::JitCode*> PreBarrieredJitCode;
 typedef PreBarriered<JSString*> PreBarrieredString;
 typedef PreBarriered<JSAtom*> PreBarrieredAtom;
 
+typedef RelocatablePtr<JSObject*> RelocatablePtrObject;
+typedef RelocatablePtr<JSFunction*> RelocatablePtrFunction;
+typedef RelocatablePtr<PlainObject*> RelocatablePtrPlainObject;
+typedef RelocatablePtr<JSScript*> RelocatablePtrScript;
+typedef RelocatablePtr<NativeObject*> RelocatablePtrNativeObject;
+typedef RelocatablePtr<NestedScopeObject*> RelocatablePtrNestedScopeObject;
+typedef RelocatablePtr<Shape*> RelocatablePtrShape;
+typedef RelocatablePtr<ObjectGroup*> RelocatablePtrObjectGroup;
+typedef RelocatablePtr<jit::JitCode*> RelocatablePtrJitCode;
+typedef RelocatablePtr<JSLinearString*> RelocatablePtrLinearString;
+typedef RelocatablePtr<JSString*> RelocatablePtrString;
+typedef RelocatablePtr<JSAtom*> RelocatablePtrAtom;
+typedef RelocatablePtr<ArrayBufferObjectMaybeShared*> RelocatablePtrArrayBufferObjectMaybeShared;
+
 typedef HeapPtr<NativeObject*> HeapPtrNativeObject;
 typedef HeapPtr<ArrayObject*> HeapPtrArrayObject;
 typedef HeapPtr<ArrayBufferObjectMaybeShared*> HeapPtrArrayBufferObjectMaybeShared;
 typedef HeapPtr<ArrayBufferObject*> HeapPtrArrayBufferObject;
 typedef HeapPtr<BaseShape*> HeapPtrBaseShape;
 typedef HeapPtr<JSAtom*> HeapPtrAtom;
 typedef HeapPtr<JSFlatString*> HeapPtrFlatString;
 typedef HeapPtr<JSFunction*> HeapPtrFunction;
@@ -621,19 +707,21 @@ typedef HeapPtr<JSString*> HeapPtrString
 typedef HeapPtr<PlainObject*> HeapPtrPlainObject;
 typedef HeapPtr<PropertyName*> HeapPtrPropertyName;
 typedef HeapPtr<Shape*> HeapPtrShape;
 typedef HeapPtr<UnownedBaseShape*> HeapPtrUnownedBaseShape;
 typedef HeapPtr<jit::JitCode*> HeapPtrJitCode;
 typedef HeapPtr<ObjectGroup*> HeapPtrObjectGroup;
 
 typedef PreBarriered<Value> PreBarrieredValue;
+typedef RelocatablePtr<Value> RelocatableValue;
 typedef HeapPtr<Value> HeapValue;
 
 typedef PreBarriered<jsid> PreBarrieredId;
+typedef RelocatablePtr<jsid> RelocatableId;
 typedef HeapPtr<jsid> HeapId;
 
 typedef ImmutableTenuredPtr<PropertyName*> ImmutablePropertyNamePtr;
 typedef ImmutableTenuredPtr<JS::Symbol*> ImmutableSymbolPtr;
 
 typedef ReadBarriered<DebugScopeObject*> ReadBarrieredDebugScopeObject;
 typedef ReadBarriered<GlobalObject*> ReadBarrieredGlobalObject;
 typedef ReadBarriered<JSFunction*> ReadBarrieredFunction;
@@ -646,17 +734,17 @@ typedef ReadBarriered<jit::JitCode*> Rea
 typedef ReadBarriered<ObjectGroup*> ReadBarrieredObjectGroup;
 typedef ReadBarriered<JSAtom*> ReadBarrieredAtom;
 typedef ReadBarriered<JS::Symbol*> ReadBarrieredSymbol;
 
 typedef ReadBarriered<Value> ReadBarrieredValue;
 
 // A pre- and post-barriered Value that is specialized to be aware that it
 // resides in a slots or elements vector. This allows it to be relocated in
-// memory, but with substantially less overhead than a HeapPtr.
+// memory, but with substantially less overhead than a RelocatablePtr.
 class HeapSlot : public BarrieredBase<Value>
 {
   public:
     enum Kind {
         Slot = 0,
         Element = 1
     };
 
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -280,18 +280,19 @@ class TenuredCell : public Cell
     }
     MOZ_ALWAYS_INLINE JS::shadow::Zone* shadowZoneFromAnyThread() const {
         return JS::shadow::Zone::asShadowZone(zoneFromAnyThread());
     }
 
     static MOZ_ALWAYS_INLINE void readBarrier(TenuredCell* thing);
     static MOZ_ALWAYS_INLINE void writeBarrierPre(TenuredCell* thing);
 
-    static MOZ_ALWAYS_INLINE void writeBarrierPost(void* cellp, TenuredCell* prior,
-                                                   TenuredCell* next);
+    static MOZ_ALWAYS_INLINE void writeBarrierPost(TenuredCell* thing, void* cellp);
+    static MOZ_ALWAYS_INLINE void writeBarrierPostRelocate(TenuredCell* thing, void* cellp);
+    static MOZ_ALWAYS_INLINE void writeBarrierPostRemove(TenuredCell* thing, void* cellp);
 
 #ifdef DEBUG
     inline bool isAligned() const;
 #endif
 };
 
 /*
  * The mark bitmap has one bit per each GC cell. For multi-cell GC things this
@@ -1464,19 +1465,31 @@ TenuredCell::writeBarrierPre(TenuredCell
 static MOZ_ALWAYS_INLINE void
 AssertValidToSkipBarrier(TenuredCell* thing)
 {
     MOZ_ASSERT(!IsInsideNursery(thing));
     MOZ_ASSERT_IF(thing, MapAllocToTraceKind(thing->getAllocKind()) != JS::TraceKind::Object);
 }
 
 /* static */ MOZ_ALWAYS_INLINE void
-TenuredCell::writeBarrierPost(void* cellp, TenuredCell* prior, TenuredCell* next)
+TenuredCell::writeBarrierPost(TenuredCell* thing, void* cellp)
+{
+    AssertValidToSkipBarrier(thing);
+}
+
+/* static */ MOZ_ALWAYS_INLINE void
+TenuredCell::writeBarrierPostRelocate(TenuredCell* thing, void* cellp)
 {
-    AssertValidToSkipBarrier(next);
+    AssertValidToSkipBarrier(thing);
+}
+
+/* static */ MOZ_ALWAYS_INLINE void
+TenuredCell::writeBarrierPostRemove(TenuredCell* thing, void* cellp)
+{
+    AssertValidToSkipBarrier(thing);
 }
 
 #ifdef DEBUG
 bool
 Cell::isAligned() const
 {
     if (!isTenured())
         return true;
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1744,16 +1744,17 @@ template <> void js::TenuringTracer::tra
 
 template <typename T>
 void
 js::gc::StoreBuffer::MonoTypeBuffer<T>::trace(StoreBuffer* owner, TenuringTracer& mover)
 {
     mozilla::ReentrancyGuard g(*owner);
     MOZ_ASSERT(owner->isEnabled());
     MOZ_ASSERT(stores_.initialized());
+    sinkStores(owner);
     for (typename StoreSet::Range r = stores_.all(); !r.empty(); r.popFront())
         r.front().trace(mover);
 }
 
 namespace js {
 namespace gc {
 template void
 StoreBuffer::MonoTypeBuffer<StoreBuffer::WholeCellEdges>::trace(StoreBuffer*, TenuringTracer&);
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -440,16 +440,24 @@ js::Nursery::collect(JSRuntime* rt, JS::
     TIME_START(traceSlots);
     sb.traceSlots(mover);
     TIME_END(traceSlots);
 
     TIME_START(traceWholeCells);
     sb.traceWholeCells(mover);
     TIME_END(traceWholeCells);
 
+    TIME_START(traceRelocatableValues);
+    sb.traceRelocatableValues(mover);
+    TIME_END(traceRelocatableValues);
+
+    TIME_START(traceRelocatableCells);
+    sb.traceRelocatableCells(mover);
+    TIME_END(traceRelocatableCells);
+
     TIME_START(traceGenericEntries);
     sb.traceGenericEntries(&mover);
     TIME_END(traceGenericEntries);
 
     TIME_START(markRuntime);
     rt->gc.markRuntime(&mover);
     TIME_END(markRuntime);
 
@@ -544,32 +552,34 @@ js::Nursery::collect(JSRuntime* rt, JS::
         rt->addTelemetry(JS_TELEMETRY_GC_MINOR_REASON_LONG, reason);
 
     TraceMinorGCEnd();
 
     if (enableProfiling_ && totalTime >= profileThreshold_) {
         static bool printedHeader = false;
         if (!printedHeader) {
             fprintf(stderr,
-                    "MinorGC: Reason               PRate  Size Time   mkVals mkClls mkSlts mkWCll mkGnrc ckTbls mkRntm mkDbgr clrNOC collct swpABO updtIn runFin frSlts clrSB  sweep resize pretnr\n");
+                    "MinorGC: Reason               PRate  Size Time   mkVals mkClls mkSlts mkWCll mkRVal mkRCll mkGnrc ckTbls mkRntm mkDbgr clrNOC collct swpABO updtIn runFin frSlts clrSB  sweep resize pretnr\n");
             printedHeader = true;
         }
 
 #define FMT " %6" PRIu64
         fprintf(stderr,
-                "MinorGC: %20s %5.1f%% %4d" FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT "\n",
+                "MinorGC: %20s %5.1f%% %4d" FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT "\n",
                 js::gcstats::ExplainReason(reason),
                 promotionRate * 100,
                 numActiveChunks_,
                 totalTime,
                 TIME_TOTAL(cancelIonCompilations),
                 TIME_TOTAL(traceValues),
                 TIME_TOTAL(traceCells),
                 TIME_TOTAL(traceSlots),
                 TIME_TOTAL(traceWholeCells),
+                TIME_TOTAL(traceRelocatableValues),
+                TIME_TOTAL(traceRelocatableCells),
                 TIME_TOTAL(traceGenericEntries),
                 TIME_TOTAL(checkHashTables),
                 TIME_TOTAL(markRuntime),
                 TIME_TOTAL(markDebugger),
                 TIME_TOTAL(clearNewObjectCache),
                 TIME_TOTAL(collectToFP),
                 TIME_TOTAL(sweepArrayBufferViewList),
                 TIME_TOTAL(updateJitActivations),
--- a/js/src/gc/StoreBuffer.cpp
+++ b/js/src/gc/StoreBuffer.cpp
@@ -39,16 +39,18 @@ StoreBuffer::enable()
 {
     if (enabled_)
         return true;
 
     if (!bufferVal.init() ||
         !bufferCell.init() ||
         !bufferSlot.init() ||
         !bufferWholeCell.init() ||
+        !bufferRelocVal.init() ||
+        !bufferRelocCell.init() ||
         !bufferGeneric.init())
     {
         return false;
     }
 
     enabled_ = true;
     return true;
 }
@@ -72,16 +74,18 @@ StoreBuffer::clear()
 
     aboutToOverflow_ = false;
     cancelIonCompilations_ = false;
 
     bufferVal.clear();
     bufferCell.clear();
     bufferSlot.clear();
     bufferWholeCell.clear();
+    bufferRelocVal.clear();
+    bufferRelocCell.clear();
     bufferGeneric.clear();
 
     return true;
 }
 
 void
 StoreBuffer::setAboutToOverflow()
 {
@@ -95,15 +99,17 @@ StoreBuffer::setAboutToOverflow()
 void
 StoreBuffer::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::GCSizes
 *sizes)
 {
     sizes->storeBufferVals       += bufferVal.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferCells      += bufferCell.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferSlots      += bufferSlot.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferWholeCells += bufferWholeCell.sizeOfExcludingThis(mallocSizeOf);
+    sizes->storeBufferRelocVals  += bufferRelocVal.sizeOfExcludingThis(mallocSizeOf);
+    sizes->storeBufferRelocCells += bufferRelocCell.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferGenerics   += bufferGeneric.sizeOfExcludingThis(mallocSizeOf);
 }
 
 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::ValueEdge>;
 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::CellPtrEdge>;
 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::SlotsEdge>;
 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::WholeCellEdges>;
--- a/js/src/gc/StoreBuffer.h
+++ b/js/src/gc/StoreBuffer.h
@@ -28,16 +28,18 @@ namespace gc {
  */
 class BufferableRef
 {
   public:
     virtual void trace(JSTracer* trc) = 0;
     bool maybeInRememberedSet(const Nursery&) const { return true; }
 };
 
+typedef HashSet<void*, PointerHasher<void*, 3>, SystemAllocPolicy> EdgeSet;
+
 /* The size of a single block of store buffer storage space. */
 static const size_t LifoAllocBlockSize = 1 << 16; /* 64KiB */
 
 /*
  * The StoreBuffer observes all writes that occur in the system and performs
  * efficient filtering of them to derive a remembered set for nursery GC.
  */
 class StoreBuffer
@@ -50,56 +52,80 @@ class StoreBuffer
     /*
      * This buffer holds only a single type of edge. Using this buffer is more
      * efficient than the generic buffer when many writes will be to the same
      * type of edge: e.g. Value or Cell*.
      */
     template<typename T>
     struct MonoTypeBuffer
     {
-        /* The set of stores. */
+        /* The canonical set of stores. */
         typedef HashSet<T, typename T::Hasher, SystemAllocPolicy> StoreSet;
         StoreSet stores_;
 
+        /*
+         * A small, fixed-size buffer in front of the canonical set to simplify
+         * insertion via jit code.
+         */
+        const static size_t NumBufferEntries = 4096 / sizeof(T);
+        T buffer_[NumBufferEntries];
+        T* insert_;
+
         /* Maximum number of entries before we request a minor GC. */
         const static size_t MaxEntries = 48 * 1024 / sizeof(T);
 
-        MonoTypeBuffer() {}
+        explicit MonoTypeBuffer() { clearBuffer(); }
         ~MonoTypeBuffer() { stores_.finish(); }
 
         bool init() {
             if (!stores_.initialized() && !stores_.init())
                 return false;
             clear();
             return true;
         }
 
+        void clearBuffer() {
+            JS_POISON(buffer_, JS_EMPTY_STOREBUFFER_PATTERN, NumBufferEntries * sizeof(T));
+            insert_ = buffer_;
+        }
+
         void clear() {
+            clearBuffer();
             if (stores_.initialized())
                 stores_.clear();
         }
 
         /* Add one item to the buffer. */
         void put(StoreBuffer* owner, const T& t) {
             MOZ_ASSERT(stores_.initialized());
-            if (MOZ_UNLIKELY(!stores_.put(t)))
-                CrashAtUnhandlableOOM("Failed to allocate for MonoTypeBuffer.");
+            *insert_++ = t;
+            if (MOZ_UNLIKELY(insert_ == buffer_ + NumBufferEntries))
+                sinkStores(owner);
+        }
+
+        /* Move any buffered stores to the canonical store set. */
+        void sinkStores(StoreBuffer* owner) {
+            MOZ_ASSERT(stores_.initialized());
+
+            for (T* p = buffer_; p < insert_; ++p) {
+                if (!stores_.put(*p))
+                    CrashAtUnhandlableOOM("Failed to allocate for MonoTypeBuffer::sinkStores.");
+            }
+            clearBuffer();
+
             if (MOZ_UNLIKELY(stores_.count() > MaxEntries))
                 owner->setAboutToOverflow();
         }
 
         /* Remove an item from the store buffer. */
         void unput(StoreBuffer* owner, const T& v) {
+            sinkStores(owner);
             stores_.remove(v);
         }
 
-        bool has(const T& v) const {
-            return stores_.has(v);
-        }
-
         /* Trace the source of all edges in the store buffer. */
         void trace(StoreBuffer* owner, TenuringTracer& mover);
 
         size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
             return stores_.sizeOfExcludingThis(mallocSizeOf);
         }
 
       private:
@@ -350,84 +376,105 @@ class StoreBuffer
         if (edge.maybeInRememberedSet(nursery_))
             buffer.put(this, edge);
     }
 
     MonoTypeBuffer<ValueEdge> bufferVal;
     MonoTypeBuffer<CellPtrEdge> bufferCell;
     MonoTypeBuffer<SlotsEdge> bufferSlot;
     MonoTypeBuffer<WholeCellEdges> bufferWholeCell;
+    MonoTypeBuffer<ValueEdge> bufferRelocVal;
+    MonoTypeBuffer<CellPtrEdge> bufferRelocCell;
     GenericBuffer bufferGeneric;
     bool cancelIonCompilations_;
 
     JSRuntime* runtime_;
     const Nursery& nursery_;
 
     bool aboutToOverflow_;
     bool enabled_;
     mozilla::DebugOnly<bool> mEntered; /* For ReentrancyGuard. */
 
   public:
     explicit StoreBuffer(JSRuntime* rt, const Nursery& nursery)
-      : bufferVal(), bufferCell(), bufferSlot(), bufferWholeCell(), bufferGeneric(),
-        cancelIonCompilations_(false), runtime_(rt), nursery_(nursery), aboutToOverflow_(false),
-        enabled_(false), mEntered(false)
+      : bufferVal(), bufferCell(), bufferSlot(), bufferWholeCell(),
+        bufferRelocVal(), bufferRelocCell(), bufferGeneric(), cancelIonCompilations_(false),
+        runtime_(rt), nursery_(nursery), aboutToOverflow_(false), enabled_(false),
+        mEntered(false)
     {
     }
 
     bool enable();
     void disable();
     bool isEnabled() const { return enabled_; }
 
     bool clear();
 
     /* Get the overflowed status. */
     bool isAboutToOverflow() const { return aboutToOverflow_; }
 
     bool cancelIonCompilations() const { return cancelIonCompilations_; }
 
     /* Insert a single edge into the buffer/remembered set. */
-    void putValueFromAnyThread(JS::Value* vp) { putFromAnyThread(bufferVal, ValueEdge(vp)); }
-    void unputValueFromAnyThread(JS::Value* vp) { unputFromAnyThread(bufferVal, ValueEdge(vp)); }
+    void putValueFromAnyThread(JS::Value* valuep) { putFromAnyThread(bufferVal, ValueEdge(valuep)); }
     void putCellFromAnyThread(Cell** cellp) { putFromAnyThread(bufferCell, CellPtrEdge(cellp)); }
-    void unputCellFromAnyThread(Cell** cellp) { unputFromAnyThread(bufferCell, CellPtrEdge(cellp)); }
     void putSlotFromAnyThread(NativeObject* obj, int kind, int32_t start, int32_t count) {
         putFromAnyThread(bufferSlot, SlotsEdge(obj, kind, start, count));
     }
     void putWholeCellFromMainThread(Cell* cell) {
         MOZ_ASSERT(cell->isTenured());
         putFromMainThread(bufferWholeCell, WholeCellEdges(cell));
     }
 
+    /* Insert or update a single edge in the Relocatable buffer. */
+    void putRelocatableValueFromAnyThread(JS::Value* valuep) {
+        putFromAnyThread(bufferRelocVal, ValueEdge(valuep));
+    }
+    void removeRelocatableValueFromAnyThread(JS::Value* valuep) {
+        unputFromAnyThread(bufferRelocVal, ValueEdge(valuep));
+    }
+    void putRelocatableCellFromAnyThread(Cell** cellp) {
+        putFromAnyThread(bufferRelocCell, CellPtrEdge(cellp));
+    }
+    void removeRelocatableCellFromAnyThread(Cell** cellp) {
+        unputFromAnyThread(bufferRelocCell, CellPtrEdge(cellp));
+    }
+
     /* Insert an entry into the generic buffer. */
     template <typename T>
     void putGeneric(const T& t) { putFromAnyThread(bufferGeneric, t);}
 
     /* Insert or update a callback entry. */
     template <typename Key>
     void putCallback(void (*callback)(JSTracer* trc, Key* key, void* data), Key* key, void* data) {
         putFromAnyThread(bufferGeneric, CallbackRef<Key>(callback, key, data));
     }
 
-    void assertHasCellEdge(Cell** cellp) const { MOZ_ASSERT(bufferCell.has(CellPtrEdge(cellp))); }
-    void assertHasValueEdge(Value* vp) const { MOZ_ASSERT(bufferVal.has(ValueEdge(vp))); }
-
     void setShouldCancelIonCompilations() {
         cancelIonCompilations_ = true;
     }
 
     /* Methods to trace the source of all edges in the store buffer. */
     void traceValues(TenuringTracer& mover)            { bufferVal.trace(this, mover); }
     void traceCells(TenuringTracer& mover)             { bufferCell.trace(this, mover); }
     void traceSlots(TenuringTracer& mover)             { bufferSlot.trace(this, mover); }
     void traceWholeCells(TenuringTracer& mover)        { bufferWholeCell.trace(this, mover); }
+    void traceRelocatableValues(TenuringTracer& mover) { bufferRelocVal.trace(this, mover); }
+    void traceRelocatableCells(TenuringTracer& mover)  { bufferRelocCell.trace(this, mover); }
     void traceGenericEntries(JSTracer *trc)            { bufferGeneric.trace(this, trc); }
 
     /* For use by our owned buffers and for testing. */
     void setAboutToOverflow();
 
+    /* For jit access to the raw buffer. */
+    void oolSinkStoresForWholeCellBuffer() { bufferWholeCell.sinkStores(this); }
+    void* addressOfWholeCellBufferPointer() const { return (void*)&bufferWholeCell.insert_; }
+    void* addressOfWholeCellBufferEnd() const {
+        return (void*)(bufferWholeCell.buffer_ + bufferWholeCell.NumBufferEntries);
+    }
+
     void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::GCSizes* sizes);
 };
 
 } /* namespace gc */
 } /* namespace js */
 
 #endif /* gc_StoreBuffer_h */
--- a/js/src/gdb/tests/test-Root.cpp
+++ b/js/src/gdb/tests/test-Root.cpp
@@ -36,22 +36,26 @@ FRAGMENT(Root, HeapSlot) {
   (void) plinth;
   (void) array;
 }
 
 FRAGMENT(Root, barriers) {
   JSObject* obj = JS_NewPlainObject(cx);
   js::PreBarriered<JSObject*> prebarriered(obj);
   js::HeapPtr<JSObject*> heapptr(obj);
+  js::RelocatablePtr<JSObject*> relocatable(obj);
 
   JS::Value val = JS::ObjectValue(*obj);
   js::PreBarrieredValue prebarrieredValue(JS::ObjectValue(*obj));
   js::HeapValue heapValue(JS::ObjectValue(*obj));
+  js::RelocatableValue relocatableValue(JS::ObjectValue(*obj));
 
   breakpoint();
 
   (void) prebarriered;
   (void) heapptr;
+  (void) relocatable;
   (void) val;
   (void) prebarrieredValue;
   (void) heapValue;
+  (void) relocatableValue;
 }
 
--- a/js/src/jit/BacktrackingAllocator.cpp
+++ b/js/src/jit/BacktrackingAllocator.cpp
@@ -1396,23 +1396,23 @@ BacktrackingAllocator::tryAllocateRegist
     if (!aliasedConflicting.empty()) {
         // One or more aliased registers is allocated to another bundle
         // overlapping this one. Keep track of the conflicting set, and in the
         // case of multiple conflicting sets keep track of the set with the
         // lowest maximum spill weight.
 
         if (JitSpewEnabled(JitSpew_RegAlloc)) {
             if (aliasedConflicting.length() == 1) {
-                LiveBundle* existing = aliasedConflicting[0];
+                mozilla::DebugOnly<LiveBundle*> existing = aliasedConflicting[0];
                 JitSpew(JitSpew_RegAlloc, "  %s collides with %s [weight %lu]",
                         r.reg.name(), existing->toString(), computeSpillWeight(existing));
             } else {
                 JitSpew(JitSpew_RegAlloc, "  %s collides with the following", r.reg.name());
                 for (size_t i = 0; i < aliasedConflicting.length(); i++) {
-                    LiveBundle* existing = aliasedConflicting[i];
+                    mozilla::DebugOnly<LiveBundle*> existing = aliasedConflicting[i];
                     JitSpew(JitSpew_RegAlloc, "      %s [weight %lu]",
                             existing->toString(), computeSpillWeight(existing));
                 }
             }
         }
 
         if (conflicting.empty()) {
             if (!conflicting.appendAll(aliasedConflicting))
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1407,17 +1407,17 @@ jit::BailoutIonToBaseline(JSContext* cx,
     TraceLogStartEvent(logger, TraceLogger_Baseline);
 
     // The caller of the top frame must be one of the following:
     //      IonJS - Ion calling into Ion.
     //      BaselineStub - Baseline calling into Ion.
     //      Entry - Interpreter or other calling into Ion.
     //      Rectifier - Arguments rectifier calling into Ion.
     MOZ_ASSERT(iter.isBailoutJS());
-    FrameType prevFrameType = iter.prevType();
+    mozilla::DebugOnly<FrameType> prevFrameType = iter.prevType();
     MOZ_ASSERT(prevFrameType == JitFrame_IonJS ||
                prevFrameType == JitFrame_BaselineStub ||
                prevFrameType == JitFrame_Entry ||
                prevFrameType == JitFrame_Rectifier ||
                prevFrameType == JitFrame_IonAccessorIC);
 
     // All incoming frames are going to look like this:
     //
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -280,16 +280,17 @@ CollectInterpreterStackScripts(JSContext
         if (obs.shouldRecompileOrInvalidate(script)) {
             if (!entries.append(DebugModeOSREntry(iter.frame()->script())))
                 return false;
         }
     }
     return true;
 }
 
+#ifdef DEBUG
 static const char*
 ICEntryKindToString(ICEntry::Kind kind)
 {
     switch (kind) {
       case ICEntry::Kind_Op:
         return "IC";
       case ICEntry::Kind_NonOp:
         return "non-op IC";
@@ -304,16 +305,17 @@ ICEntryKindToString(ICEntry::Kind kind)
       case ICEntry::Kind_DebugPrologue:
         return "debug prologue";
       case ICEntry::Kind_DebugEpilogue:
         return "debug epilogue";
       default:
         MOZ_CRASH("bad ICEntry kind");
     }
 }
+#endif // DEBUG
 
 static void
 SpewPatchBaselineFrame(uint8_t* oldReturnAddress, uint8_t* newReturnAddress,
                        JSScript* script, ICEntry::Kind frameKind, jsbytecode* pc)
 {
     JitSpew(JitSpew_BaselineDebugModeOSR,
             "Patch return %p -> %p on BaselineJS frame (%s:%d) from %s at %s",
             oldReturnAddress, newReturnAddress, script->filename(), script->lineno(),
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -114,21 +114,21 @@ struct BaselineScript
 
     // Limit the locals on a given script so that stack check on baseline frames
     // doesn't overflow a uint32_t value.
     // (MAX_JSSCRIPT_SLOTS * sizeof(Value)) must fit within a uint32_t.
     static const uint32_t MAX_JSSCRIPT_SLOTS = 0xffffu;
 
   private:
     // Code pointer containing the actual method.
-    HeapPtrJitCode method_;
+    RelocatablePtrJitCode method_;
 
     // For heavyweight scripts, template objects to use for the call object and
     // decl env object (linked via the call object's enclosing scope).
-    HeapPtrObject templateScope_;
+    RelocatablePtrObject templateScope_;
 
     // Allocated space for fallback stubs.
     FallbackICStubSpace fallbackStubSpace_;
 
     // If non-null, the list of AsmJSModules that contain an optimized call
     // directly to this script.
     Vector<DependentAsmJSModuleExit>* dependentAsmJSModules_;
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -5469,17 +5469,17 @@ IonBuilder::inlineCalls(CallInfo& callIn
         if (!inlineBlock)
             return false;
 
         // Create a function MConstant to use in the entry ResumePoint. If we
         // can't use a constant, add a no-op MPolyInlineGuard, to prevent
         // hoisting scope chain gets above the dispatch instruction.
         MInstruction* funcDef;
         if (target->isSingleton())
-            funcDef = MConstant::New(alloc(), ObjectValue(*target), constraints(), this);
+            funcDef = MConstant::New(alloc(), ObjectValue(*target), constraints());
         else
             funcDef = MPolyInlineGuard::New(alloc(), callInfo.fun());
 
         funcDef->setImplicitlyUsedUnchecked();
         dispatchBlock->add(funcDef);
 
         // Use the inlined callee in the inline resume point and on stack.
         int funIndex = inlineBlock->entryResumePoint()->stackDepth() - callInfo.numFormals();
@@ -5784,17 +5784,17 @@ IonBuilder::createThisScriptedSingleton(
         return nullptr;
 
     StackTypeSet* thisTypes = TypeScript::ThisTypes(target->nonLazyScript());
     if (!thisTypes || !thisTypes->hasType(TypeSet::ObjectType(templateObject)))
         return nullptr;
 
     // Generate an inline path to create a new |this| object with
     // the given singleton prototype.
-    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject, this);
+    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     MCreateThisWithTemplate* createThis =
         MCreateThisWithTemplate::New(alloc(), constraints(), templateConst,
                                      templateObject->group()->initialHeap(constraints()));
     current->add(templateConst);
     current->add(createThis);
 
     return createThis;
 }
@@ -5843,17 +5843,17 @@ IonBuilder::createThisScriptedBaseline(M
     current->add(prototype);
     MDefinition* protoConst = constant(ObjectValue(*proto));
     MGuardObjectIdentity* guard = MGuardObjectIdentity::New(alloc(), prototype, protoConst,
                                                             /* bailOnEquality = */ false);
     current->add(guard);
 
     // Generate an inline path to create a new |this| object with
     // the given prototype.
-    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject, this);
+    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     MCreateThisWithTemplate* createThis =
         MCreateThisWithTemplate::New(alloc(), constraints(), templateConst,
                                      templateObject->group()->initialHeap(constraints()));
     current->add(templateConst);
     current->add(createThis);
 
     return createThis;
 }
@@ -6145,19 +6145,16 @@ IonBuilder::jsop_call(uint32_t argc, boo
     }
 
     return makeCall(target, callInfo);
 }
 
 bool
 IonBuilder::testShouldDOMCall(TypeSet* inTypes, JSFunction* func, JSJitInfo::OpType opType)
 {
-    if (IsInsideNursery(func))
-        return false;
-
     if (!func->isNative() || !func->jitInfo())
         return false;
 
     // If all the DOM objects flowing through are legal with this
     // property, we can bake in a call to the bottom half of the DOM
     // accessor
     DOMInstanceClassHasProtoAtDepth instanceChecker =
         compartment->runtime()->DOMcallbacks()->instanceClassMatchesProto;
@@ -6463,17 +6460,17 @@ bool
 IonBuilder::jsop_newarray(uint32_t count)
 {
     JSObject* templateObject = inspector->getTemplateObject(pc);
     gc::InitialHeap heap;
     MConstant* templateConst;
 
     if (templateObject) {
         heap = templateObject->group()->initialHeap(constraints());
-        templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject, this);
+        templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     } else {
         heap = gc::DefaultHeap;
         templateConst = MConstant::New(alloc(), NullValue());
     }
     current->add(templateConst);
 
     MNewArray* ins = MNewArray::New(alloc(), constraints(), count, templateConst, heap, pc);
     current->add(ins);
@@ -6514,17 +6511,17 @@ bool
 IonBuilder::jsop_newobject()
 {
     JSObject* templateObject = inspector->getTemplateObject(pc);
     gc::InitialHeap heap;
     MConstant* templateConst;
 
     if (templateObject) {
         heap = templateObject->group()->initialHeap(constraints());
-        templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject, this);
+        templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     } else {
         heap = gc::DefaultHeap;
         templateConst = MConstant::New(alloc(), NullValue());
     }
 
     current->add(templateConst);
     MNewObject* ins = MNewObject::New(alloc(), constraints(), templateConst, heap,
                                       MNewObject::ObjectLiteral);
@@ -9009,17 +9006,17 @@ IonBuilder::setElemTryDense(bool* emitte
     JSValueType unboxedType = UnboxedArrayElementType(constraints(), object, index);
     if (unboxedType == JSVAL_TYPE_MAGIC) {
         if (!ElementAccessIsDenseNative(constraints(), object, index)) {
             trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
             return true;
         }
     }
 
-    if (PropertyWriteNeedsTypeBarrier(this, constraints(), current,
+    if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
                                       &object, nullptr, &value, /* canModify = */ true))
     {
         trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
         return true;
     }
 
     if (!object->resultTypeSet()) {
         trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
@@ -9089,17 +9086,17 @@ IonBuilder::setElemTryCache(bool* emitte
     // Temporary disable the cache if non dense native,
     // until the cache supports more ics
     SetElemICInspector icInspect(inspector->setElemICInspector(pc));
     if (!icInspect.sawDenseWrite() && !icInspect.sawTypedArrayWrite()) {
         trackOptimizationOutcome(TrackedOutcome::SetElemNonDenseNonTANotCached);
         return true;
     }
 
-    if (PropertyWriteNeedsTypeBarrier(this, constraints(), current,
+    if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
                                       &object, nullptr, &value, /* canModify = */ true))
     {
         trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
         return true;
     }
 
     // We can avoid worrying about holes in the IC if we know a priori we are safe
     // from them. If TI can guard that there are no indexed properties on the prototype
@@ -9427,17 +9424,17 @@ IonBuilder::jsop_rest()
         return true;
     }
 
     // We know the exact number of arguments the callee pushed.
     unsigned numActuals = inlineCallInfo_->argc();
     unsigned numFormals = info().nargs() - 1;
     unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0;
 
-    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject, this);
+    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     current->add(templateConst);
 
     MNewArray* array = MNewArray::New(alloc(), constraints(), numRest, templateConst,
                                       templateObject->group()->initialHeap(constraints()), pc);
     current->add(array);
 
     if (numRest == 0) {
         // No more updating to do. (Note that in this one case the length from
@@ -10807,18 +10804,17 @@ IonBuilder::getPropTryCommonGetter(bool*
           case InliningDecision_Inline:
             if (!inlineScriptedCall(callInfo, commonGetter))
                 return false;
             *emitted = true;
             return true;
         }
     }
 
-    JSFunction* tenuredCommonGetter = IsInsideNursery(commonGetter) ? nullptr : commonGetter;
-    if (!makeCall(tenuredCommonGetter, callInfo))
+    if (!makeCall(commonGetter, callInfo))
         return false;
 
     // If the getter could have been inlined, don't track success. The call to
     // makeInliningDecision above would have tracked a specific reason why we
     // couldn't inline.
     if (!commonGetter->isInterpreted())
         trackOptimizationSuccess();
 
@@ -11178,17 +11174,17 @@ IonBuilder::jsop_setprop(PropertyName* n
 
         // Try to emit stores to known binary data blocks
         trackOptimizationAttempt(TrackedStrategy::SetProp_TypedObject);
         if (!setPropTryTypedObject(&emitted, obj, name, value) || emitted)
             return emitted;
     }
 
     TemporaryTypeSet* objTypes = obj->resultTypeSet();
-    bool barrier = PropertyWriteNeedsTypeBarrier(this, constraints(), current, &obj, name, &value,
+    bool barrier = PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &obj, name, &value,
                                                  /* canModify = */ true);
 
     if (!forceInlineCaches()) {
         // Try to emit stores to unboxed objects.
         trackOptimizationAttempt(TrackedStrategy::SetProp_Unboxed);
         if (!setPropTryUnboxed(&emitted, obj, name, value, barrier, objTypes) || emitted)
             return emitted;
     }
@@ -11302,18 +11298,17 @@ IonBuilder::setPropTryCommonSetter(bool*
           case InliningDecision_Inline:
             if (!inlineScriptedCall(callInfo, commonSetter))
                 return false;
             *emitted = true;
             return true;
         }
     }
 
-    JSFunction* tenuredCommonSetter = IsInsideNursery(commonSetter) ? nullptr : commonSetter;
-    MCall* call = makeCallHelper(tenuredCommonSetter, callInfo);
+    MCall* call = makeCallHelper(commonSetter, callInfo);
     if (!call)
         return false;
 
     current->push(value);
     if (!resumeAfter(call))
         return false;
 
     // If the setter could have been inlined, don't track success. The call to
@@ -11820,17 +11815,17 @@ bool
 IonBuilder::jsop_lambda(JSFunction* fun)
 {
     MOZ_ASSERT(analysis().usesScopeChain());
     MOZ_ASSERT(!fun->isArrow());
 
     if (fun->isNative() && IsAsmJSModuleNative(fun->native()))
         return abort("asm.js module function");
 
-    MConstant* cst = MConstant::NewConstraintlessObject(alloc(), fun, this);
+    MConstant* cst = MConstant::NewConstraintlessObject(alloc(), fun);
     current->add(cst);
     MLambda* ins = MLambda::New(alloc(), constraints(), current->scopeChain(), cst);
     current->add(ins);
     current->push(ins);
 
     return resumeAfter(ins);
 }
 
@@ -12370,27 +12365,26 @@ IonBuilder::jsop_in_dense(JSValueType un
                                   unboxedType);
 
     current->add(ins);
     current->push(ins);
 
     return true;
 }
 
-static bool
-HasOnProtoChain(CompilerConstraintList* constraints, TypeSet::ObjectKey* key,
-                JSObject* protoObject, bool* hasOnProto)
+bool
+IonBuilder::hasOnProtoChain(TypeSet::ObjectKey* key, JSObject* protoObject, bool* hasOnProto)
 {
     MOZ_ASSERT(protoObject);
 
     while (true) {
-        if (!key->hasStableClassAndProto(constraints) || !key->clasp()->isNative())
+        if (!key->hasStableClassAndProto(constraints()) || !key->clasp()->isNative())
             return false;
 
-        JSObject* proto = key->proto().toObjectOrNull();
+        JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull());
         if (!proto) {
             *hasOnProto = false;
             return true;
         }
 
         if (proto == protoObject) {
             *hasOnProto = true;
             return true;
@@ -12424,17 +12418,17 @@ IonBuilder::tryFoldInstanceOf(MDefinitio
     bool knownIsInstance = false;
 
     for (unsigned i = 0; i < lhsTypes->getObjectCount(); i++) {
         TypeSet::ObjectKey* key = lhsTypes->getObject(i);
         if (!key)
             continue;
 
         bool isInstance;
-        if (!HasOnProtoChain(constraints(), key, protoObject, &isInstance))
+        if (!hasOnProtoChain(key, protoObject, &isInstance))
             return false;
 
         if (isFirst) {
             knownIsInstance = isInstance;
             isFirst = false;
         } else if (knownIsInstance != isInstance) {
             // Some of the objects have protoObject on their proto chain and
             // others don't, so we can't optimize this.
@@ -12506,17 +12500,17 @@ IonBuilder::jsop_instanceof()
         rhs = addShapeGuard(rhs, shape, Bailout_ShapeGuard);
 
         // Guard .prototype == protoObject.
         MOZ_ASSERT(shape->numFixedSlots() == 0, "Must be a dynamic slot");
         MSlots* slots = MSlots::New(alloc(), rhs);
         current->add(slots);
         MLoadSlot* prototype = MLoadSlot::New(alloc(), slots, slot);
         current->add(prototype);
-        MConstant* protoConst = MConstant::NewConstraintlessObject(alloc(), protoObject, this);
+        MConstant* protoConst = MConstant::NewConstraintlessObject(alloc(), protoObject);
         current->add(protoConst);
         MGuardObjectIdentity* guard = MGuardObjectIdentity::New(alloc(), prototype, protoConst,
                                                                 /* bailOnEquality = */ false);
         current->add(guard);
 
         if (tryFoldInstanceOf(obj, protoObject))
             return true;
 
@@ -12917,17 +12911,17 @@ IonBuilder::storeReferenceTypedObjectVal
     // Make sure we aren't adding new type information for writes of object and value
     // references.
     if (type != ReferenceTypeDescr::TYPE_STRING) {
         MOZ_ASSERT(type == ReferenceTypeDescr::TYPE_ANY ||
                    type == ReferenceTypeDescr::TYPE_OBJECT);
         MIRType implicitType =
             (type == ReferenceTypeDescr::TYPE_ANY) ? MIRType_Undefined : MIRType_Null;
 
-        if (PropertyWriteNeedsTypeBarrier(this, constraints(), current, &typedObj, name, &value,
+        if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &typedObj, name, &value,
                                           /* canModify = */ true, implicitType))
         {
             trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
             return false;
         }
     }
 
     // Find location within the owner object.
@@ -12986,17 +12980,17 @@ MConstant*
 IonBuilder::constant(const Value& v)
 {
     MOZ_ASSERT(!v.isString() || v.toString()->isAtom(),
                "Handle non-atomized strings outside IonBuilder.");
 
     if (v.isObject())
         checkNurseryObject(&v.toObject());
 
-    MConstant* c = MConstant::New(alloc(), v, constraints(), this);
+    MConstant* c = MConstant::New(alloc(), v, constraints());
     current->add(c);
     return c;
 }
 
 MConstant*
 IonBuilder::constantInt(int32_t i)
 {
     return constant(Int32Value(i));
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -611,16 +611,17 @@ class IonBuilder
 
     bool improveThisTypesForCall();
 
     MDefinition* getCallee();
     MDefinition* getAliasedVar(ScopeCoordinate sc);
     MDefinition* addLexicalCheck(MDefinition* input);
 
     bool tryFoldInstanceOf(MDefinition* lhs, JSObject* protoObject);
+    bool hasOnProtoChain(TypeSet::ObjectKey* key, JSObject* protoObject, bool* hasOnProto);
 
     bool jsop_add(MDefinition* left, MDefinition* right);
     bool jsop_bitnot();
     bool jsop_bitop(JSOp op);
     bool jsop_binary(JSOp op);
     bool jsop_binary(JSOp op, MDefinition* left, MDefinition* right);
     bool jsop_pos();
     bool jsop_neg();
--- a/js/src/jit/JitFrameIterator.h
+++ b/js/src/jit/JitFrameIterator.h
@@ -311,17 +311,17 @@ class JitProfilingFrameIterator
     void* stackAddress() const { return fp(); }
     FrameType frameType() const { MOZ_ASSERT(!done()); return type_; }
     void* returnAddressToFp() const { MOZ_ASSERT(!done()); return returnAddressToFp_; }
 };
 
 class RInstructionResults
 {
     // Vector of results of recover instructions.
-    typedef mozilla::Vector<HeapValue, 1, SystemAllocPolicy> Values;
+    typedef mozilla::Vector<RelocatableValue, 1, SystemAllocPolicy> Values;
     mozilla::UniquePtr<Values, JS::DeletePolicy<Values> > results_;
 
     // The frame pointer is used as a key to check if the current frame already
     // bailed out.
     JitFrameLayout* fp_;
 
     // Record if we tried and succeed at allocating and filling the vector of
     // recover instruction results, if needed.  This flag is needed in order to
@@ -336,17 +336,17 @@ class RInstructionResults
 
     ~RInstructionResults();
 
     bool init(JSContext* cx, uint32_t numResults);
     bool isInitialized() const;
 
     JitFrameLayout* frame() const;
 
-    HeapValue& operator[](size_t index);
+    RelocatableValue& operator[](size_t index);
 
     void trace(JSTracer* trc);
 };
 
 struct MaybeReadFallback
 {
     enum NoGCValue {
         NoGC_UndefinedValue,
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -1740,17 +1740,17 @@ RInstructionResults::isInitialized() con
 
 JitFrameLayout*
 RInstructionResults::frame() const
 {
     MOZ_ASSERT(fp_);
     return fp_;
 }
 
-HeapValue&
+RelocatableValue&
 RInstructionResults::operator [](size_t index)
 {
     return (*results_)[index];
 }
 
 void
 RInstructionResults::trace(JSTracer* trc)
 {
--- a/js/src/jit/JitSpewer.h
+++ b/js/src/jit/JitSpewer.h
@@ -213,22 +213,28 @@ static inline Fprinter& JitSpewPrinter()
 
 class JitSpewIndent
 {
   public:
     explicit JitSpewIndent(JitSpewChannel channel) {}
     ~JitSpewIndent() {}
 };
 
-static inline void JitSpew(JitSpewChannel, const char* fmt, ...)
+// The computation of some of the argument of the spewing functions might be
+// costly, thus we use variaidic macros to ignore any argument of these
+// functions.
+static inline void JitSpewCheckArguments(JitSpewChannel channel, const char* fmt)
 { }
-static inline void JitSpewStart(JitSpewChannel channel, const char* fmt, ...)
-{ }
-static inline void JitSpewCont(JitSpewChannel channel, const char* fmt, ...)
-{ }
+
+#define JitSpewCheckExpandedArgs(channel, fmt, ...) JitSpewCheckArguments(channel, fmt)
+#define JitSpewCheckExpandedArgs_(ArgList) JitSpewCheckExpandedArgs ArgList /* Fix MSVC issue */
+#define JitSpew(...) JitSpewCheckExpandedArgs_((__VA_ARGS__))
+#define JitSpewStart(...) JitSpewCheckExpandedArgs_((__VA_ARGS__))
+#define JitSpewCont(...) JitSpewCheckExpandedArgs_((__VA_ARGS__))
+
 static inline void JitSpewFin(JitSpewChannel channel)
 { }
 
 static inline void JitSpewHeader(JitSpewChannel channel)
 { }
 static inline bool JitSpewEnabled(JitSpewChannel channel)
 { return false; }
 static inline void JitSpewVA(JitSpewChannel channel, const char* fmt, va_list ap)
--- a/js/src/jit/JitcodeMap.cpp
+++ b/js/src/jit/JitcodeMap.cpp
@@ -1290,17 +1290,17 @@ JitcodeRegionEntry::WriteRun(CompactBuff
         WriteDelta(writer, nativeDelta, bytecodeDelta);
 
         // Spew the bytecode in these ranges.
         if (curBytecodeOffset < nextBytecodeOffset) {
             JitSpewStart(JitSpew_Profiling, "      OPS: ");
             uint32_t curBc = curBytecodeOffset;
             while (curBc < nextBytecodeOffset) {
                 jsbytecode* pc = entry[i].tree->script()->offsetToPC(curBc);
-                JSOp op = JSOp(*pc);
+                mozilla::DebugOnly<JSOp> op = JSOp(*pc);
                 JitSpewCont(JitSpew_Profiling, "%s ", js_CodeName[op]);
                 curBc += GetBytecodeLength(pc);
             }
             JitSpewFin(JitSpew_Profiling);
         }
         spewer.spewAndAdvance("      ");
 
         curNativeOffset = nextNativeOffset;
--- a/js/src/jit/LIR.cpp
+++ b/js/src/jit/LIR.cpp
@@ -335,17 +335,16 @@ LNode::printName(GenericPrinter& out)
 bool
 LAllocation::aliases(const LAllocation& other) const
 {
     if (isFloatReg() && other.isFloatReg())
         return toFloatReg()->reg().aliases(other.toFloatReg()->reg());
     return *this == other;
 }
 
-#ifdef DEBUG
 static const char * const TypeChars[] =
 {
     "g",            // GENERAL
     "i",            // INT32
     "o",            // OBJECT
     "s",            // SLOTS
     "f",            // FLOAT32
     "d",            // DOUBLE
@@ -439,17 +438,16 @@ LAllocation::toString() const
         return buf;
       case LAllocation::USE:
         PrintUse(buf, sizeof(buf), toUse());
         return buf;
       default:
         MOZ_CRASH("what?");
     }
 }
-#endif // DEBUG
 
 void
 LAllocation::dump() const
 {
     fprintf(stderr, "%s\n", toString());
 }
 
 void
--- a/js/src/jit/LIR.h
+++ b/js/src/jit/LIR.h
@@ -178,21 +178,17 @@ class LAllocation : public TempObject
     bool operator !=(const LAllocation& other) const {
         return bits_ != other.bits_;
     }
 
     HashNumber hash() const {
         return bits_;
     }
 
-#ifdef DEBUG
     const char* toString() const;
-#else
-    const char* toString() const { return "???"; }
-#endif
     bool aliases(const LAllocation& other) const;
     void dump() const;
 
 };
 
 class LUse : public LAllocation
 {
     static const uint32_t POLICY_BITS = 3;
@@ -575,21 +571,17 @@ class LDefinition
             return LDefinition::INT32X4;
           case MIRType_Float32x4:
             return LDefinition::FLOAT32X4;
           default:
             MOZ_CRASH("unexpected type");
         }
     }
 
-#ifdef DEBUG
     const char* toString() const;
-#else
-    const char* toString() const { return "???"; }
-#endif
 
     void dump() const;
 };
 
 // Forward declarations of LIR types.
 #define LIROP(op) class L##op;
     LIR_OPCODE_LIST(LIROP)
 #undef LIROP
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -586,17 +586,17 @@ IonBuilder::inlineArray(CallInfo& callIn
 
         // Don't inline large allocations.
         if (initLength > ArrayObject::EagerAllocationMaxLength)
             return InliningStatus_NotInlined;
     }
 
     callInfo.setImplicitlyUsedUnchecked();
 
-    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject, this);
+    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     current->add(templateConst);
 
     MNewArray* ins = MNewArray::New(alloc(), constraints(), initLength, templateConst,
                                     templateObject->group()->initialHeap(constraints()), pc);
     current->add(ins);
     current->push(ins);
 
     if (callInfo.argc() >= 2) {
@@ -760,17 +760,17 @@ IonBuilder::inlineArrayPush(CallInfo& ca
 {
     if (callInfo.argc() != 1 || callInfo.constructing()) {
         trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm);
         return InliningStatus_NotInlined;
     }
 
     MDefinition* obj = callInfo.thisArg();
     MDefinition* value = callInfo.getArg(0);
-    if (PropertyWriteNeedsTypeBarrier(this, constraints(), current,
+    if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
                                       &obj, nullptr, &value, /* canModify = */ false))
     {
         trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
         return InliningStatus_NotInlined;
     }
     MOZ_ASSERT(obj == callInfo.thisArg() && value == callInfo.getArg(0));
 
     if (getInlineReturnType() != MIRType_Int32)
@@ -1696,17 +1696,17 @@ IonBuilder::inlineConstantStringSplit(Ca
     }
     callInfo.setImplicitlyUsedUnchecked();
 
     TemporaryTypeSet::DoubleConversion conversion =
             getInlineReturnTypeSet()->convertDoubleElements(constraints());
     if (conversion == TemporaryTypeSet::AlwaysConvertToDoubles)
         return InliningStatus_NotInlined;
 
-    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject, this);
+    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     current->add(templateConst);
 
     MNewArray* ins = MNewArray::New(alloc(), constraints(), initLength, templateConst,
                                     templateObject->group()->initialHeap(constraints()), pc);
 
     current->add(ins);
     current->push(ins);
 
@@ -1767,17 +1767,17 @@ IonBuilder::inlineStringSplit(CallInfo& 
 
     if (!key.maybeTypes()->hasType(TypeSet::StringType())) {
         key.freeze(constraints());
         return InliningStatus_NotInlined;
     }
 
     callInfo.setImplicitlyUsedUnchecked();
     MConstant* templateObjectDef = MConstant::New(alloc(), ObjectValue(*templateObject),
-                                                  constraints(), this);
+                                                  constraints());
     current->add(templateObjectDef);
 
     MStringSplit* ins = MStringSplit::New(alloc(), constraints(), callInfo.thisArg(),
                                           callInfo.getArg(0), templateObjectDef);
     current->add(ins);
     current->push(ins);
 
     return InliningStatus_Inlined;
@@ -2092,17 +2092,17 @@ IonBuilder::inlineObjectCreate(CallInfo&
         MOZ_ASSERT(types->getKnownMIRType() == MIRType_Object);
     } else {
         if (arg->type() != MIRType_Null)
             return InliningStatus_NotInlined;
     }
 
     callInfo.setImplicitlyUsedUnchecked();
 
-    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject, this);
+    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     current->add(templateConst);
     MNewObject* ins = MNewObject::New(alloc(), constraints(), templateConst,
                                       templateObject->group()->initialHeap(constraints()),
                                       MNewObject::ObjectCreate);
     current->add(ins);
     current->push(ins);
     if (!resumeAfter(ins))
         return InliningStatus_Error;
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -636,46 +636,45 @@ MDefinition::replaceAllLiveUsesWith(MDef
 
 bool
 MDefinition::emptyResultTypeSet() const
 {
     return resultTypeSet() && resultTypeSet()->empty();
 }
 
 MConstant*
-MConstant::New(TempAllocator& alloc, const Value& v,
-               CompilerConstraintList* constraints, MIRGenerator* gen)
-{
-    return new(alloc) MConstant(v, constraints, gen);
+MConstant::New(TempAllocator& alloc, const Value& v, CompilerConstraintList* constraints)
+{
+    return new(alloc) MConstant(v, constraints);
 }
 
 MConstant*
 MConstant::NewTypedValue(TempAllocator& alloc, const Value& v, MIRType type,
-                         CompilerConstraintList* constraints, MIRGenerator* gen)
+                         CompilerConstraintList* constraints)
 {
     MOZ_ASSERT(!IsSimdType(type));
     MOZ_ASSERT_IF(type == MIRType_Float32,
                   IsNaN(v.toDouble()) || v.toDouble() == double(float(v.toDouble())));
-    MConstant* constant = new(alloc) MConstant(v, constraints, gen);
+    MConstant* constant = new(alloc) MConstant(v, constraints);
     constant->setResultType(type);
     return constant;
 }
 
 MConstant*
 MConstant::NewAsmJS(TempAllocator& alloc, const Value& v, MIRType type)
 {
     if (type == MIRType_Float32)
         return NewTypedValue(alloc, Float32Value(v.toNumber()), type);
     return NewTypedValue(alloc, v, type);
 }
 
 MConstant*
-MConstant::NewConstraintlessObject(TempAllocator& alloc, JSObject* v, MIRGenerator* gen)
-{
-    return new(alloc) MConstant(v, gen);
+MConstant::NewConstraintlessObject(TempAllocator& alloc, JSObject* v)
+{
+    return new(alloc) MConstant(v);
 }
 
 static TemporaryTypeSet*
 MakeSingletonTypeSetFromKey(CompilerConstraintList* constraints, TypeSet::ObjectKey* key)
 {
     // Invalidate when this object's ObjectGroup gets unknown properties. This
     // happens for instance when we mutate an object's __proto__, in this case
     // we want to invalidate and mark this TypeSet as containing AnyObject
@@ -701,25 +700,44 @@ jit::MakeSingletonTypeSet(CompilerConstr
 
 static TemporaryTypeSet*
 MakeUnknownTypeSet()
 {
     LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc();
     return alloc->new_<TemporaryTypeSet>(alloc, TypeSet::UnknownType());
 }
 
-MConstant::MConstant(const js::Value& vp, CompilerConstraintList* constraints, MIRGenerator* gen)
+#ifdef DEBUG
+
+bool
+jit::IonCompilationCanUseNurseryPointers()
+{
+    // If we are doing backend compilation, which could occur on a helper
+    // thread but might actually be on the main thread, check the flag set on
+    // the PerThreadData by AutoEnterIonCompilation.
+    if (CurrentThreadIsIonCompiling())
+        return !CurrentThreadIsIonCompilingSafeForMinorGC();
+
+    // Otherwise, we must be on the main thread during MIR construction. The
+    // store buffer must have been notified that minor GCs must cancel pending
+    // or in progress Ion compilations.
+    JSRuntime* rt = TlsPerThreadData.get()->runtimeFromMainThread();
+    return rt->gc.storeBuffer.cancelIonCompilations();
+}
+
+#endif // DEBUG
+
+MConstant::MConstant(const js::Value& vp, CompilerConstraintList* constraints)
   : value_(vp)
 {
     setResultType(MIRTypeFromValue(vp));
     if (vp.isObject()) {
         // Create a singleton type set for the object. This isn't necessary for
         // other types as the result type encodes all needed information.
-        MOZ_ASSERT(gen);
-        MOZ_ASSERT_IF(IsInsideNursery(&vp.toObject()), !gen->safeForMinorGC());
+        MOZ_ASSERT_IF(IsInsideNursery(&vp.toObject()), IonCompilationCanUseNurseryPointers());
         setResultTypeSet(MakeSingletonTypeSet(constraints, &vp.toObject()));
     }
     if (vp.isMagic() && vp.whyMagic() == JS_UNINITIALIZED_LEXICAL) {
         // JS_UNINITIALIZED_LEXICAL does not escape to script and is not
         // observed in type sets. However, it may flow around freely during
         // Ion compilation. Give it an unknown typeset to poison any type sets
         // it merges with.
         //
@@ -728,21 +746,20 @@ MConstant::MConstant(const js::Value& vp
         setResultTypeSet(MakeUnknownTypeSet());
     }
 
     MOZ_ASSERT_IF(vp.isString(), vp.toString()->isAtom());
 
     setMovable();
 }
 
-MConstant::MConstant(JSObject* obj, MIRGenerator* gen)
+MConstant::MConstant(JSObject* obj)
   : value_(ObjectValue(*obj))
 {
-    MOZ_ASSERT(gen);
-    MOZ_ASSERT_IF(IsInsideNursery(obj), !gen->safeForMinorGC());
+    MOZ_ASSERT_IF(IsInsideNursery(obj), IonCompilationCanUseNurseryPointers());
     setResultType(MIRType_Object);
     setMovable();
 }
 
 HashNumber
 MConstant::valueHash() const
 {
     // Fold all 64 bits into the 32-bit result. It's tempting to just discard
@@ -5203,28 +5220,28 @@ TryAddTypeBarrierForWrite(TempAllocator&
         kind = BarrierKind::TypeTagOnly;
 
     MInstruction* ins = MMonitorTypes::New(alloc, *pvalue, types, kind);
     current->add(ins);
     return true;
 }
 
 static MInstruction*
-AddGroupGuard(MIRGenerator* gen, MBasicBlock* current, MDefinition* obj,
+AddGroupGuard(TempAllocator& alloc, MBasicBlock* current, MDefinition* obj,
               TypeSet::ObjectKey* key, bool bailOnEquality)
 {
     MInstruction* guard;
 
     if (key->isGroup()) {
-        guard = MGuardObjectGroup::New(gen->alloc(), obj, key->group(), bailOnEquality,
+        guard = MGuardObjectGroup::New(alloc, obj, key->group(), bailOnEquality,
                                        Bailout_ObjectIdentityOrTypeGuard);
     } else {
-        MConstant* singletonConst = MConstant::NewConstraintlessObject(gen->alloc(), key->singleton(), gen);
+        MConstant* singletonConst = MConstant::NewConstraintlessObject(alloc, key->singleton());
         current->add(singletonConst);
-        guard = MGuardObjectIdentity::New(gen->alloc(), obj, singletonConst, bailOnEquality);
+        guard = MGuardObjectIdentity::New(alloc, obj, singletonConst, bailOnEquality);
     }
 
     current->add(guard);
 
     // For now, never move object group / identity guards.
     guard->setNotMovable();
 
     return guard;
@@ -5237,17 +5254,17 @@ jit::CanWriteProperty(TempAllocator& all
                       MIRType implicitType /* = MIRType_None */)
 {
     if (property.couldBeConstant(constraints))
         return false;
     return PropertyTypeIncludes(alloc, property, value, implicitType);
 }
 
 bool
-jit::PropertyWriteNeedsTypeBarrier(MIRGenerator* gen, CompilerConstraintList* constraints,
+jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList* constraints,
                                    MBasicBlock* current, MDefinition** pobj,
                                    PropertyName* name, MDefinition** pvalue,
                                    bool canModify, MIRType implicitType)
 {
     // If any value being written is not reflected in the type information for
     // objects which obj could represent, a type barrier is needed when writing
     // the value. As for propertyReadNeedsTypeBarrier, this only applies for
     // properties that are accounted for by type information, i.e. normal data
@@ -5270,24 +5287,24 @@ jit::PropertyWriteNeedsTypeBarrier(MIRGe
 
         // TI doesn't track TypedArray indexes and should never insert a type
         // barrier for them.
         if (!name && IsAnyTypedArrayClass(key->clasp()))
             continue;
 
         jsid id = name ? NameToId(name) : JSID_VOID;
         HeapTypeSetKey property = key->property(id);
-        if (!CanWriteProperty(gen->alloc(), constraints, property, *pvalue, implicitType)) {
+        if (!CanWriteProperty(alloc, constraints, property, *pvalue, implicitType)) {
             // Either pobj or pvalue needs to be modified to filter out the
             // types which the value could have but are not in the property,
             // or a VM call is required. A VM call is always required if pobj
             // and pvalue cannot be modified.
             if (!canModify)
                 return true;
-            success = TryAddTypeBarrierForWrite(gen->alloc(), constraints, current, types, name, pvalue,
+            success = TryAddTypeBarrierForWrite(alloc, constraints, current, types, name, pvalue,
                                                 implicitType);
             break;
         }
     }
 
     if (success)
         return false;
 
@@ -5303,17 +5320,17 @@ jit::PropertyWriteNeedsTypeBarrier(MIRGe
         TypeSet::ObjectKey* key = types->getObject(i);
         if (!key || key->unknownProperties())
             continue;
         if (!name && IsAnyTypedArrayClass(key->clasp()))
             continue;
 
         jsid id = name ? NameToId(name) : JSID_VOID;
         HeapTypeSetKey property = key->property(id);
-        if (CanWriteProperty(gen->alloc(), constraints, property, *pvalue, implicitType))
+        if (CanWriteProperty(alloc, constraints, property, *pvalue, implicitType))
             continue;
 
         if ((property.maybeTypes() && !property.maybeTypes()->empty()) || excluded)
             return true;
         excluded = key;
     }
 
     MOZ_ASSERT(excluded);
@@ -5324,11 +5341,11 @@ jit::PropertyWriteNeedsTypeBarrier(MIRGe
     if (excluded->isGroup()) {
         if (UnboxedLayout* layout = excluded->group()->maybeUnboxedLayout()) {
             if (layout->nativeGroup())
                 return true;
             excluded->watchStateChangeForUnboxedConvertedToNative(constraints);
         }
     }
 
-    *pobj = AddGroupGuard(gen, current, *pobj, excluded, /* bailOnEquality = */ true);
+    *pobj = AddGroupGuard(alloc, current, *pobj, excluded, /* bailOnEquality = */ true);
     return false;
 }
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1304,30 +1304,27 @@ class MLimitedTruncate
 };
 
 // A constant js::Value.
 class MConstant : public MNullaryInstruction
 {
     Value value_;
 
   protected:
-    MConstant(const Value& v, CompilerConstraintList* constraints, MIRGenerator* gen);
-    explicit MConstant(JSObject* obj, MIRGenerator* gen);
+    MConstant(const Value& v, CompilerConstraintList* constraints);
+    explicit MConstant(JSObject* obj);
 
   public:
     INSTRUCTION_HEADER(Constant)
     static MConstant* New(TempAllocator& alloc, const Value& v,
-                          CompilerConstraintList* constraints = nullptr,
-                          MIRGenerator* gen = nullptr);
+                          CompilerConstraintList* constraints = nullptr);
     static MConstant* NewTypedValue(TempAllocator& alloc, const Value& v, MIRType type,
-                                    CompilerConstraintList* constraints = nullptr,
-                                    MIRGenerator* gen = nullptr);
+                                    CompilerConstraintList* constraints = nullptr);
     static MConstant* NewAsmJS(TempAllocator& alloc, const Value& v, MIRType type);
-    static MConstant* NewConstraintlessObject(TempAllocator& alloc, JSObject* v,
-                                              MIRGenerator* gen);
+    static MConstant* NewConstraintlessObject(TempAllocator& alloc, JSObject* v);
 
     const js::Value& value() const {
         return value_;
     }
     const js::Value* vp() const {
         return &value_;
     }
     bool valueToBoolean() const {
@@ -2839,52 +2836,59 @@ MergeTypes(MIRType* ptype, TemporaryType
 
 bool
 TypeSetIncludes(TypeSet* types, MIRType input, TypeSet* inputTypes);
 
 bool
 EqualTypes(MIRType type1, TemporaryTypeSet* typeset1,
            MIRType type2, TemporaryTypeSet* typeset2);
 
-// Helper class to assert all GC pointers embedded in MIR instructions are
-// tenured. Off-thread Ion compilation and nursery GCs can happen in parallel,
-// so it's invalid to store pointers to nursery things. There's no need to root
-// these pointers, as GC is suppressed during compilation and off-thread
-// compilations are canceled on every major GC.
+#ifdef DEBUG
+bool
+IonCompilationCanUseNurseryPointers();
+#endif
+
+// Helper class to check that GC pointers embedded in MIR instructions are in
+// in the nursery only when the store buffer has been marked as needing to
+// cancel all ion compilations. Otherwise, off-thread Ion compilation and
+// nursery GCs can happen in parallel, so it's invalid to store pointers to
+// nursery things. There's no need to root these pointers, as GC is suppressed
+// during compilation and off-thread compilations are canceled on major GCs.
 template <typename T>
-class AlwaysTenured
+class CompilerGCPointer
 {
     js::gc::Cell* ptr_;
 
   public:
-    explicit AlwaysTenured(T ptr)
+    explicit CompilerGCPointer(T ptr)
       : ptr_(ptr)
     {
+        MOZ_ASSERT_IF(IsInsideNursery(ptr), IonCompilationCanUseNurseryPointers());
 #ifdef DEBUG
-        MOZ_ASSERT(!IsInsideNursery(ptr_));
         PerThreadData* pt = TlsPerThreadData.get();
         MOZ_ASSERT_IF(pt->runtimeIfOnOwnerThread(), pt->suppressGC);
 #endif
     }
 
     operator T() const { return static_cast<T>(ptr_); }
     T operator->() const { return static_cast<T>(ptr_); }
 
   private:
-    AlwaysTenured() = delete;
-    AlwaysTenured(const AlwaysTenured<T>&) = delete;
-    AlwaysTenured<T>& operator=(const AlwaysTenured<T>&) = delete;
-};
-
-typedef AlwaysTenured<JSObject*> AlwaysTenuredObject;
-typedef AlwaysTenured<NativeObject*> AlwaysTenuredNativeObject;
-typedef AlwaysTenured<JSFunction*> AlwaysTenuredFunction;
-typedef AlwaysTenured<JSScript*> AlwaysTenuredScript;
-typedef AlwaysTenured<PropertyName*> AlwaysTenuredPropertyName;
-typedef AlwaysTenured<Shape*> AlwaysTenuredShape;
+    CompilerGCPointer() = delete;
+    CompilerGCPointer(const CompilerGCPointer<T>&) = delete;
+    CompilerGCPointer<T>& operator=(const CompilerGCPointer<T>&) = delete;
+};
+
+typedef CompilerGCPointer<JSObject*> CompilerObject;
+typedef CompilerGCPointer<NativeObject*> CompilerNativeObject;
+typedef CompilerGCPointer<JSFunction*> CompilerFunction;
+typedef CompilerGCPointer<JSScript*> CompilerScript;
+typedef CompilerGCPointer<PropertyName*> CompilerPropertyName;
+typedef CompilerGCPointer<Shape*> CompilerShape;
+typedef CompilerGCPointer<ObjectGroup*> CompilerObjectGroup;
 
 class MNewArray
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
   private:
     // Number of space to allocate for the array.
     uint32_t count_;
@@ -2949,17 +2953,17 @@ class MNewArray
         // The template object can safely be used in the recover instruction
         // because it can never be mutated by any other function execution.
         return templateObject() != nullptr;
     }
 };
 
 class MNewArrayCopyOnWrite : public MNullaryInstruction
 {
-    AlwaysTenured<ArrayObject*> templateObject_;
+    CompilerGCPointer<ArrayObject*> templateObject_;
     gc::InitialHeap initialHeap_;
 
     MNewArrayCopyOnWrite(CompilerConstraintList* constraints, ArrayObject* templateObject,
               gc::InitialHeap initialHeap)
       : templateObject_(templateObject),
         initialHeap_(initialHeap)
     {
         MOZ_ASSERT(!templateObject->isSingleton());
@@ -2990,17 +2994,17 @@ class MNewArrayCopyOnWrite : public MNul
         return AliasSet::None();
     }
 };
 
 class MNewArrayDynamicLength
   : public MUnaryInstruction,
     public IntPolicy<0>::Data
 {
-    AlwaysTenuredObject templateObject_;
+    CompilerObject templateObject_;
     gc::InitialHeap initialHeap_;
 
     MNewArrayDynamicLength(CompilerConstraintList* constraints, JSObject* templateObject,
                            gc::InitialHeap initialHeap, MDefinition* length)
       : MUnaryInstruction(length),
         templateObject_(templateObject),
         initialHeap_(initialHeap)
     {
@@ -3098,17 +3102,17 @@ class MNewObject
         // The template object can safely be used in the recover instruction
         // because it can never be mutated by any other function execution.
         return templateObject() != nullptr;
     }
 };
 
 class MNewTypedObject : public MNullaryInstruction
 {
-    AlwaysTenured<InlineTypedObject*> templateObject_;
+    CompilerGCPointer<InlineTypedObject*> templateObject_;
     gc::InitialHeap initialHeap_;
 
     MNewTypedObject(CompilerConstraintList* constraints,
                     InlineTypedObject* templateObject,
                     gc::InitialHeap initialHeap)
       : templateObject_(templateObject),
         initialHeap_(initialHeap)
     {
@@ -3173,17 +3177,17 @@ class MTypedObjectDescr
 // Generic way for constructing a SIMD object in IonMonkey, this instruction
 // takes as argument a SIMD instruction and returns a new SIMD object which
 // corresponds to the MIRType of its operand.
 class MSimdBox
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
   protected:
-    AlwaysTenured<InlineTypedObject*> templateObject_;
+    CompilerGCPointer<InlineTypedObject*> templateObject_;
     gc::InitialHeap initialHeap_;
 
     MSimdBox(CompilerConstraintList* constraints,
              MDefinition* op,
              InlineTypedObject* templateObject,
              gc::InitialHeap initialHeap)
       : MUnaryInstruction(op),
         templateObject_(templateObject),
@@ -3526,17 +3530,17 @@ class MMutateProto
     }
 };
 
 // Slow path for adding a property to an object without a known base.
 class MInitProp
   : public MAryInstruction<2>,
     public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >::Data
 {
-    AlwaysTenuredPropertyName name_;
+    CompilerPropertyName name_;
 
   protected:
     MInitProp(MDefinition* obj, PropertyName* name, MDefinition* value)
       : name_(name)
     {
         initOperand(0, obj);
         initOperand(1, value);
         setResultType(MIRType_None);
@@ -3566,17 +3570,17 @@ class MInitProp
         return true;
     }
 };
 
 class MInitPropGetterSetter
   : public MBinaryInstruction,
     public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1> >::Data
 {
-    AlwaysTenuredPropertyName name_;
+    CompilerPropertyName name_;
 
     MInitPropGetterSetter(MDefinition* obj, PropertyName* name, MDefinition* value)
       : MBinaryInstruction(obj, value),
         name_(name)
     { }
 
   public:
     INSTRUCTION_HEADER(InitPropGetterSetter)
@@ -3668,17 +3672,17 @@ class MCall
   private:
     // An MCall uses the MPrepareCall, MDefinition for the function, and
     // MPassArg instructions. They are stored in the same list.
     static const size_t FunctionOperandIndex   = 0;
     static const size_t NumNonArgumentOperands = 1;
 
   protected:
     // Monomorphic cache of single target from TI, or nullptr.
-    AlwaysTenuredFunction target_;
+    CompilerFunction target_;
 
     // Original value of argc from the bytecode.
     uint32_t numActualArgs_;
 
     // True if the call is for JSOP_NEW.
     bool construct_;
 
     bool needsArgCheck_;
@@ -3843,17 +3847,17 @@ class MArraySplice
 
 // fun.apply(self, arguments)
 class MApplyArgs
   : public MAryInstruction<3>,
     public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, BoxPolicy<2> >::Data
 {
   protected:
     // Monomorphic cache of single target from TI, or nullptr.
-    AlwaysTenuredFunction target_;
+    CompilerFunction target_;
 
     MApplyArgs(JSFunction* target, MDefinition* fun, MDefinition* argc, MDefinition* self)
       : target_(target)
     {
         initOperand(0, fun);
         initOperand(1, argc);
         initOperand(2, self);
         setResultType(MIRType_Value);
@@ -7186,17 +7190,17 @@ class MThrowUninitializedLexical : publi
     }
 };
 
 // If not defined, set a global variable to |undefined|.
 class MDefVar
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
-    AlwaysTenuredPropertyName name_; // Target name to be defined.
+    CompilerPropertyName name_; // Target name to be defined.
     unsigned attrs_; // Attributes to be set.
 
   private:
     MDefVar(PropertyName* name, unsigned attrs, MDefinition* scopeChain)
       : MUnaryInstruction(scopeChain),
         name_(name),
         attrs_(attrs)
     {
@@ -7224,17 +7228,17 @@ class MDefVar
         return true;
     }
 };
 
 class MDefFun
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
-    AlwaysTenuredFunction fun_;
+    CompilerFunction fun_;
 
   private:
     MDefFun(JSFunction* fun, MDefinition* scopeChain)
       : MUnaryInstruction(scopeChain),
         fun_(fun)
     {}
 
   public:
@@ -7252,17 +7256,17 @@ class MDefFun
     }
     bool possiblyCalls() const override {
         return true;
     }
 };
 
 class MRegExp : public MNullaryInstruction
 {
-    AlwaysTenured<RegExpObject*> source_;
+    CompilerGCPointer<RegExpObject*> source_;
     bool mustClone_;
 
     MRegExp(CompilerConstraintList* constraints, RegExpObject* source, bool mustClone)
       : source_(source),
         mustClone_(mustClone)
     {
         setResultType(MIRType_Object);
         setResultTypeSet(MakeSingletonTypeSet(constraints, source));
@@ -7508,17 +7512,17 @@ class MSubstr
     }
 };
 
 struct LambdaFunctionInfo
 {
     // The functions used in lambdas are the canonical original function in
     // the script, and are immutable except for delazification. Record this
     // information while still on the main thread to avoid races.
-    AlwaysTenuredFunction fun;
+    CompilerFunction fun;
     uint16_t flags;
     uint16_t nargs;
     gc::Cell* scriptOrLazyScript;
     bool singletonType;
     bool useSingletonForClone;
 
     explicit LambdaFunctionInfo(JSFunction* fun)
       : fun(fun), flags(fun->flags()), nargs(fun->nargs()),
@@ -9025,17 +9029,17 @@ class MStoreUnboxedString
 };
 
 // Passes through an object, after ensuring it is converted from an unboxed
 // object to a native representation.
 class MConvertUnboxedObjectToNative
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
-    AlwaysTenured<ObjectGroup*> group_;
+    CompilerObjectGroup group_;
 
     explicit MConvertUnboxedObjectToNative(MDefinition* obj, ObjectGroup* group)
       : MUnaryInstruction(obj),
         group_(group)
     {
         setGuard();
         setMovable();
         setResultType(MIRType_Object);
@@ -9172,17 +9176,17 @@ class MArrayPush
     ALLOW_CLONE(MArrayPush)
 };
 
 // Array.prototype.concat on two dense arrays.
 class MArrayConcat
   : public MBinaryInstruction,
     public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1> >::Data
 {
-    AlwaysTenuredObject templateObj_;
+    CompilerObject templateObj_;
     gc::InitialHeap initialHeap_;
     JSValueType unboxedType_;
 
     MArrayConcat(CompilerConstraintList* constraints, MDefinition* lhs, MDefinition* rhs,
                  JSObject* templateObj, gc::InitialHeap initialHeap, JSValueType unboxedType)
       : MBinaryInstruction(lhs, rhs),
         templateObj_(templateObj),
         initialHeap_(initialHeap),
@@ -9225,17 +9229,17 @@ class MArrayConcat
     }
 };
 
 // Array.prototype.slice on a dense array.
 class MArraySlice
   : public MTernaryInstruction,
     public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2>>::Data
 {
-    AlwaysTenuredObject templateObj_;
+    CompilerObject templateObj_;
     gc::InitialHeap initialHeap_;
     JSValueType unboxedType_;
 
     MArraySlice(CompilerConstraintList* constraints, MDefinition* obj,
                 MDefinition* begin, MDefinition* end,
                 JSObject* templateObj, gc::InitialHeap initialHeap, JSValueType unboxedType)
       : MTernaryInstruction(obj, begin, end),
         templateObj_(templateObj),
@@ -9519,17 +9523,18 @@ class MLoadTypedArrayElementStatic
         if (type == Scalar::Float32)
             setResultType(MIRType_Float32);
         else if (type == Scalar::Float64)
             setResultType(MIRType_Double);
         else
             setResultType(MIRType_Int32);
     }
 
-    AlwaysTenured<JSObject*> someTypedArray_;
+    CompilerObject someTypedArray_;
+
     // An offset to be encoded in the load instruction - taking advantage of the
     // addressing modes. This is only non-zero when the access is proven to be
     // within bounds.
     int32_t offset_;
     bool needsBoundsCheck_;
     bool fallible_;
 
   public:
@@ -9765,17 +9770,17 @@ class MStoreTypedArrayElementStatic :
     MStoreTypedArrayElementStatic(JSObject* someTypedArray, MDefinition* ptr, MDefinition* v,
                                   int32_t offset, bool needsBoundsCheck)
         : MBinaryInstruction(ptr, v),
           StoreUnboxedScalarBase(AnyTypedArrayType(someTypedArray)),
           someTypedArray_(someTypedArray),
           offset_(offset), needsBoundsCheck_(needsBoundsCheck)
     {}
 
-    AlwaysTenured<JSObject*> someTypedArray_;
+    CompilerObject someTypedArray_;
 
     // An offset to be encoded in the store instruction - taking advantage of the
     // addressing modes. This is only non-zero when the access is proven to be
     // within bounds.
     int32_t offset_;
     bool needsBoundsCheck_;
 
   public:
@@ -9985,18 +9990,18 @@ class MStoreFixedSlot
 };
 
 typedef Vector<JSObject*, 4, JitAllocPolicy> ObjectVector;
 typedef Vector<bool, 4, JitAllocPolicy> BoolVector;
 
 class InlinePropertyTable : public TempObject
 {
     struct Entry : public TempObject {
-        AlwaysTenured<ObjectGroup*> group;
-        AlwaysTenuredFunction func;
+        CompilerObjectGroup group;
+        CompilerFunction func;
 
         Entry(ObjectGroup* group, JSFunction* func)
           : group(group), func(func)
         { }
     };
 
     jsbytecode* pc_;
     MResumePoint* priorResumePoint_;
@@ -10062,17 +10067,17 @@ class CacheLocationList : public InlineC
     jsbytecode* pc;
     JSScript* script;
 };
 
 class MGetPropertyCache
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
-    AlwaysTenuredPropertyName name_;
+    CompilerPropertyName name_;
     bool idempotent_;
     bool monitoredResult_;
 
     CacheLocationList location_;
 
     InlinePropertyTable* inlinePropertyTable_;
 
     MGetPropertyCache(MDefinition* obj, PropertyName* name, bool monitoredResult)
@@ -10166,17 +10171,17 @@ class MGetPropertyPolymorphic
         // The group and/or shape to guard against.
         ReceiverGuard receiver;
 
         // The property to load, null for loads from unboxed properties.
         Shape* shape;
     };
 
     Vector<Entry, 4, JitAllocPolicy> receivers_;
-    AlwaysTenuredPropertyName name_;
+    CompilerPropertyName name_;
 
     MGetPropertyPolymorphic(TempAllocator& alloc, MDefinition* obj, PropertyName* name)
       : MUnaryInstruction(obj),
         receivers_(alloc),
         name_(name)
     {
         setGuard();
         setMovable();
@@ -10246,17 +10251,17 @@ class MSetPropertyPolymorphic
         // The group and/or shape to guard against.
         ReceiverGuard receiver;
 
         // The property to store, null for stores to unboxed properties.
         Shape* shape;
     };
 
     Vector<Entry, 4, JitAllocPolicy> receivers_;
-    AlwaysTenuredPropertyName name_;
+    CompilerPropertyName name_;
     bool needsBarrier_;
 
     MSetPropertyPolymorphic(TempAllocator& alloc, MDefinition* obj, MDefinition* value,
                             PropertyName* name)
       : MBinaryInstruction(obj, value),
         receivers_(alloc),
         name_(name),
         needsBarrier_(false)
@@ -10509,18 +10514,18 @@ class MGetElementCache
 
     bool allowDoubleResult() const;
 };
 
 class MBindNameCache
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
-    AlwaysTenuredPropertyName name_;
-    AlwaysTenuredScript script_;
+    CompilerPropertyName name_;
+    CompilerScript script_;
     jsbytecode* pc_;
 
     MBindNameCache(MDefinition* scopeChain, PropertyName* name, JSScript* script, jsbytecode* pc)
       : MUnaryInstruction(scopeChain), name_(name), script_(script), pc_(pc)
     {
         setResultType(MIRType_Object);
     }
 
@@ -10547,17 +10552,17 @@ class MBindNameCache
     }
 };
 
 // Guard on an object's shape.
 class MGuardShape
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
-    AlwaysTenuredShape shape_;
+    CompilerShape shape_;
     BailoutKind bailoutKind_;
 
     MGuardShape(MDefinition* obj, Shape* shape, BailoutKind bailoutKind)
       : MUnaryInstruction(obj),
         shape_(shape),
         bailoutKind_(bailoutKind)
     {
         setGuard();
@@ -10648,17 +10653,17 @@ class MGuardReceiverPolymorphic
     }
 };
 
 // Guard on an object's group, inclusively or exclusively.
 class MGuardObjectGroup
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
-    AlwaysTenured<ObjectGroup*> group_;
+    CompilerObjectGroup group_;
     bool bailOnEquality_;
     BailoutKind bailoutKind_;
 
     MGuardObjectGroup(MDefinition* obj, ObjectGroup* group, bool bailOnEquality,
                       BailoutKind bailoutKind)
       : MUnaryInstruction(obj),
         group_(group),
         bailOnEquality_(bailOnEquality),
@@ -11023,17 +11028,17 @@ class MGetNameCache
 {
   public:
     enum AccessKind {
         NAMETYPEOF,
         NAME
     };
 
   private:
-    AlwaysTenuredPropertyName name_;
+    CompilerPropertyName name_;
     AccessKind kind_;
 
     MGetNameCache(MDefinition* obj, PropertyName* name, AccessKind kind)
       : MUnaryInstruction(obj),
         name_(name),
         kind_(kind)
     {
         setResultType(MIRType_Value);
@@ -11055,17 +11060,17 @@ class MGetNameCache
     }
     AccessKind accessKind() const {
         return kind_;
     }
 };
 
 class MCallGetIntrinsicValue : public MNullaryInstruction
 {
-    AlwaysTenuredPropertyName name_;
+    CompilerPropertyName name_;
 
     explicit MCallGetIntrinsicValue(PropertyName* name)
       : name_(name)
     {
         setResultType(MIRType_Value);
     }
 
   public:
@@ -11082,17 +11087,17 @@ class MCallGetIntrinsicValue : public MN
     }
     bool possiblyCalls() const override {
         return true;
     }
 };
 
 class MSetPropertyInstruction : public MBinaryInstruction
 {
-    AlwaysTenuredPropertyName name_;
+    CompilerPropertyName name_;
     bool strict_;
     bool needsBarrier_;
 
   protected:
     MSetPropertyInstruction(MDefinition* obj, MDefinition* value, PropertyName* name,
                             bool strict)
       : MBinaryInstruction(obj, value),
         name_(name), strict_(strict), needsBarrier_(true)
@@ -11144,17 +11149,17 @@ class MSetElementInstruction
         return strict_;
     }
 };
 
 class MDeleteProperty
   : public MUnaryInstruction,
     public BoxInputsPolicy::Data
 {
-    AlwaysTenuredPropertyName name_;
+    CompilerPropertyName name_;
     bool strict_;
 
   protected:
     MDeleteProperty(MDefinition* val, PropertyName* name, bool strict)
       : MUnaryInstruction(val),
         name_(name),
         strict_(strict)
     {
@@ -11292,17 +11297,17 @@ class MSetElementCache
 
     bool canConsumeFloat32(MUse* use) const override { return use == getUseFor(2); }
 };
 
 class MCallGetProperty
   : public MUnaryInstruction,
     public BoxInputsPolicy::Data
 {
-    AlwaysTenuredPropertyName name_;
+    CompilerPropertyName name_;
     bool idempotent_;
     bool callprop_;
 
     MCallGetProperty(MDefinition* value, PropertyName* name, bool callprop)
       : MUnaryInstruction(value), name_(name),
         idempotent_(false),
         callprop_(callprop)
     {
@@ -11991,17 +11996,17 @@ class MInArray
     }
 };
 
 // Implementation for instanceof operator with specific rhs.
 class MInstanceOf
   : public MUnaryInstruction,
     public InstanceOfPolicy::Data
 {
-    AlwaysTenuredObject protoObj_;
+    CompilerObject protoObj_;
 
     MInstanceOf(MDefinition* obj, JSObject* proto)
       : MUnaryInstruction(obj),
         protoObj_(proto)
     {
         setResultType(MIRType_Boolean);
     }
 
@@ -12164,17 +12169,17 @@ class MSetFrameArgument
     AliasSet getAliasSet() const override {
         return AliasSet::Store(AliasSet::FrameArgument);
     }
 };
 
 class MRestCommon
 {
     unsigned numFormals_;
-    AlwaysTenured<ArrayObject*> templateObject_;
+    CompilerGCPointer<ArrayObject*> templateObject_;
 
   protected:
     MRestCommon(unsigned numFormals, ArrayObject* templateObject)
       : numFormals_(numFormals),
         templateObject_(templateObject)
    { }
 
   public:
@@ -12388,17 +12393,17 @@ class MPostWriteBarrier : public MBinary
     }
 #endif
 
     ALLOW_CLONE(MPostWriteBarrier)
 };
 
 class MNewDeclEnvObject : public MNullaryInstruction
 {
-    AlwaysTenured<DeclEnvObject*> templateObj_;
+    CompilerGCPointer<DeclEnvObject*> templateObj_;
 
     explicit MNewDeclEnvObject(DeclEnvObject* templateObj)
       : MNullaryInstruction(),
         templateObj_(templateObj)
     {
         setResultType(MIRType_Object);
     }
 
@@ -12414,17 +12419,17 @@ class MNewDeclEnvObject : public MNullar
     }
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 };
 
 class MNewCallObjectBase : public MNullaryInstruction
 {
-    AlwaysTenured<CallObject*> templateObj_;
+    CompilerGCPointer<CallObject*> templateObj_;
 
   protected:
     explicit MNewCallObjectBase(CallObject* templateObj)
       : MNullaryInstruction(),
         templateObj_(templateObj)
     {
         setResultType(MIRType_Object);
     }
@@ -12469,17 +12474,17 @@ class MNewRunOnceCallObject : public MNe
         return new(alloc) MNewRunOnceCallObject(templateObj);
     }
 };
 
 class MNewStringObject :
   public MUnaryInstruction,
   public ConvertToStringPolicy<0>::Data
 {
-    AlwaysTenuredObject templateObj_;
+    CompilerObject templateObj_;
 
     MNewStringObject(MDefinition* input, JSObject* templateObj)
       : MUnaryInstruction(input),
         templateObj_(templateObj)
     {
         setResultType(MIRType_Object);
     }
 
@@ -13605,17 +13610,17 @@ BarrierKind PropertyReadOnPrototypeNeeds
                                                     TemporaryTypeSet* observed);
 bool PropertyReadIsIdempotent(CompilerConstraintList* constraints,
                               MDefinition* obj, PropertyName* name);
 void AddObjectsForPropertyRead(MDefinition* obj, PropertyName* name,
                                TemporaryTypeSet* observed);
 bool CanWriteProperty(TempAllocator& alloc, CompilerConstraintList* constraints,
                       HeapTypeSetKey property, MDefinition* value,
                       MIRType implicitType = MIRType_None);
-bool PropertyWriteNeedsTypeBarrier(MIRGenerator* gen, CompilerConstraintList* constraints,
+bool PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList* constraints,
                                    MBasicBlock* current, MDefinition** pobj,
                                    PropertyName* name, MDefinition** pvalue,
                                    bool canModify, MIRType implicitType = MIRType_None);
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_MIR_h */
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -112,17 +112,19 @@ MResumePoint::writeRecoverData(CompactBu
 #endif
 
     // Test if we honor the maximum of arguments at all times.  This is a sanity
     // check and not an algorithm limit. So check might be a bit too loose.  +4
     // to account for scope chain, return value, this value and maybe
     // arguments_object.
     MOZ_ASSERT(CountArgSlots(script, fun) < SNAPSHOT_MAX_NARGS + 4);
 
+#ifdef DEBUG
     uint32_t implicit = StartArgSlot(script);
+#endif
     uint32_t formalArgs = CountArgSlots(script, fun);
     uint32_t nallocs = formalArgs + script->nfixed() + exprStack;
 
     JitSpew(JitSpew_IonSnapshots, "Starting frame; implicit %u, formals %u, fixed %u, exprs %u",
             implicit, formalArgs - implicit, script->nfixed(), exprStack);
 
     uint32_t pcoff = script->pcToOffset(pc());
     JitSpew(JitSpew_IonSnapshots, "Writing pc offset %u, nslots %u", pcoff, nallocs);
--- a/js/src/jsapi-tests/testGCStoreBufferRemoval.cpp
+++ b/js/src/jsapi-tests/testGCStoreBufferRemoval.cpp
@@ -27,64 +27,64 @@ BEGIN_TEST(testGCStoreBufferRemoval)
     CHECK(js::gc::IsInsideNursery(obj.get()));
     JS_GC(cx->runtime());
     CHECK(!js::gc::IsInsideNursery(obj.get()));
     JS::RootedObject tenuredObject(cx, obj);
 
     // Hide the horrors herein from the static rooting analysis.
     AutoIgnoreRootingHazards ignore;
 
-    // Test removal of store buffer entries added by HeapPtr<T>.
+    // Test removal of store buffer entries added by RelocatablePtr<T>.
     {
         JSObject* badObject = reinterpret_cast<JSObject*>(1);
         JSObject* punnedPtr = nullptr;
-        HeapPtrObject* relocPtr =
-            reinterpret_cast<HeapPtrObject*>(&punnedPtr);
-        new (relocPtr) HeapPtrObject;
+        RelocatablePtrObject* relocPtr =
+            reinterpret_cast<RelocatablePtrObject*>(&punnedPtr);
+        new (relocPtr) RelocatablePtrObject;
         *relocPtr = NurseryObject();
-        relocPtr->~HeapPtrObject();
+        relocPtr->~RelocatablePtrObject();
         punnedPtr = badObject;
         JS_GC(cx->runtime());
 
-        new (relocPtr) HeapPtrObject;
+        new (relocPtr) RelocatablePtrObject;
         *relocPtr = NurseryObject();
         *relocPtr = tenuredObject;
-        relocPtr->~HeapPtrObject();
+        relocPtr->~RelocatablePtrObject();
         punnedPtr = badObject;
         JS_GC(cx->runtime());
 
-        new (relocPtr) HeapPtrObject;
+        new (relocPtr) RelocatablePtrObject;
         *relocPtr = NurseryObject();
         *relocPtr = nullptr;
-        relocPtr->~HeapPtrObject();
+        relocPtr->~RelocatablePtrObject();
         punnedPtr = badObject;
         JS_GC(cx->runtime());
     }
 
-    // Test removal of store buffer entries added by HeapValue.
+    // Test removal of store buffer entries added by RelocatableValue.
     {
         Value punnedValue;
-        HeapValue* relocValue = reinterpret_cast<HeapValue*>(&punnedValue);
-        new (relocValue) HeapValue;
+        RelocatableValue* relocValue = reinterpret_cast<RelocatableValue*>(&punnedValue);
+        new (relocValue) RelocatableValue;
         *relocValue = ObjectValue(*NurseryObject());
-        relocValue->~HeapValue();
+        relocValue->~RelocatableValue();
         punnedValue = ObjectValueCrashOnTouch();
         JS_GC(cx->runtime());
 
-        new (relocValue) HeapValue;
+        new (relocValue) RelocatableValue;
         *relocValue = ObjectValue(*NurseryObject());
         *relocValue = ObjectValue(*tenuredObject);
-        relocValue->~HeapValue();
+        relocValue->~RelocatableValue();
         punnedValue = ObjectValueCrashOnTouch();
         JS_GC(cx->runtime());
 
-        new (relocValue) HeapValue;
+        new (relocValue) RelocatableValue;
         *relocValue = ObjectValue(*NurseryObject());
         *relocValue = NullValue();
-        relocValue->~HeapValue();
+        relocValue->~RelocatableValue();
         punnedValue = ObjectValueCrashOnTouch();
         JS_GC(cx->runtime());
     }
 
     // Test removal of store buffer entries added by Heap<T>.
     {
         JSObject* badObject = reinterpret_cast<JSObject*>(1);
         JSObject* punnedPtr = nullptr;
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1196,33 +1196,16 @@ js::GetObjectMetadata(JSObject* obj)
 {
     ObjectWeakMap* map = obj->compartment()->objectMetadataTable;
     if (map)
         return map->lookup(obj);
     return nullptr;
 }
 
 JS_FRIEND_API(bool)
-js::DefineOwnProperty(JSContext* cx, JSObject* objArg, jsid idArg,
-                      JS::Handle<js::PropertyDescriptor> descriptor, ObjectOpResult& result)
-{
-    RootedObject obj(cx, objArg);
-    RootedId id(cx, idArg);
-    AssertHeapIsIdle(cx);
-    CHECK_REQUEST(cx);
-    assertSameCompartment(cx, obj, id, descriptor.value());
-    if (descriptor.hasGetterObject())
-        assertSameCompartment(cx, descriptor.getterObject());
-    if (descriptor.hasSetterObject())
-        assertSameCompartment(cx, descriptor.setterObject());
-
-    return StandardDefineProperty(cx, obj, id, descriptor, result);
-}
-
-JS_FRIEND_API(bool)
 js::ReportIsNotFunction(JSContext* cx, HandleValue v)
 {
     return ReportIsNotFunction(cx, v, -1);
 }
 
 JS_FRIEND_API(void)
 js::ReportErrorWithId(JSContext* cx, const char* msg, HandleId id)
 {
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2808,20 +2808,16 @@ GetSavedFramePrincipals(JS::HandleObject
  * The savedFrame and cx do not need to be in the same compartment.
  */
 extern JS_FRIEND_API(JSObject*)
 GetFirstSubsumedSavedFrame(JSContext* cx, JS::HandleObject savedFrame);
 
 extern JS_FRIEND_API(bool)
 ReportIsNotFunction(JSContext* cx, JS::HandleValue v);
 
-extern JS_FRIEND_API(bool)
-DefineOwnProperty(JSContext* cx, JSObject* objArg, jsid idArg,
-                  JS::Handle<JSPropertyDescriptor> descriptor, JS::ObjectOpResult& result);
-
 extern JS_FRIEND_API(JSObject*)
 ConvertArgsToArray(JSContext* cx, const JS::CallArgs& args);
 
 } /* namespace js */
 
 extern JS_FRIEND_API(void)
 JS_StoreObjectPostBarrierCallback(JSContext* cx,
                                   void (*callback)(JSTracer* trc, JSObject* key, void* data),
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -269,425 +269,17 @@ js::Throw(JSContext* cx, JSObject* obj, 
     } else {
         MOZ_ASSERT(js_ErrorFormatString[errorNumber].argCount == 0);
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, errorNumber);
     }
     return false;
 }
 
 
-/*** Standard-compliant property definition (used by Object.defineProperty) **********************/
-
-static bool
-DefinePropertyOnObject(JSContext* cx, HandleNativeObject obj, HandleId id,
-                       Handle<PropertyDescriptor> desc, ObjectOpResult& result)
-{
-    /* 8.12.9 step 1. */
-    RootedShape shape(cx);
-    MOZ_ASSERT(!obj->getOps()->lookupProperty);
-    if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
-        return false;
-
-    MOZ_ASSERT(!obj->getOps()->defineProperty);
-
-    /* 8.12.9 steps 2-4. */
-    if (!shape) {
-        bool extensible;
-        if (!IsExtensible(cx, obj, &extensible))
-            return false;
-        if (!extensible)
-            return result.fail(JSMSG_OBJECT_NOT_EXTENSIBLE);
-
-        if (desc.isGenericDescriptor() || desc.isDataDescriptor()) {
-            MOZ_ASSERT(!obj->getOps()->defineProperty);
-            RootedValue v(cx, desc.hasValue() ? desc.value() : UndefinedValue());
-            unsigned attrs = desc.attributes() & (JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY);
-
-            if (!desc.hasConfigurable())
-                attrs |= JSPROP_PERMANENT;
-            if (!desc.hasWritable())
-                attrs |= JSPROP_READONLY;
-            return NativeDefineProperty(cx, obj, id, v, nullptr, nullptr, attrs, result);
-        }
-
-        MOZ_ASSERT(desc.isAccessorDescriptor());
-
-        unsigned attrs = desc.attributes() & (JSPROP_PERMANENT | JSPROP_ENUMERATE |
-                                              JSPROP_SHARED | JSPROP_GETTER | JSPROP_SETTER);
-        if (!desc.hasConfigurable())
-            attrs |= JSPROP_PERMANENT;
-        return NativeDefineProperty(cx, obj, id, UndefinedHandleValue,
-                                    desc.getter(), desc.setter(), attrs, result);
-    }
-
-    /* 8.12.9 steps 5-6 (note 5 is merely a special case of 6). */
-    RootedValue v(cx);
-
-    bool shapeDataDescriptor = true,
-         shapeAccessorDescriptor = false,
-         shapeWritable = true,
-         shapeConfigurable = true,
-         shapeEnumerable = true,
-         shapeHasDefaultGetter = true,
-         shapeHasDefaultSetter = true,
-         shapeHasGetterValue = false,
-         shapeHasSetterValue = false;
-    uint8_t shapeAttributes = GetShapeAttributes(obj, shape);
-    if (!IsImplicitDenseOrTypedArrayElement(shape)) {
-        shapeDataDescriptor = shape->isDataDescriptor();
-        shapeAccessorDescriptor = shape->isAccessorDescriptor();
-        shapeWritable = shape->writable();
-        shapeConfigurable = shape->configurable();
-        shapeEnumerable = shape->enumerable();
-        shapeHasDefaultGetter = shape->hasDefaultGetter();
-        shapeHasDefaultSetter = shape->hasDefaultSetter();
-        shapeHasGetterValue = shape->hasGetterValue();
-        shapeHasSetterValue = shape->hasSetterValue();
-        shapeAttributes = shape->attributes();
-    }
-
-    do {
-        if (desc.isAccessorDescriptor()) {
-            if (!shapeAccessorDescriptor)
-                break;
-
-            if (desc.hasGetterObject()) {
-                if (!shape->hasGetterValue() || desc.getterObject() != shape->getterObject())
-                    break;
-            }
-
-            if (desc.hasSetterObject()) {
-                if (!shape->hasSetterValue() || desc.setterObject() != shape->setterObject())
-                    break;
-            }
-        } else {
-            /*
-             * Determine the current value of the property once, if the current
-             * value might actually need to be used or preserved later.  NB: we
-             * guard on whether the current property is a data descriptor to
-             * avoid calling a getter; we won't need the value if it's not a
-             * data descriptor.
-             */
-            if (IsImplicitDenseOrTypedArrayElement(shape)) {
-                v = obj->getDenseOrTypedArrayElement(JSID_TO_INT(id));
-            } else if (shape->isDataDescriptor()) {
-                /*
-                 * We must rule out a non-configurable js::SetterOp-guarded
-                 * property becoming a writable unguarded data property, since
-                 * such a property can have its value changed to one the getter
-                 * and setter preclude.
-                 *
-                 * A desc lacking writable but with value is a data descriptor
-                 * and we must reject it as if it had writable: true if current
-                 * is writable.
-                 */
-                if (!shape->configurable() &&
-                    (!shape->hasDefaultGetter() || !shape->hasDefaultSetter()) &&
-                    desc.isDataDescriptor() &&
-                    (desc.hasWritable() ? desc.writable() : shape->writable()))
-                {
-                    return result.fail(JSMSG_CANT_REDEFINE_PROP);
-                }
-
-                if (!NativeGetExistingProperty(cx, obj, obj, shape, &v))
-                    return false;
-            }
-
-            if (desc.isDataDescriptor()) {
-                if (!shapeDataDescriptor)
-                    break;
-
-                bool same;
-                if (desc.hasValue()) {
-                    if (!SameValue(cx, desc.value(), v, &same))
-                        return false;
-                    if (!same) {
-                        /*
-                         * Insist that a non-configurable js::GetterOp data
-                         * property is frozen at exactly the last-got value.
-                         *
-                         * Duplicate the first part of the big conjunction that
-                         * we tested above, rather than add a local bool flag.
-                         * Likewise, don't try to keep shape->writable() in a
-                         * flag we veto from true to false for non-configurable
-                         * GetterOp-based data properties and test before the
-                         * SameValue check later on in order to re-use that "if
-                         * (!SameValue) return false" logic.
-                         *
-                         * This function is large and complex enough that it
-                         * seems best to repeat a small bit of code and return
-                         * result.fail() ASAP, instead of being clever.
-                         */
-                        if (!shapeConfigurable &&
-                            (!shape->hasDefaultGetter() || !shape->hasDefaultSetter()))
-                        {
-                            return result.fail(JSMSG_CANT_REDEFINE_PROP);
-                        }
-                        break;
-                    }
-                }
-                if (desc.hasWritable() && desc.writable() != shapeWritable)
-                    break;
-            } else {
-                /* The only fields in desc will be handled below. */
-                MOZ_ASSERT(desc.isGenericDescriptor());
-            }
-        }
-
-        if (desc.hasConfigurable() && desc.configurable() != shapeConfigurable)
-            break;
-        if (desc.hasEnumerable() && desc.enumerable() != shapeEnumerable)
-            break;
-
-        /* The conditions imposed by step 5 or step 6 apply. */
-        return result.succeed();
-    } while (0);
-
-    /* 8.12.9 step 7. */
-    if (!shapeConfigurable) {
-        if ((desc.hasConfigurable() && desc.configurable()) ||
-            (desc.hasEnumerable() && desc.enumerable() != shape->enumerable())) {
-            return result.fail(JSMSG_CANT_REDEFINE_PROP);
-        }
-    }
-
-    bool callDelProperty = false;
-
-    if (desc.isGenericDescriptor()) {
-        /* 8.12.9 step 8, no validation required */
-    } else if (desc.isDataDescriptor() != shapeDataDescriptor) {
-        /* 8.12.9 step 9. */
-        if (!shapeConfigurable)
-            return result.fail(JSMSG_CANT_REDEFINE_PROP);
-    } else if (desc.isDataDescriptor()) {
-        /* 8.12.9 step 10. */
-        MOZ_ASSERT(shapeDataDescriptor);
-        if (!shapeConfigurable && !shape->writable()) {
-            if (desc.hasWritable() && desc.writable())
-                return result.fail(JSMSG_CANT_REDEFINE_PROP);
-            if (desc.hasValue()) {
-                bool same;
-                if (!SameValue(cx, desc.value(), v, &same))
-                    return false;
-                if (!same)
-                    return result.fail(JSMSG_CANT_REDEFINE_PROP);
-            }
-        }
-
-        callDelProperty = !shapeHasDefaultGetter || !shapeHasDefaultSetter;
-    } else {
-        /* 8.12.9 step 11. */
-        MOZ_ASSERT(desc.isAccessorDescriptor() && shape->isAccessorDescriptor());
-        if (!shape->configurable()) {
-            // The hasSetterValue() and hasGetterValue() calls below ought to
-            // be redundant here, because accessor shapes should always have
-            // both JSPROP_GETTER and JSPROP_SETTER. But this is not the case
-            // currently; in particular Object.defineProperty(obj, key, {get: fn})
-            // creates a property without JSPROP_SETTER (bug 1133315).
-            if (desc.hasSetterObject() &&
-                desc.setterObject() != (shape->hasSetterValue() ? shape->setterObject() : nullptr))
-            {
-                return result.fail(JSMSG_CANT_REDEFINE_PROP);
-            }
-
-            if (desc.hasGetterObject() &&
-                desc.getterObject() != (shape->hasGetterValue() ? shape->getterObject() : nullptr))
-            {
-                return result.fail(JSMSG_CANT_REDEFINE_PROP);
-            }
-        }
-    }
-
-    /* 8.12.9 step 12. */
-    unsigned attrs;
-    GetterOp getter;
-    SetterOp setter;
-    if (desc.isGenericDescriptor()) {
-        unsigned changed = 0;
-        if (desc.hasConfigurable())
-            changed |= JSPROP_PERMANENT;
-        if (desc.hasEnumerable())
-            changed |= JSPROP_ENUMERATE;
-
-        attrs = (shapeAttributes & ~changed) | (desc.attributes() & changed);
-        getter = IsImplicitDenseOrTypedArrayElement(shape) ? nullptr : shape->getter();
-        setter = IsImplicitDenseOrTypedArrayElement(shape) ? nullptr : shape->setter();
-    } else if (desc.isDataDescriptor()) {
-        /* Watch out for accessor -> data transformations here. */
-        unsigned changed = JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED;
-        unsigned descAttrs = desc.attributes();
-        if (desc.hasConfigurable())
-            changed |= JSPROP_PERMANENT;
-        if (desc.hasEnumerable())
-            changed |= JSPROP_ENUMERATE;
-
-        if (desc.hasWritable()) {
-            changed |= JSPROP_READONLY;
-        } else if (!shapeDataDescriptor) {
-            changed |= JSPROP_READONLY;
-            descAttrs |= JSPROP_READONLY;
-        }
-
-        if (desc.hasValue())
-            v = desc.value();
-        attrs = (descAttrs & changed) | (shapeAttributes & ~changed);
-        getter = nullptr;
-        setter = nullptr;
-    } else {
-        MOZ_ASSERT(desc.isAccessorDescriptor());
-
-        /* 8.12.9 step 12. */
-        unsigned changed = 0;
-        if (desc.hasConfigurable())
-            changed |= JSPROP_PERMANENT;
-        if (desc.hasEnumerable())
-            changed |= JSPROP_ENUMERATE;
-        if (desc.hasGetterObject())
-            changed |= JSPROP_GETTER | JSPROP_SHARED | JSPROP_READONLY | JSPROP_SHADOWABLE;
-        if (desc.hasSetterObject())
-            changed |= JSPROP_SETTER | JSPROP_SHARED | JSPROP_READONLY | JSPROP_SHADOWABLE;
-
-        attrs = (desc.attributes() & changed) | (shapeAttributes & ~changed);
-        if (desc.hasGetterObject())
-            getter = desc.getter();
-        else
-            getter = shapeHasGetterValue ? shape->getter() : nullptr;
-        if (desc.hasSetterObject())
-            setter = desc.setter();
-        else
-            setter = shapeHasSetterValue ? shape->setter() : nullptr;
-    }
-
-    /*
-     * Since "data" properties implemented using native C functions may rely on
-     * side effects during setting, we must make them aware that they have been
-     * "assigned"; deleting the property before redefining it does the trick.
-     * See bug 539766, where we ran into problems when we redefined
-     * arguments.length without making the property aware that its value had
-     * been changed (which would have happened if we had deleted it before
-     * redefining it or we had invoked its setter to change its value).
-     */
-    if (callDelProperty) {
-        ObjectOpResult ignored;
-        if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, ignored))
-            return false;
-    }
-
-    return NativeDefineProperty(cx, obj, id, v, getter, setter, attrs, result);
-}
-
-/* ES6 20130308 draft 8.4.2.1 [[DefineOwnProperty]] */
-static bool
-DefinePropertyOnArray(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
-                      Handle<PropertyDescriptor> desc, ObjectOpResult& result)
-{
-    /* Step 2. */
-    if (id == NameToId(cx->names().length)) {
-        // Canonicalize value, if necessary, before proceeding any further.  It
-        // would be better if this were always/only done by ArraySetLength.
-        // But canonicalization may throw a RangeError (or other exception, if
-        // the value is an object with user-defined conversion semantics)
-        // before other attributes are checked.  So as long as our internal
-        // defineProperty hook doesn't match the ECMA one, this duplicate
-        // checking can't be helped.
-        RootedValue v(cx);
-        if (desc.hasValue()) {
-            uint32_t newLen;
-            if (!CanonicalizeArrayLengthValue(cx, desc.value(), &newLen))
-                return false;
-            v.setNumber(newLen);
-        } else {
-            v.setNumber(arr->length());
-        }
-
-        if (desc.hasConfigurable() && desc.configurable())
-            return result.fail(JSMSG_CANT_REDEFINE_PROP);
-        if (desc.hasEnumerable() && desc.enumerable())
-            return result.fail(JSMSG_CANT_REDEFINE_PROP);
-
-        if (desc.isAccessorDescriptor())
-            return result.fail(JSMSG_CANT_REDEFINE_PROP);
-
-        unsigned attrs = arr->lookup(cx, id)->attributes();
-        if (!arr->lengthIsWritable()) {
-            if (desc.hasWritable() && desc.writable())
-                return result.fail(JSMSG_CANT_REDEFINE_PROP);
-        } else {
-            if (desc.hasWritable() && !desc.writable())
-                attrs = attrs | JSPROP_READONLY;
-        }
-
-        return ArraySetLength(cx, arr, id, attrs, v, result);
-    }
-
-    /* Step 3. */
-    uint32_t index;
-    if (IdIsIndex(id, &index)) {
-        /* Step 3b. */
-        uint32_t oldLen = arr->length();
-
-        /* Steps 3a, 3e. */
-        if (index >= oldLen && !arr->lengthIsWritable())
-            return result.fail(JSMSG_CANT_APPEND_TO_ARRAY);
-
-        /* Steps 3f-j. */
-        return DefinePropertyOnObject(cx, arr, id, desc, result);
-    }
-
-    /* Step 4. */
-    return DefinePropertyOnObject(cx, arr, id, desc, result);
-}
-
-// ES6 draft rev31 9.4.5.3 [[DefineOwnProperty]]
-static bool
-DefinePropertyOnTypedArray(JSContext* cx, HandleObject obj, HandleId id,
-                           Handle<PropertyDescriptor> desc, ObjectOpResult& result)
-{
-    MOZ_ASSERT(IsAnyTypedArray(obj));
-    // Steps 3.a-c.
-    uint64_t index;
-    if (IsTypedArrayIndex(id, &index))
-        return DefineTypedArrayElement(cx, obj, index, desc, result);
-
-    // Step 4.
-    return DefinePropertyOnObject(cx, obj.as<NativeObject>(), id, desc, result);
-}
-
-bool
-js::StandardDefineProperty(JSContext* cx, HandleObject obj, HandleId id,
-                           Handle<PropertyDescriptor> desc, ObjectOpResult& result)
-{
-    if (obj->is<ArrayObject>()) {
-        Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
-        return DefinePropertyOnArray(cx, arr, id, desc, result);
-    }
-
-    if (IsAnyTypedArray(obj))
-        return DefinePropertyOnTypedArray(cx, obj, id, desc, result);
-
-    if (obj->is<ProxyObject>()) {
-        Rooted<PropertyDescriptor> pd(cx, desc);
-        pd.object().set(obj);
-        return Proxy::defineProperty(cx, obj, id, pd, result);
-    }
-
-    if (obj->getOps()->defineProperty)
-        return obj->getOps()->defineProperty(cx, obj, id, desc, result);
-
-    return DefinePropertyOnObject(cx, obj.as<NativeObject>(), id, desc, result);
-}
-
-bool
-js::StandardDefineProperty(JSContext* cx, HandleObject obj, HandleId id,
-                           Handle<PropertyDescriptor> desc)
-{
-    ObjectOpResult success;
-    return StandardDefineProperty(cx, obj, id, desc, success) &&
-           success.checkStrict(cx, obj, id);
-}
+/*** PropertyDescriptor operations and DefineProperties ******************************************/
 
 bool
 CheckCallable(JSContext* cx, JSObject* obj, const char* fieldName)
 {
     if (obj && !obj->isCallable()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD,
                              fieldName);
         return false;
@@ -874,23 +466,24 @@ bool
 js::DefineProperties(JSContext* cx, HandleObject obj, HandleObject props)
 {
     AutoIdVector ids(cx);
     AutoPropertyDescriptorVector descs(cx);
     if (!ReadPropertyDescriptors(cx, props, true, &ids, &descs))
         return false;
 
     for (size_t i = 0, len = ids.length(); i < len; i++) {
-        if (!StandardDefineProperty(cx, obj, ids[i], descs[i]))
+        if (!DefineProperty(cx, obj, ids[i], descs[i]))
             return false;
     }
 
     return true;
 }
 
+
 /*** Seal and freeze *****************************************************************************/
 
 static unsigned
 GetSealedOrFrozenAttributes(unsigned attrs, IntegrityLevel level)
 {
     /* Make all attributes permanent; if freezing, make data attributes read-only. */
     if (level == IntegrityLevel::Frozen && !(attrs & (JSPROP_GETTER | JSPROP_SETTER)))
         return JSPROP_PERMANENT | JSPROP_READONLY;
@@ -983,25 +576,25 @@ js::SetIntegrityLevel(JSContext* cx, Han
                 // 9.a.iii.1-2
                 if (currentDesc.isAccessorDescriptor())
                     desc.setAttributes(AllowConfigure | JSPROP_PERMANENT);
                 else
                     desc.setAttributes(AllowConfigureAndWritable | JSPROP_PERMANENT | JSPROP_READONLY);
             }
 
             // 8.a.i-ii. / 9.a.iii.3-4
-            if (!StandardDefineProperty(cx, obj, id, desc))
+            if (!DefineProperty(cx, obj, id, desc))
                 return false;
         }
     }
 
     // Ordinarily ArraySetLength handles this, but we're going behind its back
     // right now, so we must do this manually.  Neither the custom property
-    // tree mutations nor the StandardDefineProperty call in the above code will
-    // do this for us.
+    // tree mutations nor the DefineProperty call in the above code will do
+    // this for us.
     //
     // ArraySetLength also implements the capacity <= length invariant for
     // arrays with non-writable length.  We don't need to do anything special
     // for that, because capacity was zeroed out by preventExtensions.  (See
     // the assertion before the if-else above.)
     if (level == IntegrityLevel::Frozen && obj->is<ArrayObject>()) {
         if (!obj->as<ArrayObject>().maybeCopyElementsForWrite(cx))
             return false;
@@ -1499,17 +1092,17 @@ JS_CopyPropertyFrom(JSContext* cx, Handl
         desc.attributesRef() &= ~JSPROP_PERMANENT;
     }
 
     JSAutoCompartment ac(cx, target);
     RootedId wrappedId(cx, id);
     if (!cx->compartment()->wrap(cx, &desc))
         return false;
 
-    return StandardDefineProperty(cx, target, wrappedId, desc);
+    return DefineProperty(cx, target, wrappedId, desc);
 }
 
 JS_FRIEND_API(bool)
 JS_CopyPropertiesFrom(JSContext* cx, HandleObject target, HandleObject obj)
 {
     JSAutoCompartment ac(cx, obj);
 
     AutoIdVector props(cx);
@@ -3003,16 +2596,24 @@ js::GetOwnPropertyDescriptor(JSContext* 
     }
 
     desc.object().set(nobj);
     desc.assertComplete();
     return true;
 }
 
 bool
+js::DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle<PropertyDescriptor> desc)
+{
+    ObjectOpResult result;
+    return DefineProperty(cx, obj, id, desc, result) &&
+           result.checkStrict(cx, obj, id);
+}
+
+bool
 js::DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle<PropertyDescriptor> desc,
                    ObjectOpResult& result)
 {
     desc.assertValid();
     if (DefinePropertyOp op = obj->getOps()->defineProperty)
         return op(cx, obj, id, desc, result);
     return NativeDefineProperty(cx, obj.as<NativeObject>(), id, desc, result);
 }
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -305,17 +305,19 @@ class JSObject : public js::gc::Cell
     MOZ_ALWAYS_INLINE JS::Zone* zoneFromAnyThread() const {
         return group_->zoneFromAnyThread();
     }
     MOZ_ALWAYS_INLINE JS::shadow::Zone* shadowZoneFromAnyThread() const {
         return JS::shadow::Zone::asShadowZone(zoneFromAnyThread());
     }
     static MOZ_ALWAYS_INLINE void readBarrier(JSObject* obj);
     static MOZ_ALWAYS_INLINE void writeBarrierPre(JSObject* obj);
-    static MOZ_ALWAYS_INLINE void writeBarrierPost(void* cellp, JSObject* prev, JSObject* next);
+    static MOZ_ALWAYS_INLINE void writeBarrierPost(JSObject* obj, void* cellp);
+    static MOZ_ALWAYS_INLINE void writeBarrierPostRelocate(JSObject* obj, void* cellp);
+    static MOZ_ALWAYS_INLINE void writeBarrierPostRemove(JSObject* obj, void* cellp);
 
     /* Return the allocKind we would use if we were to tenure this object. */
     js::gc::AllocKind allocKindForTenure(const js::Nursery& nursery) const;
 
     size_t tenuredSizeOfThis() const {
         MOZ_ASSERT(isTenured());
         return js::gc::Arena::thingSize(asTenured().getAllocKind());
     }
@@ -623,36 +625,46 @@ JSObject::readBarrier(JSObject* obj)
 /* static */ MOZ_ALWAYS_INLINE void
 JSObject::writeBarrierPre(JSObject* obj)
 {
     if (!isNullLike(obj) && obj->isTenured())
         obj->asTenured().writeBarrierPre(&obj->asTenured());
 }
 
 /* static */ MOZ_ALWAYS_INLINE void
-JSObject::writeBarrierPost(void* cellp, JSObject* prev, JSObject* next)
+JSObject::writeBarrierPost(JSObject* obj, void* cellp)
+{
+    MOZ_ASSERT(cellp);
+    if (IsNullTaggedPointer(obj))
+        return;
+    MOZ_ASSERT(obj == *static_cast<JSObject**>(cellp));
+    js::gc::StoreBuffer* storeBuffer = obj->storeBuffer();
+    if (storeBuffer)
+        storeBuffer->putCellFromAnyThread(static_cast<js::gc::Cell**>(cellp));
+}
+
+/* static */ MOZ_ALWAYS_INLINE void
+JSObject::writeBarrierPostRelocate(JSObject* obj, void* cellp)
 {
     MOZ_ASSERT(cellp);
+    MOZ_ASSERT(obj);
+    MOZ_ASSERT(obj == *static_cast<JSObject**>(cellp));
+    js::gc::StoreBuffer* storeBuffer = obj->storeBuffer();
+    if (storeBuffer)
+        storeBuffer->putRelocatableCellFromAnyThread(static_cast<js::gc::Cell**>(cellp));
+}
 
-    // If the target needs an entry, add it.
-    js::gc::StoreBuffer* buffer;
-    if (!IsNullTaggedPointer(next) && (buffer = next->storeBuffer())) {
-        // If we know that the prev has already inserted an entry, we can skip
-        // doing the lookup to add the new entry.
-        if (!IsNullTaggedPointer(prev) && prev->storeBuffer()) {
-            buffer->assertHasCellEdge(static_cast<js::gc::Cell**>(cellp));
-            return;
-        }
-        buffer->putCellFromAnyThread(static_cast<js::gc::Cell**>(cellp));
-        return;
-    }
-
-    // Remove the prev entry if the new value does not need it.
-    if (!IsNullTaggedPointer(prev) && (buffer = prev->storeBuffer()))
-        buffer->unputCellFromAnyThread(static_cast<js::gc::Cell**>(cellp));
+/* static */ MOZ_ALWAYS_INLINE void
+JSObject::writeBarrierPostRemove(JSObject* obj, void* cellp)
+{
+    MOZ_ASSERT(cellp);
+    MOZ_ASSERT(obj);
+    MOZ_ASSERT(obj == *static_cast<JSObject**>(cellp));
+    obj->shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->removeRelocatableCellFromAnyThread(
+        static_cast<js::gc::Cell**>(cellp));
 }
 
 namespace js {
 
 inline bool
 IsCallable(const Value& v)
 {
     return v.isObject() && v.toObject().isCallable();
@@ -751,39 +763,17 @@ PreventExtensions(JSContext* cx, HandleO
  *
  * If no such property exists on obj, return true with desc.object() set to
  * null.
  */
 extern bool
 GetOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
                          MutableHandle<PropertyDescriptor> desc);
 
-/*
- * ES6 [[DefineOwnProperty]]. Define a property on obj.
- *
- * If obj is an array, this follows ES5 15.4.5.1.
- * If obj is any other native object, this follows ES5 8.12.9.
- * If obj is a proxy, this calls the proxy handler's defineProperty method.
- * Otherwise, this reports an error and returns false.
- *
- * Both StandardDefineProperty functions hew close to the ES5 spec. Note that
- * the DefineProperty functions do not enforce some invariants mandated by ES6.
- */
-extern bool
-StandardDefineProperty(JSContext* cx, HandleObject obj, HandleId id,
-                       Handle<PropertyDescriptor> descriptor, ObjectOpResult& result);
-
-/*
- * Same as above except without the ObjectOpResult out-parameter. Throws a
- * TypeError on failure.
- */
-extern bool
-StandardDefineProperty(JSContext* cx, HandleObject obj, HandleId id,
-                       Handle<PropertyDescriptor> desc);
-
+/* ES6 [[DefineOwnProperty]]. Define a property on obj. */
 extern bool
 DefineProperty(JSContext* cx, HandleObject obj, HandleId id,
                Handle<PropertyDescriptor> desc, ObjectOpResult& result);
 
 extern bool
 DefineProperty(ExclusiveContext* cx, HandleObject obj, HandleId id, HandleValue value,
                JSGetterOp getter, JSSetterOp setter, unsigned attrs, ObjectOpResult& result);
 
@@ -795,16 +785,19 @@ extern bool
 DefineElement(ExclusiveContext* cx, HandleObject obj, uint32_t index, HandleValue value,
               JSGetterOp getter, JSSetterOp setter, unsigned attrs, ObjectOpResult& result);
 
 /*
  * When the 'result' out-param is omitted, the behavior is the same as above, except
  * that any failure results in a TypeError.
  */
 extern bool
+DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle<PropertyDescriptor> desc);
+
+extern bool
 DefineProperty(ExclusiveContext* cx, HandleObject obj, HandleId id, HandleValue value,
                JSGetterOp getter = nullptr,
                JSSetterOp setter = nullptr,
                unsigned attrs = JSPROP_ENUMERATE);
 
 extern bool
 DefineProperty(ExclusiveContext* cx, HandleObject obj, PropertyName* name, HandleValue value,
                JSGetterOp getter = nullptr,
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -714,17 +714,17 @@ Walk(JSContext* cx, HandleObject holder,
                 if (newElement.isUndefined()) {
                     /* Step 2a(iii)(2). The spec deliberately ignores strict failure. */
                     if (!DeleteProperty(cx, obj, id, ignored))
                         return false;
                 } else {
                     /* Step 2a(iii)(3). The spec deliberately ignores strict failure. */
                     Rooted<PropertyDescriptor> desc(cx);
                     desc.setDataDescriptor(newElement, JSPROP_ENUMERATE);
-                    if (!StandardDefineProperty(cx, obj, id, desc, ignored))
+                    if (!DefineProperty(cx, obj, id, desc, ignored))
                         return false;
                 }
             }
         } else {
             /* Step 2b(i). */
             AutoIdVector keys(cx);
             if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &keys))
                 return false;
@@ -742,17 +742,17 @@ Walk(JSContext* cx, HandleObject holder,
                 if (newElement.isUndefined()) {
                     /* Step 2b(ii)(2). The spec deliberately ignores strict failure. */
                     if (!DeleteProperty(cx, obj, id, ignored))
                         return false;
                 } else {
                     /* Step 2b(ii)(3). The spec deliberately ignores strict failure. */
                     Rooted<PropertyDescriptor> desc(cx);
                     desc.setDataDescriptor(newElement, JSPROP_ENUMERATE);
-                    if (!StandardDefineProperty(cx, obj, id, desc, ignored))
+                    if (!DefineProperty(cx, obj, id, desc, ignored))
                         return false;
                 }
             }
         }
     }
 
     /* Step 3. */
     RootedString key(cx, IdToString(cx, name));
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -209,17 +209,17 @@ typedef InternalHandle<Bindings*> Intern
  * both function and top-level scripts (the latter is needed to track names in
  * strict mode eval code, to give such code its own lexical environment).
  */
 class Bindings
 {
     friend class BindingIter;
     friend class AliasedFormalIter;
 
-    HeapPtrShape callObjShape_;
+    RelocatablePtrShape callObjShape_;
     uintptr_t bindingArrayAndFlag_;
     uint16_t numArgs_;
     uint16_t numBlockScoped_;
     uint16_t numBodyLevelLexicals_;
     uint16_t aliasedBodyLevelLexicalBegin_;
     uint16_t numUnaliasedBodyLevelLexicals_;
     uint32_t numVars_;
     uint32_t numUnaliasedVars_;
--- a/js/src/proxy/DirectProxyHandler.cpp
+++ b/js/src/proxy/DirectProxyHandler.cpp
@@ -34,17 +34,17 @@ DirectProxyHandler::getOwnPropertyDescri
 
 bool
 DirectProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
                                    Handle<PropertyDescriptor> desc,
                                    ObjectOpResult& result) const
 {
     assertEnteredPolicy(cx, proxy, id, SET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
-    return StandardDefineProperty(cx, target, id, desc, result);
+    return DefineProperty(cx, target, id, desc, result);
 }
 
 bool
 DirectProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy,
                                     AutoIdVector& props) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
--- a/js/src/proxy/ScriptedDirectProxyHandler.cpp
+++ b/js/src/proxy/ScriptedDirectProxyHandler.cpp
@@ -543,17 +543,17 @@ ScriptedDirectProxyHandler::defineProper
 
     // steps 6-7
     RootedValue trap(cx);
     if (!GetProperty(cx, handler, handler, cx->names().defineProperty, &trap))
         return false;
 
     // step 8
     if (trap.isUndefined())
-        return StandardDefineProperty(cx, target, id, desc, result);
+        return DefineProperty(cx, target, id, desc, result);
 
     // step 9
     RootedValue descObj(cx);
     if (!FromPropertyDescriptorToObject(cx, desc, &descObj))
         return false;
 
     // steps 10-11
     RootedValue propKey(cx);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2481,17 +2481,17 @@ Debugger::trace(JSTracer* trc)
      * Mark Debugger.Frame objects. These are all reachable from JS, because the
      * corresponding JS frames are still on the stack.
      *
      * (Once we support generator frames properly, we will need
      * weakly-referenced Debugger.Frame objects as well, for suspended generator
      * frames.)
      */
     for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
-        HeapPtrNativeObject& frameobj = r.front().value();
+        RelocatablePtrNativeObject& frameobj = r.front().value();
         MOZ_ASSERT(MaybeForwarded(frameobj.get())->getPrivate());
         TraceEdge(trc, &frameobj, "live Debugger.Frame");
     }
 
     /*
      * Mark every allocation site in our allocation log.
      */
     for (AllocationSite* s = allocationsLog.getFirst(); s; s = s->getNext()) {
@@ -7015,17 +7015,17 @@ DebuggerObject_defineProperty(JSContext*
 
     {
         Maybe<AutoCompartment> ac;
         ac.emplace(cx, obj);
         if (!cx->compartment()->wrap(cx, &desc))
             return false;
 
         ErrorCopier ec(ac);
-        if (!StandardDefineProperty(cx, obj, id, desc))
+        if (!DefineProperty(cx, obj, id, desc))
             return false;
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
@@ -7058,17 +7058,17 @@ DebuggerObject_defineProperties(JSContex
         ac.emplace(cx, obj);
         for (size_t i = 0; i < n; i++) {
             if (!cx->compartment()->wrap(cx, descs[i]))
                 return false;
         }
 
         ErrorCopier ec(ac);
         for (size_t i = 0; i < n; i++) {
-            if (!StandardDefineProperty(cx, obj, ids[i], descs[i]))
+            if (!DefineProperty(cx, obj, ids[i], descs[i]))
                 return false;
         }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -59,21 +59,21 @@ typedef HashSet<ReadBarrieredGlobalObjec
  * and compartments may be unnecessarily grouped, however it results in a
  * simpler and faster implementation.
  *
  * If InvisibleKeysOk is true, then the map can have keys in invisible-to-
  * debugger compartments. If it is false, we assert that such entries are never
  * created.
  */
 template <class UnbarrieredKey, bool InvisibleKeysOk=false>
-class DebuggerWeakMap : private WeakMap<PreBarriered<UnbarrieredKey>, HeapPtrObject>
+class DebuggerWeakMap : private WeakMap<PreBarriered<UnbarrieredKey>, RelocatablePtrObject>
 {
   private:
     typedef PreBarriered<UnbarrieredKey> Key;
-    typedef HeapPtrObject Value;
+    typedef RelocatablePtrObject Value;
 
     typedef HashMap<JS::Zone*,
                     uintptr_t,
                     DefaultHasher<JS::Zone*>,
                     RuntimeAllocPolicy> CountMap;
 
     CountMap zoneCounts;
 
@@ -281,20 +281,20 @@ class Debugger : private mozilla::Linked
               ctorName(nullptr)
         {
             MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>());
         };
 
         static AllocationSite* create(JSContext* cx, HandleObject frame, double when,
                                       HandleObject obj);
 
-        HeapPtrObject frame;
+        RelocatablePtrObject frame;
         double when;
         const char* className;
-        HeapPtrAtom ctorName;
+        RelocatablePtrAtom ctorName;
     };
     typedef mozilla::LinkedList<AllocationSite> AllocationSiteList;
 
     bool allowUnobservedAsmJS;
     bool trackingAllocationSites;
     double allocationSamplingProbability;
     AllocationSiteList allocationsLog;
     size_t allocationsLogLength;
@@ -357,17 +357,17 @@ class Debugger : private mozilla::Linked
      * removeDebuggee.
      *
      * We don't trace the keys of this map (the frames are on the stack and
      * thus necessarily live), but we do trace the values. It's like a WeakMap
      * that way, but since stack frames are not gc-things, the implementation
      * has to be different.
      */
     typedef HashMap<AbstractFramePtr,
-                    HeapPtrNativeObject,
+                    RelocatablePtrNativeObject,
                     DefaultHasher<AbstractFramePtr>,
                     RuntimeAllocPolicy> FrameMap;
     FrameMap frames;
 
     /* An ephemeral map from JSScript* to Debugger.Script instances. */
     typedef DebuggerWeakMap<JSScript*> ScriptWeakMap;
     ScriptWeakMap scripts;
 
--- a/js/src/vm/DebuggerMemory.cpp
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -213,19 +213,19 @@ DebuggerMemory::drainAllocationsLog(JSCo
         RootedValue ctorName(cx, NullValue());
         if (allocSite->ctorName)
             ctorName.setString(allocSite->ctorName);
         if (!DefineProperty(cx, obj, cx->names().constructor, ctorName))
             return false;
 
         result->setDenseElement(i, ObjectValue(*obj));
 
-        // Pop the front queue entry, and delete it immediately, so that the GC
-        // sees the AllocationSite's HeapPtr barriers run atomically with the
-        // change to the graph (the queue link).
+        // Pop the front queue entry, and delete it immediately, so that
+        // the GC sees the AllocationSite's RelocatablePtr barriers run
+        // atomically with the change to the graph (the queue link).
         MOZ_ALWAYS_TRUE(dbg->allocationsLog.popFirst() == allocSite);
         js_delete(allocSite);
     }
 
     dbg->allocationsLogOverflowed = false;
     dbg->allocationsLogLength = 0;
     args.rval().setObject(*result);
     return true;
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -115,29 +115,29 @@ class RegExpShared
   private:
     friend class RegExpCompartment;
     friend class RegExpStatics;
 
     typedef frontend::TokenStream TokenStream;
 
     struct RegExpCompilation
     {
-        HeapPtrJitCode jitCode;
+        RelocatablePtrJitCode jitCode;
         uint8_t* byteCode;
 
         RegExpCompilation() : byteCode(nullptr) {}
         ~RegExpCompilation() { js_free(byteCode); }
 
         bool compiled(ForceByteCodeEnum force = DontForceByteCode) const {
             return byteCode || (force == DontForceByteCode && jitCode);
         }
     };
 
     /* Source to the RegExp, for lazy compilation. */
-    HeapPtrAtom        source;
+    RelocatablePtrAtom source;
 
     RegExpFlag         flags;
     size_t             parenCount;
     bool               canStringMatch;
     bool               marked_;
 
     RegExpCompilation  compilationArray[4];
 
--- a/js/src/vm/RegExpStatics.h
+++ b/js/src/vm/RegExpStatics.h
@@ -16,29 +16,29 @@ namespace js {
 
 class GlobalObject;
 class RegExpStaticsObject;
 
 class RegExpStatics
 {
     /* The latest RegExp output, set after execution. */
     VectorMatchPairs        matches;
-    HeapPtrLinearString     matchesInput;
+    RelocatablePtrLinearString matchesInput;
 
     /*
      * The previous RegExp input, used to resolve lazy state.
      * A raw RegExpShared cannot be stored because it may be in
      * a different compartment via evalcx().
      */
-    HeapPtrAtom             lazySource;
+    RelocatablePtrAtom      lazySource;
     RegExpFlag              lazyFlags;
     size_t                  lazyIndex;
 
     /* The latest RegExp input, set before execution. */
-    HeapPtrString           pendingInput;
+    RelocatablePtrString    pendingInput;
     RegExpFlag              flags;
 
     /*
      * If non-zero, |matchesInput| and the |lazy*| fields may be used
      * to replay the last executed RegExp, and |matches| is invalid.
      */
     int32_t                 pendingLazyEvaluation;
 
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -342,18 +342,18 @@ class NewObjectCache
         entry->kind = kind;
 
         entry->nbytes = gc::Arena::thingSize(kind);
         js_memcpy(&entry->templateObject, obj, entry->nbytes);
     }
 
     static void copyCachedToObject(NativeObject* dst, NativeObject* src, gc::AllocKind kind) {
         js_memcpy(dst, src, gc::Arena::thingSize(kind));
-        Shape::writeBarrierPost(&dst->shape_, nullptr, dst->shape_);
-        ObjectGroup::writeBarrierPost(&dst->group_, nullptr, dst->group_);
+        Shape::writeBarrierPost(dst->shape_, &dst->shape_);
+        ObjectGroup::writeBarrierPost(dst->group_, &dst->group_);
     }
 };
 
 /*
  * A FreeOp can do one thing: free memory. For convenience, it has delete_
  * convenience methods that also call destructors.
  *
  * FreeOp is passed to finalizers and other sweep-phase hooks so that we do not
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -863,17 +863,17 @@ class MissingScopeKey
 
 // The value in LiveScopeMap, mapped from by live scope objects.
 class LiveScopeVal
 {
     friend class DebugScopes;
     friend class MissingScopeKey;
 
     AbstractFramePtr frame_;
-    HeapPtrObject staticScope_;
+    RelocatablePtrObject staticScope_;
 
     void sweep();
     static void staticAsserts();
 
   public:
     explicit LiveScopeVal(const ScopeIter& si)
       : frame_(si.initialFrame()),
         staticScope_(si.maybeStaticScope())
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -344,17 +344,17 @@ js::intrinsic_DefineDataProperty(JSConte
     } else {
         // If the fourth argument is unspecified, the attributes are for a
         // plain data property.
         attrs = JSPROP_ENUMERATE;
     }
 
     Rooted<PropertyDescriptor> desc(cx);
     desc.setDataDescriptor(value, attrs);
-    if (!StandardDefineProperty(cx, obj, id, desc))
+    if (!DefineProperty(cx, obj, id, desc))
         return false;
 
     args.rval().setUndefined();
     return true;
 }
 
 bool
 js::intrinsic_UnsafeSetReservedSlot(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -102,16 +102,35 @@ InterpreterFrame::initExecuteFrame(JSCon
     if (script->isDebuggee())
         setIsDebuggee();
 
 #ifdef DEBUG
     Debug_SetValueRangeToCrashOnTouch(&rval_, 1);
 #endif
 }
 
+void
+InterpreterFrame::writeBarrierPost()
+{
+    /* This needs to follow the same rules as in InterpreterFrame::mark. */
+    if (scopeChain_)
+        JSObject::writeBarrierPost(scopeChain_, &scopeChain_);
+    if (flags_ & HAS_ARGS_OBJ)
+        JSObject::writeBarrierPost(argsObj_, &argsObj_);
+    if (isFunctionFrame()) {
+        JSFunction::writeBarrierPost(exec.fun, &exec.fun);
+        if (isEvalFrame())
+            JSScript::writeBarrierPost(u.evalScript, &u.evalScript);
+    } else {
+        JSScript::writeBarrierPost(exec.script, &exec.script);
+    }
+    if (hasReturnValue())
+        HeapValue::writeBarrierPost(rval_, &rval_);
+}
+
 bool
 InterpreterFrame::copyRawFrameSlots(AutoValueVector* vec)
 {
     if (!vec->resize(numFormalArgs() + script()->nfixed()))
         return false;
     PodCopy(vec->begin(), argv(), numFormalArgs());
     PodCopy(vec->begin() + numFormalArgs(), slots(), script()->nfixed());
     return true;
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -376,16 +376,18 @@ class InterpreterFrame
     Value*              argv_;         /* If hasArgs(), points to frame's arguments. */
     LifoAlloc::Mark     mark_;          /* Used to release memory for this frame. */
 
     static void staticAsserts() {
         JS_STATIC_ASSERT(offsetof(InterpreterFrame, rval_) % sizeof(Value) == 0);
         JS_STATIC_ASSERT(sizeof(InterpreterFrame) % sizeof(Value) == 0);
     }
 
+    void writeBarrierPost();
+
     /*
      * The utilities are private since they are not able to assert that only
      * unaliased vars/formals are accessed. Normal code should prefer the
      * InterpreterFrame::unaliased* members (or InterpreterRegs::stackDepth for
      * the usual "depth is at least" assertions).
      */
     Value* slots() const { return (Value*)(this + 1); }
     Value* base() const { return slots() + script()->nfixed(); }
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -799,17 +799,17 @@ class PreliminaryObjectArray
     }
 
     bool full() const;
     void sweep();
 };
 
 class PreliminaryObjectArrayWithTemplate : public PreliminaryObjectArray
 {
-    HeapPtrShape shape_;
+    RelocatablePtrShape shape_;
 
   public:
     explicit PreliminaryObjectArrayWithTemplate(Shape* shape)
       : shape_(shape)
     {}
 
     void clear() {
         shape_.init(nullptr);
@@ -876,50 +876,50 @@ class TypeNewScript
         uint32_t offset;
         Initializer(Kind kind, uint32_t offset)
           : kind(kind), offset(offset)
         {}
     };
 
   private:
     // Scripted function which this information was computed for.
-    HeapPtrFunction function_;
+    RelocatablePtrFunction function_;
 
     // Any preliminary objects with the type. The analyses are not performed
     // until this array is cleared.
     PreliminaryObjectArray* preliminaryObjects;
 
     // After the new script properties analyses have been performed, a template
     // object to use for newly constructed objects. The shape of this object
     // reflects all definite properties the object will have, and the
     // allocation kind to use. This is null if the new objects have an unboxed
     // layout, in which case the UnboxedLayout provides the initial structure
     // of the object.
-    HeapPtrPlainObject templateObject_;
+    RelocatablePtrPlainObject templateObject_;
 
     // Order in which definite properties become initialized. We need this in
     // case the definite properties are invalidated (such as by adding a setter
     // to an object on the prototype chain) while an object is in the middle of
     // being initialized, so we can walk the stack and fixup any objects which
     // look for in-progress objects which were prematurely set with an incorrect
     // shape. Property assignments in inner frames are preceded by a series of
     // SETPROP_FRAME entries specifying the stack down to the frame containing
     // the write.
     Initializer* initializerList;
 
     // If there are additional properties found by the acquired properties
     // analysis which were not found by the definite properties analysis, this
     // shape contains all such additional properties (plus the definite
     // properties). When an object of this group acquires this shape, it is
     // fully initialized and its group can be changed to initializedGroup.
-    HeapPtrShape initializedShape_;
+    RelocatablePtrShape initializedShape_;
 
     // Group with definite properties set for all properties found by
     // both the definite and acquired properties analyses.
-    HeapPtrObjectGroup initializedGroup_;
+    RelocatablePtrObjectGroup initializedGroup_;
 
   public:
     TypeNewScript() { mozilla::PodZero(this); }
     ~TypeNewScript() {
         js_delete(preliminaryObjects);
         js_free(initializerList);
     }
 
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -604,17 +604,17 @@ UnboxedPlainObject::convertToNative(JSCo
         Rooted<UnboxedExpandoObject*> nexpando(cx, expando);
         RootedId id(cx);
         Rooted<PropertyDescriptor> desc(cx);
         for (size_t i = 0; i < ids.length(); i++) {
             id = ids[i];
             if (!GetOwnPropertyDescriptor(cx, nexpando, id, &desc))
                 return false;
             ObjectOpResult result;
-            if (!StandardDefineProperty(cx, nobj, id, desc, result))
+            if (!DefineProperty(cx, nobj, id, desc, result))
                 return false;
             MOZ_ASSERT(result.ok());
         }
     }
 
     return true;
 }
 
--- a/js/src/vm/WeakMapObject.h
+++ b/js/src/vm/WeakMapObject.h
@@ -7,21 +7,21 @@
 #ifndef vm_WeakMapObject_h
 #define vm_WeakMapObject_h
 
 #include "jsobj.h"
 #include "jsweakmap.h"
 
 namespace js {
 
-class ObjectValueMap : public WeakMap<PreBarrieredObject, HeapValue>
+class ObjectValueMap : public WeakMap<PreBarrieredObject, RelocatableValue>
 {
   public:
     ObjectValueMap(JSContext* cx, JSObject* obj)
-      : WeakMap<PreBarrieredObject, HeapValue>(cx, obj) {}
+      : WeakMap<PreBarrieredObject, RelocatableValue>(cx, obj) {}
 
     virtual bool findZoneEdges();
 };
 
 class WeakMapObject : public NativeObject
 {
   public:
     static const Class class_;
--- a/layout/base/nsCSSRenderingBorders.cpp
+++ b/layout/base/nsCSSRenderingBorders.cpp
@@ -1067,20 +1067,16 @@ nsCSSBorderRenderer::CreateCornerGradien
   aPoint1 = Point(pat1.x, pat1.y);
   aPoint2 = Point(pat2.x, pat2.y);
 
   Color firstColor = Color::FromABGR(aFirstColor);
   Color secondColor = Color::FromABGR(aSecondColor);
 
   nsTArray<gfx::GradientStop> rawStops(2);
   rawStops.SetLength(2);
-  // This is only guaranteed to give correct (and in some cases more correct)
-  // rendering with the Direct2D Azure and Quartz Cairo backends. For other
-  // cairo backends it could create un-antialiased border corner transitions
-  // since that at least used to be pixman's behaviour for hard stops.
   rawStops[0].color = firstColor;
   rawStops[0].offset = 0.5;
   rawStops[1].color = secondColor;
   rawStops[1].offset = 0.5;
   RefPtr<GradientStops> gs =
     gfxGradientCache::GetGradientStops(aDT, rawStops, ExtendMode::CLAMP);
   if (!gs) {
     // Having two corners, both with reversed color stops is pretty common
--- a/layout/reftests/bugs/664127-1.css
+++ b/layout/reftests/bugs/664127-1.css
@@ -1,11 +1,11 @@
 /* This external stylesheet is needed to be able to use a cached image
    (solidblue.png) through a relative path */
 tree {
-  height: 100px; -moz-appearance: none; border: none;
+  height: 100px; -moz-appearance: none; border: none; background-color: transparent;
 }
 treecol, treecolpicker {
   visibility: hidden;
 }
 treechildren::-moz-tree-image {
   list-style-image: url('solidblue.png');
 }
--- a/layout/reftests/bugs/668319-1.xul
+++ b/layout/reftests/bugs/668319-1.xul
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml-stylesheet type="text/css" href="data:text/css,
-tree { height: 100px; -moz-appearance: none; border: none; }
+tree { height: 100px; -moz-appearance: none; border: none; background-color: transparent; }
 treecol, treecolpicker { visibility: hidden; }
 treechildren::-moz-tree-cell-text { opacity: 0; }
 "?>
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <tree>
     <treecols>
       <treecol flex="1"/>
     </treecols>
--- a/layout/reftests/bugs/966992-1-ref.html
+++ b/layout/reftests/bugs/966992-1-ref.html
@@ -10,16 +10,17 @@ font-face {
 
         html,body {
             color:black; background-color:white; font:16px DejaVuSansMono!important; padding:0; margin:7px;
         }
 
     input {
       width: 100px; padding:50px; -moz-appearance:none; overflow-clip-box:padding-box;
       border: 3px solid black;
+      background-color: white;
     }
     textarea, #textarea {
       width: 160px; height:110px; padding:40px; overflow:scroll; -moz-appearance:none; overflow-clip-box:padding-box;
       border: 3px solid black;
     }
     #textarea { word-break: break-all; font:14px DejaVuSansMono!important; }
 
 
--- a/layout/reftests/bugs/966992-1.html
+++ b/layout/reftests/bugs/966992-1.html
@@ -10,16 +10,17 @@ font-face {
 
         html,body {
             color:black; background-color:white; font:16px DejaVuSansMono!important; padding:0; margin:7px;
         }
 
     input {
       width: 100px; padding:50px; -moz-appearance:none; overflow-clip-box:content-box;
       border: 3px solid black;
+      background-color: white;
     }
     textarea {
       width: 160px; height:110px; padding:40px; overflow:scroll; -moz-appearance:none; overflow-clip-box:content-box;
       border: 3px solid black;font:14px DejaVuSansMono!important;
     }
 
 p {
  position:absolute;
--- a/layout/reftests/css-parsing/reftest.list
+++ b/layout/reftests/css-parsing/reftest.list
@@ -1,7 +1,8 @@
 == at-rule-013.html at-rule-013-ref.html
 == invalid-url-handling.xhtml invalid-url-handling-ref.xhtml
 == pseudo-elements-1.html pseudo-elements-1-ref.html
 == invalid-attr-1.html invalid-attr-1-ref.html
 == at-rule-error-handling-import-1.html at-rule-error-handling-ref.html
 == at-rule-error-handling-media-1.html at-rule-error-handling-ref.html
 == invalid-font-face-descriptor-1.html invalid-font-face-descriptor-1-ref.html
+== two-dash-identifiers.html two-dash-identifiers-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-parsing/two-dash-identifiers-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+  div { color: green; }
+</style>
+<div>This should be green.</div>
+<div>This should be green.</div>
+<div>This should be green.</div>
+<div>This should be green.</div>
+<div>This should be green.</div>
+<div>This should be green.</div>
+<div>This should be green.</div>
+<div>This should be green.</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-parsing/two-dash-identifiers.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<style>
+  .ok { color: red; }
+  #-- { color: green; }
+  #-foo { color: green }
+  #--foo { color: green; }
+  .-- { color: green; }
+  .-foo { color: green; }
+  .--foo { color: green; }
+
+  .fail { color: green; }
+  #- { color: red; }
+  .- { color: red; }
+</style>
+<div class="ok" id="--">This should be green.</div>
+<div class="ok" id="-foo">This should be green.</div>
+<div class="ok" id="--foo">This should be green.</div>
+<div class="ok --">This should be green.</div>
+<div class="ok -foo">This should be green.</div>
+<div class="ok --foo">This should be green.</div>
+<div class="fail" id="-">This should be green.</div>
+<div class="fail -">This should be green.</div>
--- a/layout/reftests/text-overflow/reftest.list
+++ b/layout/reftests/text-overflow/reftest.list
@@ -1,14 +1,14 @@
 skip-if(B2G||Mulet) == ellipsis-font-fallback.html ellipsis-font-fallback-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == line-clipping.html line-clipping-ref.html
 fuzzy-if(Android,16,244) skip-if(B2G||Mulet) HTTP(..) == marker-basic.html marker-basic-ref.html  # Bug 1128229 # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) HTTP(..) == marker-string.html marker-string-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(Android||B2G) HTTP(..) == bidi-simple.html bidi-simple-ref.html # Fails on Android due to anti-aliasing
-skip-if(!gtkWidget) fuzzy-if(gtkWidget,1,104) HTTP(..) == bidi-simple-scrolled.html bidi-simple-scrolled-ref.html # Fails on Windows and OSX 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
 skip-if(B2G||Mulet) fuzzy-if(Android&&AndroidVersion<15,9,2545) fuzzy-if(Android&&AndroidVersion>=15,24,4000) fuzzy-if(cocoaWidget,1,40) fuzzy-if(asyncPanZoom&&!layersGPUAccelerated,102,1770) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264 # Initial mulet triage: parity with B2G/B2G Desktop
 fuzzy-if(OSX==1008,1,1) HTTP(..) == anonymous-block.html anonymous-block-ref.html
 skip-if(B2G||Mulet) HTTP(..) == false-marker-overlap.html false-marker-overlap-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 HTTP(..) == visibility-hidden.html visibility-hidden-ref.html
 skip-if(B2G||Mulet) fuzzy-if(asyncPanZoom&&!layersGPUAccelerated,102,1724) HTTP(..) == block-padding.html block-padding-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 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
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -679,18 +679,18 @@ CSS_PROP_LOGICAL(
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_STORES_CALC |
         CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
         CSS_PROPERTY_ALWAYS_ENABLED_IN_UA_SHEETS |
         CSS_PROPERTY_LOGICAL |
         CSS_PROPERTY_LOGICAL_AXIS |
         CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
     "layout.css.vertical-text.enabled",
-    VARIANT_AHKLP | VARIANT_CALC,
-    kWidthKTable,
+    VARIANT_AHLP | VARIANT_CALC,
+    nullptr,
     Size,
     Position,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_SHORTHAND(
     border,
     border,
     Border,
@@ -2119,18 +2119,18 @@ CSS_PROP_POSITION(
     height,
     Height,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_STORES_CALC |
         CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
         CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
     "",
-    VARIANT_AHLP | VARIANT_CALC,
-    nullptr,
+    VARIANT_AHKLP | VARIANT_CALC,
+    kWidthKTable,
     offsetof(nsStylePosition, mHeight),
     eStyleAnimType_Coord)
 CSS_PROP_VISIBILITY(
     image-orientation,
     image_orientation,
     ImageOrientation,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_PARSER_FUNCTION,
@@ -2422,34 +2422,34 @@ CSS_PROP_LOGICAL(
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_STORES_CALC |
         CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
         CSS_PROPERTY_ALWAYS_ENABLED_IN_UA_SHEETS |
         CSS_PROPERTY_LOGICAL |
         CSS_PROPERTY_LOGICAL_AXIS |
         CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
     "layout.css.vertical-text.enabled",
-    VARIANT_HKLPO | VARIANT_CALC,
-    kWidthKTable,
+    VARIANT_HLPO | VARIANT_CALC,
+    nullptr,
     MaxSize,
     Position,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_POSITION(
     max-height,
     max_height,
     MaxHeight,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_STORES_CALC |
         CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
         CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
     "",
-    VARIANT_HLPO | VARIANT_CALC,
-    nullptr,
+    VARIANT_HKLPO | VARIANT_CALC,
+    kWidthKTable,
     offsetof(nsStylePosition, mMaxHeight),
     eStyleAnimType_Coord)
 CSS_PROP_LOGICAL(
     max-inline-size,
     max_inline_size,
     MaxInlineSize,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE |
@@ -2484,35 +2484,35 @@ CSS_PROP_POSITION(
     min_height,
     MinHeight,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_STORES_CALC |
         CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
         CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
     "",
-    VARIANT_AHLP | VARIANT_CALC,
-    nullptr,
+    VARIANT_AHKLP | VARIANT_CALC,
+    kWidthKTable,
     offsetof(nsStylePosition, mMinHeight),
     eStyleAnimType_Coord)
 CSS_PROP_LOGICAL(
     min-block-size,
     min_block_size,
     MinBlockSize,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_STORES_CALC |
         CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH |
         CSS_PROPERTY_ALWAYS_ENABLED_IN_UA_SHEETS |
         CSS_PROPERTY_LOGICAL |
         CSS_PROPERTY_LOGICAL_AXIS |
         CSS_PROPERTY_LOGICAL_BLOCK_AXIS,
     "layout.css.vertical-text.enabled",
-    VARIANT_AHKLP | VARIANT_CALC,
-    kWidthKTable,
+    VARIANT_AHLP | VARIANT_CALC,
+    nullptr,
     MinSize,
     Position,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_LOGICAL(
     min-inline-size,
     min_inline_size,
     MinInlineSize,
--- a/layout/style/nsCSSScanner.cpp
+++ b/layout/style/nsCSSScanner.cpp
@@ -160,17 +160,17 @@ IsIdentStart(int32_t ch) {
 /**
  * True if the two-character sequence aFirstChar+aSecondChar begins an
  * identifier.
  */
 static inline bool
 StartsIdent(int32_t aFirstChar, int32_t aSecondChar)
 {
   return IsIdentStart(aFirstChar) ||
-    (aFirstChar == '-' && IsIdentStart(aSecondChar));
+    (aFirstChar == '-' && (aSecondChar == '-' || IsIdentStart(aSecondChar)));
 }
 
 /**
  * True if 'ch' is a decimal digit.
  */
 static inline bool
 IsDigit(int32_t ch) {
   return (ch >= '0') && (ch <= '9');
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -4259,18 +4259,18 @@ nsComputedDOMStyle::DoGetHeight()
       StyleCoordToNSCoord(positionData->mMinHeight,
                           &nsComputedDOMStyle::GetCBContentHeight, 0, true);
 
     nscoord maxHeight =
       StyleCoordToNSCoord(positionData->mMaxHeight,
                           &nsComputedDOMStyle::GetCBContentHeight,
                           nscoord_MAX, true);
 
-    SetValueToCoord(val, positionData->mHeight, true, nullptr, nullptr,
-                    minHeight, maxHeight);
+    SetValueToCoord(val, positionData->mHeight, true, nullptr,
+                    nsCSSProps::kWidthKTable, minHeight, maxHeight);
   }
 
   return val;
 }
 
 CSSValue*
 nsComputedDOMStyle::DoGetWidth()
 {
@@ -4315,17 +4315,18 @@ nsComputedDOMStyle::DoGetWidth()
   return val;
 }
 
 CSSValue*
 nsComputedDOMStyle::DoGetMaxHeight()
 {
   nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
   SetValueToCoord(val, StylePosition()->mMaxHeight, true,
-                  &nsComputedDOMStyle::GetCBContentHeight);
+                  &nsComputedDOMStyle::GetCBContentHeight,
+                  nsCSSProps::kWidthKTable);
   return val;
 }
 
 CSSValue*
 nsComputedDOMStyle::DoGetMaxWidth()
 {
   nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
   SetValueToCoord(val, StylePosition()->mMaxWidth, true,
@@ -4344,17 +4345,18 @@ nsComputedDOMStyle::DoGetMinHeight()
     // In non-flexbox contexts, "min-height: auto" means "min-height: 0"
     // XXXdholbert For flex items, we should set |minHeight| to the
     // -moz-min-content keyword, instead of 0, once we support -moz-min-content
     // as a height value.
     minHeight.SetCoordValue(0);
   }
 
   SetValueToCoord(val, minHeight, true,
-                  &nsComputedDOMStyle::GetCBContentHeight);
+                  &nsComputedDOMStyle::GetCBContentHeight,
+                  nsCSSProps::kWidthKTable);
   return val;
 }
 
 CSSValue*
 nsComputedDOMStyle::DoGetMinWidth()
 {
   nsROCSSPrimitiveValue *val = new nsROCSSPrimitiveValue;
 
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7627,52 +7627,83 @@ nsRuleNode::ComputePositionData(void* aS
                  coord, parentCoord,
                  SETCOORD_LPAH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
                    SETCOORD_UNSET_INITIAL,
                  aContext, mPresContext, conditions)) {
       pos->mOffset.Set(side, coord);
     }
   }
 
-  SetCoord(*aRuleData->ValueForWidth(), pos->mWidth, parentPos->mWidth,
+  // We allow the enumerated box size property values -moz-min-content, etc. to
+  // be specified on both the {,min-,max-}width properties and the
+  // {,min-,max-}height properties, regardless of the writing mode.  This is
+  // because the writing mode is not determined until here, at computed value
+  // time.  Since we do not support layout behavior of these keywords on the
+  // block-axis properties, we turn them into unset if we find them in
+  // that case.
+
+  bool vertical;
+  switch (aContext->StyleVisibility()->mWritingMode) {
+    default:
+      MOZ_ASSERT(false, "unexpected writing-mode value");
+      // fall through
+    case NS_STYLE_WRITING_MODE_HORIZONTAL_TB:
+      vertical = false;
+      break;
+    case NS_STYLE_WRITING_MODE_VERTICAL_RL:
+    case NS_STYLE_WRITING_MODE_VERTICAL_LR:
+      vertical = true;
+      break;
+  }
+
+  const nsCSSValue* width = aRuleData->ValueForWidth();
+  SetCoord(width->GetUnit() == eCSSUnit_Enumerated && vertical ?
+             nsCSSValue(eCSSUnit_Unset) : *width,
+           pos->mWidth, parentPos->mWidth,
            SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
-  SetCoord(*aRuleData->ValueForMinWidth(), pos->mMinWidth, parentPos->mMinWidth,
+
+  const nsCSSValue* minWidth = aRuleData->ValueForMinWidth();
+  SetCoord(minWidth->GetUnit() == eCSSUnit_Enumerated && vertical ?
+             nsCSSValue(eCSSUnit_Unset) : *minWidth,
+           pos->mMinWidth, parentPos->mMinWidth,
            SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
-  SetCoord(*aRuleData->ValueForMaxWidth(), pos->mMaxWidth, parentPos->mMaxWidth,
+
+  const nsCSSValue* maxWidth = aRuleData->ValueForMaxWidth();
+  SetCoord(maxWidth->GetUnit() == eCSSUnit_Enumerated && vertical ?
+             nsCSSValue(eCSSUnit_Unset) : *maxWidth,
+           pos->mMaxWidth, parentPos->mMaxWidth,
            SETCOORD_LPOEH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
 
-  // We can get enumerated values for {,min-,max-}height (-moz-min-content,
-  // -moz-max-content, etc.) since we parse the logical properties with all the
-  // values that width accepts.  If we get a value we don't support on these
-  // properties, turn them into unset.
   const nsCSSValue* height = aRuleData->ValueForHeight();
-  SetCoord(height->GetUnit() == eCSSUnit_Enumerated ?
+  SetCoord(height->GetUnit() == eCSSUnit_Enumerated && !vertical ?
              nsCSSValue(eCSSUnit_Unset) : *height,
            pos->mHeight, parentPos->mHeight,
-           SETCOORD_LPAH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
+           SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
+
   const nsCSSValue* minHeight = aRuleData->ValueForMinHeight();
-  SetCoord(minHeight->GetUnit() == eCSSUnit_Enumerated ?
+  SetCoord(minHeight->GetUnit() == eCSSUnit_Enumerated && !vertical ?
              nsCSSValue(eCSSUnit_Unset) : *minHeight,
            pos->mMinHeight, parentPos->mMinHeight,
-           SETCOORD_LPAH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
+           SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
+
   const nsCSSValue* maxHeight = aRuleData->ValueForMaxHeight();
-  SetCoord(maxHeight->GetUnit() == eCSSUnit_Enumerated ?
+  SetCoord(maxHeight->GetUnit() == eCSSUnit_Enumerated && !vertical ?
              nsCSSValue(eCSSUnit_Unset) : *maxHeight,
            pos->mMaxHeight, parentPos->mMaxHeight,
-           SETCOORD_LPOH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC |
+           SETCOORD_LPOEH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
 
   // box-sizing: enum, inherit, initial
   SetDiscrete(*aRuleData->ValueForBoxSizing(),
               pos->mBoxSizing, conditions,
               SETDSC_ENUMERATED | SETDSC_UNSET_INITIAL,
               parentPos->mBoxSizing,
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -271,8 +271,9 @@ support-files = bug732209-css.sjs
 [test_counter_style.html]
 [test_counter_descriptor_storage.html]
 [test_position_float_display.html]
 [test_animations_async_tests.html]
 support-files = ../../reftests/fonts/Ahem.ttf file_animations_async_tests.html
 [test_setPropertyWithNull.html]
 [test_attribute_selector_eof_behavior.html]
 [test_css_loader_crossorigin_data_url.html]
+[test_box_size_keywords.html]
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -2679,27 +2679,31 @@ var gCSSProperties = {
     other_values: [ "bold", "100", "200", "300", "500", "600", "700", "800", "900", "bolder", "lighter" ],
     invalid_values: [ "0", "100.0", "107", "399", "401", "699", "710", "1000" ]
   },
   "height": {
     domProp: "height",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     /* FIXME: test zero, and test calc clamping */
-    initial_values: [ " auto" ],
+    initial_values: [ " auto",
+      // these four keywords compute to the initial value when the
+      // writing mode is horizontal, and that's the context we're testing in
+      "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+    ],
     /* computed value tests for height test more with display:block */
     prerequisites: { "display": "block" },
     other_values: [ "15px", "3em", "15%",
       "calc(2px)",
       "calc(50%)",
       "calc(3*25px)",
       "calc(25px*3)",
       "calc(3*25px + 50%)",
     ],
-    invalid_values: [ "none", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ],
+    invalid_values: [ "none" ],
     quirks_values: { "5": "5px" },
   },
   "ime-mode": {
     domProp: "imeMode",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "auto" ],
     other_values: [ "normal", "disabled", "active", "inactive" ],
@@ -2940,70 +2944,86 @@ var gCSSProperties = {
     other_values: [ "crop", "cross", "crop cross", "cross crop" ],
     invalid_values: [ "none none", "crop none", "none crop", "cross none", "none cross" ]
   },
   "max-height": {
     domProp: "maxHeight",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     prerequisites: { "display": "block" },
-    initial_values: [ "none" ],
+    initial_values: [ "none",
+      // these four keywords compute to the initial value when the
+      // writing mode is horizontal, and that's the context we're testing in
+      "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+    ],
     other_values: [ "30px", "50%", "0",
       "calc(2px)",
       "calc(-2px)",
       "calc(0px)",
       "calc(50%)",
       "calc(3*25px)",
       "calc(25px*3)",
       "calc(3*25px + 50%)",
     ],
-    invalid_values: [ "auto", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ],
+    invalid_values: [ "auto" ],
     quirks_values: { "5": "5px" },
   },
   "max-width": {
     domProp: "maxWidth",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     prerequisites: { "display": "block" },
     initial_values: [ "none" ],
-    other_values: [ "30px", "50%", "0", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+    other_values: [ "30px", "50%", "0",
+      // these four keywords compute to the initial value only when the
+      // writing mode is vertical, and we're testing with a horizontal
+      // writing mode
+      "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
       "calc(2px)",
       "calc(-2px)",
       "calc(0px)",
       "calc(50%)",
       "calc(3*25px)",
       "calc(25px*3)",
       "calc(3*25px + 50%)",
     ],
     invalid_values: [ "auto" ],
     quirks_values: { "5": "5px" },
   },
   "min-height": {
     domProp: "minHeight",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     prerequisites: { "display": "block" },
-    initial_values: [ "auto", "0", "calc(0em)", "calc(-2px)", "calc(-1%)" ],
+    initial_values: [ "auto", "0", "calc(0em)", "calc(-2px)", "calc(-1%)",
+      // these four keywords compute to the initial value when the
+      // writing mode is horizontal, and that's the context we're testing in
+      "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available",
+    ],
     other_values: [ "30px", "50%",
       "calc(2px)",
       "calc(50%)",
       "calc(3*25px)",
       "calc(25px*3)",
       "calc(3*25px + 50%)",
     ],
-    invalid_values: ["none", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ],
+    invalid_values: ["none"],
     quirks_values: { "5": "5px" },
   },
   "min-width": {
     domProp: "minWidth",
     inherited: false,
     type: CSS