merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 18 Apr 2017 10:21:31 +0200
changeset 353558 bb38d935d699e0529f9e0bb35578d381026415c4
parent 353527 92f94fc0993bcba7ac34cae4c0e82b06012bcd91 (current diff)
parent 353557 9bbf24f5eb3bb3e5313db5dd64b0aa2d426675f1 (diff)
child 353559 39fc167067212498ca7a11bf7ae616906600579a
child 353574 a3851757e3f6778aa568961ea4431b722725f3c3
child 353625 6cbcddb1bfc91c8db7254737e177807c63348eb3
child 355133 8dabb0dac55a96fc7eb0295587bdb6aa86ce24d8
push id31671
push usercbook@mozilla.com
push dateTue, 18 Apr 2017 08:21:51 +0000
treeherdermozilla-central@bb38d935d699 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
bb38d935d699 / 55.0a1 / 20170418100213 / files
nightly linux64
bb38d935d699 / 55.0a1 / 20170418100213 / files
nightly mac
bb38d935d699 / 55.0a1 / 20170418030220 / files
nightly win32
bb38d935d699 / 55.0a1 / 20170418030220 / files
nightly win64
bb38d935d699 / 55.0a1 / 20170418030220 / 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/components/extensions/test/xpcshell/test_getAPILevelForWindow.js
tools/profiler/core/platform.cpp
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -333,16 +333,31 @@ DocAccessibleParent::RecvTextChangeEvent
   RefPtr<xpcAccTextChangeEvent> event =
     new xpcAccTextChangeEvent(type, xpcAcc, doc, node, aFromUser, aStart, aLen,
                               aIsInsert, aStr);
   nsCoreUtils::DispatchAccEvent(Move(event));
 
   return IPC_OK();
 }
 
+#if defined(XP_WIN)
+
+mozilla::ipc::IPCResult
+DocAccessibleParent::RecvSyncTextChangeEvent(const uint64_t& aID,
+                                             const nsString& aStr,
+                                             const int32_t& aStart,
+                                             const uint32_t& aLen,
+                                             const bool& aIsInsert,
+                                             const bool& aFromUser)
+{
+  return RecvTextChangeEvent(aID, aStr, aStart, aLen, aIsInsert, aFromUser);
+}
+
+#endif // defined(XP_WIN)
+
 mozilla::ipc::IPCResult
 DocAccessibleParent::RecvSelectionEvent(const uint64_t& aID,
                                         const uint64_t& aWidgetID,
                                         const uint32_t& aType)
 {
   if (mShutdown) {
     return IPC_OK();
   }
@@ -431,16 +446,21 @@ DocAccessibleParent::AddChildDoc(DocAcce
   // OuterDocAccessibles are expected to only have a document as a child.
   // However for compatibility we tolerate replacing one document with another
   // here.
   if (outerDoc->ChildrenCount() > 1 ||
       (outerDoc->ChildrenCount() == 1 && !outerDoc->ChildAt(0)->IsDoc())) {
     return IPC_FAIL(this, "binding to proxy that can't be a outerDoc!");
   }
 
+  if (outerDoc->ChildrenCount() == 1) {
+    MOZ_ASSERT(outerDoc->ChildAt(0)->AsDoc());
+    outerDoc->ChildAt(0)->AsDoc()->Unbind();
+  }
+
   aChildDoc->SetParent(outerDoc);
   outerDoc->SetChildDoc(aChildDoc);
   mChildDocs.AppendElement(aChildDoc->mActorID);
   aChildDoc->mParentDoc = mActorID;
 
   if (aCreating) {
     ProxyCreated(aChildDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
   }
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -83,16 +83,23 @@ public:
   virtual mozilla::ipc::IPCResult RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
     override final;
 
   virtual mozilla::ipc::IPCResult RecvTextChangeEvent(const uint64_t& aID, const nsString& aStr,
                                                       const int32_t& aStart, const uint32_t& aLen,
                                                       const bool& aIsInsert,
                                                       const bool& aFromUser) override;
 
+#if defined(XP_WIN)
+  virtual mozilla::ipc::IPCResult RecvSyncTextChangeEvent(const uint64_t& aID, const nsString& aStr,
+                                                          const int32_t& aStart, const uint32_t& aLen,
+                                                          const bool& aIsInsert,
+                                                          const bool& aFromUser) override;
+#endif // defined(XP_WIN)
+
   virtual mozilla::ipc::IPCResult RecvSelectionEvent(const uint64_t& aID,
                                                      const uint64_t& aWidgetID,
                                                      const uint32_t& aType) override;
 
   virtual mozilla::ipc::IPCResult RecvRoleChangedEvent(const uint32_t& aRole) override final;
 
   virtual mozilla::ipc::IPCResult RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID) override;
 
--- a/accessible/ipc/ProxyAccessibleBase.cpp
+++ b/accessible/ipc/ProxyAccessibleBase.cpp
@@ -49,25 +49,19 @@ ProxyAccessibleBase<Derived>::Shutdown()
   ProxyDestroyed(static_cast<Derived*>(this));
   mDoc->RemoveAccessible(static_cast<Derived*>(this));
 }
 
 template <class Derived>
 void
 ProxyAccessibleBase<Derived>::SetChildDoc(DocAccessibleParent* aChildDoc)
 {
-  // DocAccessibleParent::AddChildDoc tolerates replacing one document with
-  // another. We must reflect that here.
   MOZ_ASSERT(aChildDoc);
-  MOZ_ASSERT(mChildren.Length() <= 1);
-  if (mChildren.IsEmpty()) {
-    mChildren.AppendElement(aChildDoc);
-  } else {
-    mChildren.ReplaceElementAt(0, aChildDoc);
-  }
+  MOZ_ASSERT(mChildren.Length() == 0);
+  mChildren.AppendElement(aChildDoc);
   mOuterDoc = true;
 }
 
 template <class Derived>
 void
 ProxyAccessibleBase<Derived>::ClearChildDoc(DocAccessibleParent* aChildDoc)
 {
   MOZ_ASSERT(aChildDoc);
--- a/accessible/ipc/win/DocAccessibleChild.cpp
+++ b/accessible/ipc/win/DocAccessibleChild.cpp
@@ -72,16 +72,28 @@ DocAccessibleChild::RecvEmulatedWindow(c
     MOZ_ASSERT(!mEmulatedWindowProxy);
     mEmulatedWindowProxy.reset(
       const_cast<IAccessibleHolder&>(aEmulatedWindowCOMProxy).Release());
   }
 
   return IPC_OK();
 }
 
+HWND
+DocAccessibleChild::GetNativeWindowHandle() const
+{
+  if (mEmulatedWindowHandle) {
+    return mEmulatedWindowHandle;
+  }
+
+  auto tab = static_cast<dom::TabChild*>(Manager());
+  MOZ_ASSERT(tab);
+  return reinterpret_cast<HWND>(tab->GetNativeWindowHandle());
+}
+
 void
 DocAccessibleChild::PushDeferredEvent(UniquePtr<DeferredEvent> aEvent)
 {
   DocAccessibleChild* topLevelIPCDoc = nullptr;
 
   if (mDoc && mDoc->IsRoot()) {
     topLevelIPCDoc = this;
   } else {
@@ -173,16 +185,23 @@ bool
 DocAccessibleChild::SendTextChangeEvent(const uint64_t& aID,
                                         const nsString& aStr,
                                         const int32_t& aStart,
                                         const uint32_t& aLen,
                                         const bool& aIsInsert,
                                         const bool& aFromUser)
 {
   if (IsConstructedInParentProcess()) {
+    if (aStr.Contains(L'\xfffc')) {
+      // The AT is going to need to reenter content while the event is being
+      // dispatched synchronously.
+      return PDocAccessibleChild::SendSyncTextChangeEvent(aID, aStr, aStart,
+                                                          aLen, aIsInsert,
+                                                          aFromUser);
+    }
     return PDocAccessibleChild::SendTextChangeEvent(aID, aStr, aStart,
                                                     aLen, aIsInsert, aFromUser);
   }
 
   PushDeferredEvent(MakeUnique<SerializedTextChange>(this, aID, aStr, aStart,
                                                      aLen, aIsInsert, aFromUser));
   return true;
 }
--- a/accessible/ipc/win/DocAccessibleChild.h
+++ b/accessible/ipc/win/DocAccessibleChild.h
@@ -28,17 +28,17 @@ public:
   virtual void Shutdown() override;
 
   virtual ipc::IPCResult
   RecvParentCOMProxy(const IAccessibleHolder& aParentCOMProxy) override;
   virtual ipc::IPCResult
     RecvEmulatedWindow(const WindowsHandle& aEmulatedWindowHandle,
                        const IAccessibleHolder& aEmulatedWindowCOMProxy) override;
 
-  HWND GetEmulatedWindowHandle() const { return mEmulatedWindowHandle; }
+  HWND GetNativeWindowHandle() const;
   IAccessible* GetEmulatedWindowIAccessible() const { return mEmulatedWindowProxy.get(); }
 
   IAccessible* GetParentIAccessible() const { return mParentProxy.get(); }
 
   bool SendEvent(const uint64_t& aID, const uint32_t& type);
   bool SendHideEvent(const uint64_t& aRootID, const bool& aFromUser);
   bool SendStateChangeEvent(const uint64_t& aID, const uint64_t& aState,
                             const bool& aEnabled);
--- a/accessible/ipc/win/PDocAccessible.ipdl
+++ b/accessible/ipc/win/PDocAccessible.ipdl
@@ -48,16 +48,18 @@ parent:
    */
   async Event(uint64_t aID, uint32_t type);
   async ShowEvent(ShowEventData data, bool aFromUser);
   async HideEvent(uint64_t aRootID, bool aFromUser);
   async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled);
   async CaretMoveEvent(uint64_t aID, int32_t aOffset);
   async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen,
                         bool aIsInsert, bool aFromUser);
+  sync SyncTextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart,
+                           uint32_t aLen, bool aIsInsert, bool aFromUser);
   async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType);
   async RoleChangedEvent(uint32_t aRole);
 
   /*
    * Tell the parent document to bind the existing document as a new child
    * document.
    */
   async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID);
--- a/accessible/ipc/win/handler/AccessibleHandler.cpp
+++ b/accessible/ipc/win/handler/AccessibleHandler.cpp
@@ -7,16 +7,17 @@
 #if defined(MOZILLA_INTERNAL_API)
 #error This code is NOT for internal Gecko use!
 #endif // defined(MOZILLA_INTERNAL_API)
 
 #define INITGUID
 
 #include "AccessibleHandler.h"
 #include "AccessibleHandlerControl.h"
+#include "AccessibleTextTearoff.h"
 
 #include "Factory.h"
 #include "HandlerData.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/mscom/Registration.h"
 #include "mozilla/UniquePtr.h"
 
 #include <objbase.h>
@@ -178,16 +179,22 @@ AccessibleHandler::QueryHandlerInterface
   }
 
   if (aIid == IID_IServiceProvider) {
     RefPtr<IServiceProvider> svcProv(static_cast<IServiceProvider*>(this));
     svcProv.forget(aOutInterface);
     return S_OK;
   }
 
+  if (aIid == IID_IAccessibleText || aIid == IID_IAccessibleHypertext) {
+    RefPtr<IAccessibleHypertext> textTearoff(new AccessibleTextTearoff(this));
+    textTearoff.forget(aOutInterface);
+    return S_OK;
+  }
+
   if (aIid == IID_IProvideClassInfo) {
     RefPtr<IProvideClassInfo> clsInfo(this);
     clsInfo.forget(aOutInterface);
     return S_OK;
   }
 
   return E_NOINTERFACE;
 }
--- a/accessible/ipc/win/handler/AccessibleHandlerControl.cpp
+++ b/accessible/ipc/win/handler/AccessibleHandlerControl.cpp
@@ -17,78 +17,173 @@
 #include "mozilla/Move.h"
 #include "mozilla/RefPtr.h"
 
 namespace mozilla {
 namespace a11y {
 
 mscom::SingletonFactory<AccessibleHandlerControl> gControlFactory;
 
+namespace detail {
+
+TextChange::TextChange()
+  : mIA2UniqueId(0)
+  , mIsInsert(false)
+  , mText()
+{
+}
+
+TextChange::TextChange(long aIA2UniqueId, bool aIsInsert,
+                       NotNull<IA2TextSegment*> aText)
+  : mIA2UniqueId(aIA2UniqueId)
+  , mIsInsert(aIsInsert)
+  , mText{BSTRCopy(aText->text), aText->start, aText->end}
+{
+}
+
+TextChange::TextChange(TextChange&& aOther)
+  : mText()
+{
+  *this = Move(aOther);
+}
+
+TextChange::TextChange(const TextChange& aOther)
+  : mText()
+{
+  *this = aOther;
+}
+
+TextChange&
+TextChange::operator=(TextChange&& aOther)
+{
+  mIA2UniqueId = aOther.mIA2UniqueId;
+  mIsInsert = aOther.mIsInsert;
+  aOther.mIA2UniqueId = 0;
+  ::SysFreeString(mText.text);
+  mText = aOther.mText;
+  aOther.mText.text = nullptr;
+  return *this;
+}
+
+TextChange&
+TextChange::operator=(const TextChange& aOther)
+{
+  mIA2UniqueId = aOther.mIA2UniqueId;
+  mIsInsert = aOther.mIsInsert;
+  ::SysFreeString(mText.text);
+  mText = {BSTRCopy(aOther.mText.text), aOther.mText.start, aOther.mText.end};
+  return *this;
+}
+
+TextChange::~TextChange()
+{
+  ::SysFreeString(mText.text);
+}
+
+HRESULT
+TextChange::GetOld(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutOldSegment)
+{
+  if (mIsInsert || aIA2UniqueId != mIA2UniqueId) {
+    return S_OK;
+  }
+
+  return SegCopy(*aOutOldSegment, mText);
+}
+
+HRESULT
+TextChange::GetNew(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutNewSegment)
+{
+  if (!mIsInsert || aIA2UniqueId != mIA2UniqueId) {
+    return S_OK;
+  }
+
+  return SegCopy(*aOutNewSegment, mText);
+}
+
+/* static */ BSTR
+TextChange::BSTRCopy(const BSTR& aIn)
+{
+  return ::SysAllocStringLen(aIn, ::SysStringLen(aIn));
+}
+
+/* static */ HRESULT
+TextChange::SegCopy(IA2TextSegment& aDest, const IA2TextSegment& aSrc)
+{
+  aDest = {BSTRCopy(aSrc.text), aSrc.start, aSrc.end};
+  if (aSrc.text && !aDest.text) {
+    return E_OUTOFMEMORY;
+  }
+  if (!::SysStringLen(aDest.text)) {
+    return S_FALSE;
+  }
+  return S_OK;
+}
+
+} // namespace detail
+
 HRESULT
 AccessibleHandlerControl::Create(AccessibleHandlerControl** aOutObject)
 {
   if (!aOutObject) {
     return E_INVALIDARG;
   }
 
   RefPtr<AccessibleHandlerControl> ctl(new AccessibleHandlerControl());
   ctl.forget(aOutObject);
   return S_OK;
 }
 
 AccessibleHandlerControl::AccessibleHandlerControl()
-  : mRefCnt(0)
-  , mCacheGen(0)
+  : mCacheGen(0)
   , mIA2Proxy(mscom::RegisterProxy(L"ia2marshal.dll"))
   , mHandlerProxy(mscom::RegisterProxy())
 {
   MOZ_ASSERT(mIA2Proxy);
 }
 
-HRESULT
-AccessibleHandlerControl::QueryInterface(REFIID aIid, void** aOutInterface)
-{
-  if (!aOutInterface) {
-    return E_INVALIDARG;
-  }
-
-  if (aIid == IID_IUnknown || aIid == IID_IHandlerControl) {
-    RefPtr<IHandlerControl> ctl(this);
-    ctl.forget(aOutInterface);
-    return S_OK;
-  }
-
-  *aOutInterface = nullptr;
-  return E_NOINTERFACE;
-}
-
-ULONG
-AccessibleHandlerControl::AddRef()
-{
-  return ++mRefCnt;
-}
-
-ULONG
-AccessibleHandlerControl::Release()
-{
-  ULONG result = --mRefCnt;
-  if (!result) {
-    delete this;
-  }
-  return result;
-}
+IMPL_IUNKNOWN1(AccessibleHandlerControl, IHandlerControl)
 
 HRESULT
 AccessibleHandlerControl::Invalidate()
 {
   ++mCacheGen;
   return S_OK;
 }
 
 HRESULT
+AccessibleHandlerControl::OnTextChange(long aHwnd, long aIA2UniqueId,
+                                       VARIANT_BOOL aIsInsert,
+                                       IA2TextSegment* aText)
+{
+  if (!aText) {
+    return E_INVALIDARG;
+  }
+
+  mTextChange = detail::TextChange(aIA2UniqueId, aIsInsert, WrapNotNull(aText));
+  NotifyWinEvent(aIsInsert ? IA2_EVENT_TEXT_INSERTED : IA2_EVENT_TEXT_REMOVED,
+                 reinterpret_cast<HWND>(static_cast<uintptr_t>(aHwnd)),
+                 OBJID_CLIENT, aIA2UniqueId);
+  return S_OK;
+}
+
+HRESULT
+AccessibleHandlerControl::GetNewText(long aIA2UniqueId,
+                                     NotNull<IA2TextSegment*> aOutNewText)
+{
+  return mTextChange.GetNew(aIA2UniqueId, aOutNewText);
+}
+
+HRESULT
+AccessibleHandlerControl::GetOldText(long aIA2UniqueId,
+                                     NotNull<IA2TextSegment*> aOutOldText)
+{
+  return mTextChange.GetOld(aIA2UniqueId, aOutOldText);
+}
+
+HRESULT
 AccessibleHandlerControl::GetHandlerTypeInfo(ITypeInfo** aOutTypeInfo)
 {
   if (!mHandlerProxy) {
     return E_UNEXPECTED;
   }
 
   return mHandlerProxy->GetTypeInfoForGuid(CLSID_AccessibleHandler,
                                            aOutTypeInfo);
--- a/accessible/ipc/win/handler/AccessibleHandlerControl.h
+++ b/accessible/ipc/win/handler/AccessibleHandlerControl.h
@@ -8,47 +8,80 @@
 #error This code is NOT for internal Gecko use!
 #endif // defined(MOZILLA_INTERNAL_API)
 
 #ifndef mozilla_a11y_AccessibleHandlerControl_h
 #define mozilla_a11y_AccessibleHandlerControl_h
 
 #include "Factory.h"
 #include "HandlerData.h"
+#include "IUnknownImpl.h"
 #include "mozilla/mscom/Registration.h"
+#include "mozilla/NotNull.h"
 
 namespace mozilla {
 namespace a11y {
 
+namespace detail {
+
+class TextChange final
+{
+public:
+  TextChange();
+  TextChange(long aIA2UniqueId, bool aIsInsert, NotNull<IA2TextSegment*> aText);
+  TextChange(TextChange&& aOther);
+  TextChange(const TextChange& aOther);
+
+  TextChange& operator=(TextChange&& aOther);
+  TextChange& operator=(const TextChange& aOther);
+
+  ~TextChange();
+
+  HRESULT GetOld(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutOldSegment);
+  HRESULT GetNew(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutNewSegment);
+
+private:
+  static BSTR BSTRCopy(const BSTR& aIn);
+  static HRESULT SegCopy(IA2TextSegment& aDest, const IA2TextSegment& aSrc);
+
+  long mIA2UniqueId;
+  bool mIsInsert;
+  IA2TextSegment mText;
+};
+
+} // namespace detail
+
 class AccessibleHandlerControl final : public IHandlerControl
 {
 public:
   static HRESULT Create(AccessibleHandlerControl** aOutObject);
 
-  // IUnknown
-  STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
-  STDMETHODIMP_(ULONG) AddRef() override;
-  STDMETHODIMP_(ULONG) Release() override;
+  DECL_IUNKNOWN
 
   // IHandlerControl
   STDMETHODIMP Invalidate() override;
+  STDMETHODIMP OnTextChange(long aHwnd, long aIA2UniqueId,
+                            VARIANT_BOOL aIsInsert, IA2TextSegment* aText) override;
 
   uint32_t GetCacheGen() const
   {
     return mCacheGen;
   }
 
+  HRESULT GetNewText(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutNewText);
+  HRESULT GetOldText(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutOldText);
+
   HRESULT GetHandlerTypeInfo(ITypeInfo** aOutTypeInfo);
 
 private:
   AccessibleHandlerControl();
   ~AccessibleHandlerControl() = default;
 
-  ULONG mRefCnt;
   uint32_t mCacheGen;
+  detail::TextChange mTextChange;
   UniquePtr<mscom::RegisteredProxy> mIA2Proxy;
   UniquePtr<mscom::RegisteredProxy> mHandlerProxy;
 };
 
 extern mscom::SingletonFactory<AccessibleHandlerControl> gControlFactory;
 
 } // namespace a11y
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/accessible/ipc/win/handler/AccessibleTextTearoff.cpp
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(MOZILLA_INTERNAL_API)
+#error This code is NOT for internal Gecko use!
+#endif // defined(MOZILLA_INTERNAL_API)
+
+#include "AccessibleTextTearoff.h"
+
+#include "AccessibleHandlerControl.h"
+#include "AccessibleText_i.c"
+#include "AccessibleHypertext_i.c"
+#include "Factory.h"
+
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+namespace a11y {
+
+AccessibleTextTearoff::AccessibleTextTearoff(AccessibleHandler* aHandler)
+  : mHandler(aHandler)
+{
+  MOZ_ASSERT(aHandler);
+}
+
+HRESULT
+AccessibleTextTearoff::ResolveAccText()
+{
+  if (mAccTextProxy) {
+    return S_OK;
+  }
+
+  RefPtr<IUnknown> proxy(mHandler->GetProxy());
+  if (!proxy) {
+    return E_UNEXPECTED;
+  }
+
+  return proxy->QueryInterface(IID_IAccessibleText,
+                               getter_AddRefs(mAccTextProxy));
+}
+
+HRESULT
+AccessibleTextTearoff::ResolveAccHypertext()
+{
+  if (mAccHypertextProxy) {
+    return S_OK;
+  }
+
+  RefPtr<IUnknown> proxy(mHandler->GetProxy());
+  if (!proxy) {
+    return E_UNEXPECTED;
+  }
+
+  return proxy->QueryInterface(IID_IAccessibleHypertext,
+                               getter_AddRefs(mAccHypertextProxy));
+}
+
+IMPL_IUNKNOWN_QUERY_HEAD(AccessibleTextTearoff)
+IMPL_IUNKNOWN_QUERY_IFACE(IAccessibleText)
+IMPL_IUNKNOWN_QUERY_IFACE(IAccessibleHypertext)
+IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mHandler)
+
+HRESULT
+AccessibleTextTearoff::addSelection(long startOffset, long endOffset)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->addSelection(startOffset, endOffset);
+}
+
+HRESULT
+AccessibleTextTearoff::get_attributes(long offset, long *startOffset,
+                                      long *endOffset, BSTR *textAttributes)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_attributes(offset, startOffset, endOffset,
+                                       textAttributes);
+}
+
+HRESULT
+AccessibleTextTearoff::get_caretOffset(long *offset)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_caretOffset(offset);
+}
+
+HRESULT
+AccessibleTextTearoff::get_characterExtents(long offset,
+                                            enum IA2CoordinateType coordType,
+                                            long *x, long *y, long *width,
+                                            long *height)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_characterExtents(offset, coordType, x, y, width,
+                                             height);
+}
+
+HRESULT
+AccessibleTextTearoff::get_nSelections(long *nSelections)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_nSelections(nSelections);
+}
+
+HRESULT
+AccessibleTextTearoff::get_offsetAtPoint(long x, long y,
+                                         enum IA2CoordinateType coordType,
+                                         long *offset)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_offsetAtPoint(x, y, coordType, offset);
+}
+
+HRESULT
+AccessibleTextTearoff::get_selection(long selectionIndex, long *startOffset,
+                                     long *endOffset)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_selection(selectionIndex, startOffset, endOffset);
+}
+
+HRESULT
+AccessibleTextTearoff::get_text(long startOffset, long endOffset, BSTR *text)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_text(startOffset, endOffset, text);
+}
+
+HRESULT
+AccessibleTextTearoff::get_textBeforeOffset(long offset,
+                                            enum IA2TextBoundaryType boundaryType,
+                                            long *startOffset, long *endOffset,
+                                            BSTR *text)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_textBeforeOffset(offset, boundaryType, startOffset,
+                                             endOffset, text);
+}
+
+HRESULT
+AccessibleTextTearoff::get_textAfterOffset(long offset,
+                                           enum IA2TextBoundaryType boundaryType,
+                                           long *startOffset, long *endOffset,
+                                           BSTR *text)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_textAfterOffset(offset, boundaryType,
+                                            startOffset, endOffset, text);
+}
+
+HRESULT
+AccessibleTextTearoff::get_textAtOffset(long offset,
+                                        enum IA2TextBoundaryType boundaryType,
+                                        long *startOffset, long *endOffset,
+                                        BSTR *text)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_textAtOffset(offset, boundaryType, startOffset,
+                                         endOffset, text);
+}
+
+HRESULT
+AccessibleTextTearoff::removeSelection(long selectionIndex)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->removeSelection(selectionIndex);
+}
+
+HRESULT
+AccessibleTextTearoff::setCaretOffset(long offset)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->setCaretOffset(offset);
+}
+
+HRESULT
+AccessibleTextTearoff::setSelection(long selectionIndex, long startOffset,
+                                    long endOffset)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->setSelection(selectionIndex, startOffset, endOffset);
+}
+
+HRESULT
+AccessibleTextTearoff::get_nCharacters(long *nCharacters)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->get_nCharacters(nCharacters);
+}
+
+HRESULT
+AccessibleTextTearoff::scrollSubstringTo(long startIndex, long endIndex,
+                                         enum IA2ScrollType scrollType)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->scrollSubstringTo(startIndex, endIndex, scrollType);
+}
+
+HRESULT
+AccessibleTextTearoff::scrollSubstringToPoint(long startIndex, long endIndex,
+                                              enum IA2CoordinateType coordinateType,
+                                              long x, long y)
+{
+  HRESULT hr = ResolveAccText();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccTextProxy->scrollSubstringToPoint(startIndex, endIndex,
+                                               coordinateType, x, y);
+}
+
+HRESULT
+AccessibleTextTearoff::get_newText(IA2TextSegment *newText)
+{
+  if (!newText) {
+    return E_INVALIDARG;
+  }
+
+  RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetSingleton());
+  MOZ_ASSERT(ctl);
+  if (!ctl) {
+    return S_OK;
+  }
+
+  long id;
+  HRESULT hr = mHandler->get_uniqueID(&id);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return ctl->GetNewText(id, WrapNotNull(newText));
+}
+
+HRESULT
+AccessibleTextTearoff::get_oldText(IA2TextSegment *oldText)
+{
+  if (!oldText) {
+    return E_INVALIDARG;
+  }
+
+  RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetSingleton());
+  MOZ_ASSERT(ctl);
+  if (!ctl) {
+    return S_OK;
+  }
+
+  long id;
+  HRESULT hr = mHandler->get_uniqueID(&id);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return ctl->GetOldText(id, WrapNotNull(oldText));
+}
+
+HRESULT
+AccessibleTextTearoff::get_nHyperlinks(long *hyperlinkCount)
+{
+  HRESULT hr = ResolveAccHypertext();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccHypertextProxy->get_nHyperlinks(hyperlinkCount);
+}
+
+HRESULT
+AccessibleTextTearoff::get_hyperlink(long index,
+                                     IAccessibleHyperlink **hyperlink)
+{
+  HRESULT hr = ResolveAccHypertext();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccHypertextProxy->get_hyperlink(index, hyperlink);
+}
+
+HRESULT
+AccessibleTextTearoff::get_hyperlinkIndex(long charIndex, long *hyperlinkIndex)
+{
+  HRESULT hr = ResolveAccHypertext();
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return mAccHypertextProxy->get_hyperlinkIndex(charIndex, hyperlinkIndex);
+}
+
+
+} // namespace a11y
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/accessible/ipc/win/handler/AccessibleTextTearoff.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(MOZILLA_INTERNAL_API)
+#error This code is NOT for internal Gecko use!
+#endif // defined(MOZILLA_INTERNAL_API)
+
+#ifndef mozilla_a11y_AccessibleTextTearoff_h
+#define mozilla_a11y_AccessibleTextTearoff_h
+
+#include "AccessibleHandler.h"
+#include "AccessibleHypertext.h"
+#include "IUnknownImpl.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleTextTearoff final : public IAccessibleHypertext
+{
+public:
+  explicit AccessibleTextTearoff(AccessibleHandler* aHandler);
+
+  DECL_IUNKNOWN
+
+  // IAccessibleText
+  STDMETHODIMP addSelection(long startOffset, long endOffset) override;
+  STDMETHODIMP get_attributes(long offset, long *startOffset, long *endOffset,
+                              BSTR *textAttributes) override;
+  STDMETHODIMP get_caretOffset(long *offset) override;
+  STDMETHODIMP get_characterExtents(long offset,
+                                    enum IA2CoordinateType coordType, long *x,
+                                    long *y, long *width, long *height) override;
+  STDMETHODIMP get_nSelections(long *nSelections) override;
+  STDMETHODIMP get_offsetAtPoint(long x, long y,
+                                 enum IA2CoordinateType coordType,
+                                 long *offset) override;
+  STDMETHODIMP get_selection(long selectionIndex, long *startOffset,
+                             long *endOffset) override;
+  STDMETHODIMP get_text(long startOffset, long endOffset, BSTR *text) override;
+  STDMETHODIMP get_textBeforeOffset(long offset,
+                                    enum IA2TextBoundaryType boundaryType,
+                                    long *startOffset, long *endOffset,
+                                    BSTR *text) override;
+  STDMETHODIMP get_textAfterOffset(long offset,
+                                   enum IA2TextBoundaryType boundaryType,
+                                   long *startOffset, long *endOffset,
+                                   BSTR *text) override;
+  STDMETHODIMP get_textAtOffset(long offset,
+                                enum IA2TextBoundaryType boundaryType,
+                                long *startOffset, long *endOffset,
+                                BSTR *text) override;
+  STDMETHODIMP removeSelection(long selectionIndex) override;
+  STDMETHODIMP setCaretOffset(long offset) override;
+  STDMETHODIMP setSelection(long selectionIndex, long startOffset,
+                            long endOffset) override;
+  STDMETHODIMP get_nCharacters(long *nCharacters) override;
+  STDMETHODIMP scrollSubstringTo(long startIndex, long endIndex,
+                                 enum IA2ScrollType scrollType) override;
+  STDMETHODIMP scrollSubstringToPoint(long startIndex, long endIndex,
+                                      enum IA2CoordinateType coordinateType,
+                                      long x, long y) override;
+  STDMETHODIMP get_newText(IA2TextSegment *newText) override;
+  STDMETHODIMP get_oldText(IA2TextSegment *oldText) override;
+
+  // IAccessibleHypertext
+  STDMETHODIMP get_nHyperlinks(long *hyperlinkCount) override;
+  STDMETHODIMP get_hyperlink(long index,
+                             IAccessibleHyperlink **hyperlink) override;
+  STDMETHODIMP get_hyperlinkIndex(long charIndex, long *hyperlinkIndex) override;
+
+private:
+  ~AccessibleTextTearoff() = default;
+  HRESULT ResolveAccText();
+  HRESULT ResolveAccHypertext();
+
+  RefPtr<AccessibleHandler>     mHandler;
+  RefPtr<IAccessibleText>       mAccTextProxy;
+  RefPtr<IAccessibleHypertext>  mAccHypertextProxy;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_AccessibleTextTearoff_h
--- a/accessible/ipc/win/handler/HandlerData.idl
+++ b/accessible/ipc/win/handler/HandlerData.idl
@@ -4,16 +4,18 @@
  * 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 "AccessibleHandler.h"
 
 import "ocidl.idl";
 import "ServProv.idl";
 
+import "AccessibleText.idl";
+
 typedef struct _IA2Data
 {
   long mUniqueId;
 } IA2Data;
 
 interface IGeckoBackChannel;
 
 // We define different CLSIDs and IIDs depending on channel and officiality.
@@ -79,16 +81,19 @@ interface HandlerData
 
 [object,
  uuid(IHANDLERCONTROL_IID),
  async_uuid(ASYNCIHANDLERCONTROL_IID),
  pointer_default(unique)]
 interface IHandlerControl : IUnknown
 {
   HRESULT Invalidate();
+  HRESULT OnTextChange([in] long aHwnd, [in] long aIA2UniqueId,
+                       [in] VARIANT_BOOL aIsInsert,
+                       [in] IA2TextSegment* aText);
 }
 
 [object,
  uuid(IGECKOBACKCHANNEL_IID),
  pointer_default(unique)]
 interface IGeckoBackChannel : IUnknown
 {
   [propput] HRESULT HandlerControl([in] long aPid, [in] IHandlerControl* aCtrl);
--- a/accessible/ipc/win/handler/moz.build
+++ b/accessible/ipc/win/handler/moz.build
@@ -15,16 +15,17 @@ LOCAL_INCLUDES += [
 
 SOURCES += [
     '!dlldata.c',
     '!HandlerData_c.c',
     '!HandlerData_i.c',
     '!HandlerData_p.c',
     'AccessibleHandler.cpp',
     'AccessibleHandlerControl.cpp',
+    'AccessibleTextTearoff.cpp',
 ]
 
 GENERATED_FILES += [
     'dlldata.c',
     'HandlerData.h',
     'HandlerData.tlb',
     'HandlerData_c.c',
     'HandlerData_i.c',
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -38,16 +38,17 @@
 #include "nsNameSpaceManager.h"
 #include "nsTextFormatter.h"
 #include "nsView.h"
 #include "nsViewManager.h"
 #include "nsEventMap.h"
 #include "nsArrayUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsIXULRuntime.h"
+#include "mozilla/mscom/AsyncInvoker.h"
 
 #include "oleacc.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 const uint32_t USE_ROLE_STRING = 0;
 
@@ -1622,8 +1623,65 @@ AccessibleWrap::SetHandlerControl(DWORD 
   HandlerControllerData ctrlData(aPid, Move(aCtrl));
   if (sHandlerControllers->Contains(ctrlData)) {
     return;
   }
 
   sHandlerControllers->AppendElement(Move(ctrlData));
 }
 
+
+bool
+AccessibleWrap::DispatchTextChangeToHandler(bool aIsInsert,
+                                            const nsString& aText,
+                                            int32_t aStart, uint32_t aLen)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sHandlerControllers || sHandlerControllers->IsEmpty()) {
+    return false;
+  }
+
+  HWND hwnd = GetHWNDFor(this);
+  MOZ_ASSERT(hwnd);
+  if (!hwnd) {
+    return false;
+  }
+
+  long msaaId = GetChildIDFor(this);
+
+  DWORD ourPid = ::GetCurrentProcessId();
+
+  // The handler ends up calling NotifyWinEvent, which should only be done once
+  // since it broadcasts the same event to every process who is subscribed.
+  // OTOH, if our chrome process contains a handler, we should prefer to
+  // broadcast the event from that process, as we want any DLLs injected by ATs
+  // to receive the event synchronously. Otherwise we simply choose the first
+  // handler in the list, for the lack of a better heuristic.
+
+  nsTArray<HandlerControllerData>::index_type ctrlIndex =
+    sHandlerControllers->IndexOf(ourPid);
+
+  if (ctrlIndex == nsTArray<HandlerControllerData>::NoIndex) {
+    ctrlIndex = 0;
+  }
+
+  HandlerControllerData& controller = sHandlerControllers->ElementAt(ctrlIndex);
+  MOZ_ASSERT(controller.mPid);
+  MOZ_ASSERT(controller.mCtrl);
+
+  VARIANT_BOOL isInsert = aIsInsert ? VARIANT_TRUE : VARIANT_FALSE;
+
+  IA2TextSegment textSegment{::SysAllocStringLen(aText.get(), aText.Length()),
+                             aStart, static_cast<long>(aLen)};
+
+  ASYNC_INVOKER_FOR(IHandlerControl) invoker(controller.mCtrl,
+                                             Some(controller.mIsProxy));
+
+  HRESULT hr = ASYNC_INVOKE(invoker, OnTextChange, PtrToLong(hwnd), msaaId,
+                            isInsert, &textSegment);
+
+  ::SysFreeString(textSegment.text);
+
+  return SUCCEEDED(hr);
+}
+
--- a/accessible/windows/msaa/AccessibleWrap.h
+++ b/accessible/windows/msaa/AccessibleWrap.h
@@ -187,16 +187,18 @@ public: // construction, destruction
   static const uint32_t kNoID = 0;
   void SetID(uint32_t aID);
 
   static uint32_t GetContentProcessIdFor(dom::ContentParentId aIPCContentId);
   static void ReleaseContentProcessIdFor(dom::ContentParentId aIPCContentId);
 
   static void SetHandlerControl(DWORD aPid, RefPtr<IHandlerControl> aCtrl);
 
+  bool DispatchTextChangeToHandler(bool aIsInsert, const nsString& aText,
+                                   int32_t aStart, uint32_t aLen);
 protected:
   virtual ~AccessibleWrap();
 
   uint32_t mID;
 
   HRESULT
   ResolveChild(const VARIANT& aVarChild, IAccessible** aOutInterface);
 
--- a/accessible/windows/msaa/DocAccessibleWrap.cpp
+++ b/accessible/windows/msaa/DocAccessibleWrap.cpp
@@ -126,28 +126,17 @@ void*
 DocAccessibleWrap::GetNativeWindow() const
 {
   if (XRE_IsContentProcess()) {
     DocAccessibleChild* ipcDoc = IPCDoc();
     if (!ipcDoc) {
       return nullptr;
     }
 
-    HWND hWnd = ipcDoc->GetEmulatedWindowHandle();
-    if (hWnd) {
-      return hWnd;
-    }
-
-    auto tab = static_cast<dom::TabChild*>(ipcDoc->Manager());
-    MOZ_ASSERT(tab);
-    if (!tab) {
-      return nullptr;
-    }
-
-    return reinterpret_cast<HWND>(tab->GetNativeWindowHandle());
+    return ipcDoc->GetNativeWindowHandle();
   } else if (mHWND) {
     return mHWND;
   }
   return DocAccessible::GetNativeWindow();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // DocAccessible protected
--- a/accessible/windows/msaa/Platform.cpp
+++ b/accessible/windows/msaa/Platform.cpp
@@ -127,16 +127,24 @@ a11y::ProxyTextChangeEvent(ProxyAccessib
                            int32_t aStart, uint32_t aLen, bool aInsert, bool)
 {
   AccessibleWrap* wrapper = WrapperFor(aText);
   MOZ_ASSERT(wrapper);
   if (!wrapper) {
     return;
   }
 
+  static const bool useHandler =
+    Preferences::GetBool("accessibility.handler.enabled", false);
+
+  if (useHandler) {
+    wrapper->DispatchTextChangeToHandler(aInsert, aStr, aStart, aLen);
+    return;
+  }
+
   auto text = static_cast<HyperTextAccessibleWrap*>(wrapper->AsHyperText());
   if (text) {
     ia2AccessibleText::UpdateTextChangeData(text, aInsert, aStr, aStart, aLen);
   }
 
   uint32_t eventType = aInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED :
     nsIAccessibleEvent::EVENT_TEXT_REMOVED;
   AccessibleWrap::FireWinEvent(wrapper, eventType);
--- a/browser/components/extensions/ext-sessions.js
+++ b/browser/components/extensions/ext-sessions.js
@@ -57,16 +57,47 @@ this.sessions = class extends ExtensionA
     let {extension} = context;
     return {
       sessions: {
         getRecentlyClosed: function(filter) {
           let maxResults = filter.maxResults == undefined ? this.MAX_SESSION_RESULTS : filter.maxResults;
           return Promise.resolve(getRecentlyClosed(maxResults, extension));
         },
 
+        forgetClosedTab: function(windowId, sessionId) {
+          let window = context.extension.windowManager.get(windowId).window;
+          let closedTabData = SessionStore.getClosedTabData(window, false);
+
+          let closedTabIndex = closedTabData.findIndex((closedTab) => {
+            return closedTab.closedId === parseInt(sessionId, 10);
+          });
+
+          if (closedTabIndex < 0) {
+            return Promise.reject({message: `Could not find closed tab using sessionId ${sessionId}.`});
+          }
+
+          SessionStore.forgetClosedTab(window, closedTabIndex);
+          return Promise.resolve();
+        },
+
+        forgetClosedWindow: function(sessionId) {
+          let closedWindowData = SessionStore.getClosedWindowData(false);
+
+          let closedWindowIndex = closedWindowData.findIndex((closedWindow) => {
+            return closedWindow.closedId === parseInt(sessionId, 10);
+          });
+
+          if (closedWindowIndex < 0) {
+            return Promise.reject({message: `Could not find closed window using sessionId ${sessionId}.`});
+          }
+
+          SessionStore.forgetClosedWindow(closedWindowIndex);
+          return Promise.resolve();
+        },
+
         restore: function(sessionId) {
           let session, closedId;
           if (sessionId) {
             closedId = sessionId;
             session = SessionStore.undoCloseById(closedId);
           } else if (SessionStore.lastClosedObjectType == "window") {
             // If the most recently closed object is a window, just undo closing the most recent window.
             session = SessionStore.undoCloseWindow(0);
--- a/browser/components/extensions/schemas/sessions.json
+++ b/browser/components/extensions/schemas/sessions.json
@@ -51,16 +51,47 @@
           "info": {"type": "string"},
           "deviceName": {"type": "string", "description": "The name of the foreign device."},
           "sessions": {"type": "array", "items": {"$ref": "Session"}, "description": "A list of open window sessions for the foreign device, sorted from most recently to least recently modified session."}
         }
       }
     ],
     "functions": [
       {
+        "name": "forgetClosedTab",
+        "type": "function",
+        "description": "Forget a recently closed tab.",
+        "async": true,
+        "parameters": [
+          {
+            "name": "windowId",
+            "type": "integer",
+            "description": "The windowId of the window to which the recently closed tab to be forgotten belongs."
+          },
+          {
+            "name": "sessionId",
+            "type": "string",
+            "description": "The sessionId (closedId) of the recently closed tab to be forgotten."
+          }
+        ]
+      },
+      {
+        "name": "forgetClosedWindow",
+        "type": "function",
+        "description": "Forget a recently closed window.",
+        "async": true,
+        "parameters": [
+          {
+            "name": "sessionId",
+            "type": "string",
+            "description": "The sessionId (closedId) of the recently closed window to be forgotten."
+          }
+        ]
+      },
+      {
         "name": "getRecentlyClosed",
         "type": "function",
         "description": "Gets the list of recently closed tabs and/or windows.",
         "async": "callback",
         "parameters": [
           {
             "$ref": "Filter",
             "name": "filter",
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -77,16 +77,18 @@ support-files =
 [browser_ext_popup_api_injection.js]
 [browser_ext_popup_background.js]
 [browser_ext_popup_corners.js]
 [browser_ext_popup_sendMessage.js]
 [browser_ext_popup_shutdown.js]
 [browser_ext_runtime_openOptionsPage.js]
 [browser_ext_runtime_openOptionsPage_uninstall.js]
 [browser_ext_runtime_setUninstallURL.js]
+[browser_ext_sessions_forgetClosedTab.js]
+[browser_ext_sessions_forgetClosedWindow.js]
 [browser_ext_sessions_getRecentlyClosed.js]
 [browser_ext_sessions_getRecentlyClosed_private.js]
 [browser_ext_sessions_getRecentlyClosed_tabs.js]
 [browser_ext_sessions_restore.js]
 [browser_ext_sidebarAction.js]
 [browser_ext_sidebarAction_context.js]
 [browser_ext_sidebarAction_tabs.js]
 [browser_ext_sidebarAction_windows.js]
@@ -97,16 +99,17 @@ support-files =
 [browser_ext_tabs_create.js]
 [browser_ext_tabs_create_invalid_url.js]
 [browser_ext_tabs_detectLanguage.js]
 [browser_ext_tabs_duplicate.js]
 [browser_ext_tabs_events.js]
 [browser_ext_tabs_executeScript.js]
 [browser_ext_tabs_executeScript_good.js]
 [browser_ext_tabs_executeScript_bad.js]
+[browser_ext_tabs_executeScript_multiple.js]
 [browser_ext_tabs_executeScript_no_create.js]
 [browser_ext_tabs_executeScript_runAt.js]
 [browser_ext_tabs_getCurrent.js]
 [browser_ext_tabs_insertCSS.js]
 [browser_ext_tabs_removeCSS.js]
 [browser_ext_tabs_move.js]
 [browser_ext_tabs_move_array.js]
 [browser_ext_tabs_move_window.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_forgetClosedTab.js
@@ -0,0 +1,70 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* test_sessions_forget_closed_tab() {
+  function background() {
+    browser.test.onMessage.addListener((msg, windowId, sessionId) => {
+      if (msg === "check-sessions") {
+        browser.sessions.getRecentlyClosed().then(recentlyClosed => {
+          browser.test.sendMessage("recentlyClosed", recentlyClosed);
+        });
+      } else if (msg === "forget-tab") {
+        browser.sessions.forgetClosedTab(windowId, sessionId).then(
+          () => {
+            browser.test.sendMessage("forgot-tab");
+          },
+          error => {
+            browser.test.assertEq(error.message,
+                     `Could not find closed tab using sessionId ${sessionId}.`);
+            browser.test.sendMessage("forget-reject");
+          }
+        );
+      }
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["sessions", "tabs"],
+    },
+    background,
+  });
+
+  yield extension.startup();
+
+  let tabUrl = "http://example.com";
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, tabUrl);
+  yield BrowserTestUtils.removeTab(tab);
+  tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, tabUrl);
+  yield BrowserTestUtils.removeTab(tab);
+
+  extension.sendMessage("check-sessions");
+  let recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  let recentlyClosedLength = recentlyClosed.length;
+  let recentlyClosedTab = recentlyClosed[0].tab;
+
+  // Check that forgetting a tab works properly
+  extension.sendMessage("forget-tab", recentlyClosedTab.windowId,
+                                      recentlyClosedTab.sessionId);
+  yield extension.awaitMessage("forgot-tab");
+  extension.sendMessage("check-sessions");
+  let remainingClosed = yield extension.awaitMessage("recentlyClosed");
+  is(remainingClosed.length, recentlyClosedLength - 1,
+     "One tab was forgotten.");
+  is(remainingClosed[0].tab.sessionId, recentlyClosed[1].tab.sessionId,
+     "The correct tab was forgotten.");
+
+  // Check that re-forgetting the same tab fails properly
+  extension.sendMessage("forget-tab", recentlyClosedTab.windowId,
+                                      recentlyClosedTab.sessionId);
+  yield extension.awaitMessage("forget-reject");
+  extension.sendMessage("check-sessions");
+  remainingClosed = yield extension.awaitMessage("recentlyClosed");
+  is(remainingClosed.length, recentlyClosedLength - 1,
+     "No extra tab was forgotten.");
+  is(remainingClosed[0].tab.sessionId, recentlyClosed[1].tab.sessionId,
+     "The correct tab remains.");
+
+  yield extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_forgetClosedWindow.js
@@ -0,0 +1,72 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* test_sessions_forget_closed_window() {
+  function* openAndCloseWindow(url = "http://example.com") {
+    let win = yield BrowserTestUtils.openNewBrowserWindow();
+    yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, url);
+    yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+    yield BrowserTestUtils.closeWindow(win);
+  }
+
+  function background() {
+    browser.test.onMessage.addListener((msg, sessionId) => {
+      if (msg === "check-sessions") {
+        browser.sessions.getRecentlyClosed().then(recentlyClosed => {
+          browser.test.sendMessage("recentlyClosed", recentlyClosed);
+        });
+      } else if (msg === "forget-window") {
+        browser.sessions.forgetClosedWindow(sessionId).then(
+          () => {
+            browser.test.sendMessage("forgot-window");
+          },
+          error => {
+            browser.test.assertEq(error.message,
+                  `Could not find closed window using sessionId ${sessionId}.`);
+            browser.test.sendMessage("forget-reject");
+          }
+        );
+      }
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["sessions", "tabs"],
+    },
+    background,
+  });
+
+  yield extension.startup();
+
+  yield openAndCloseWindow("about:config");
+  yield openAndCloseWindow("about:robots");
+
+  extension.sendMessage("check-sessions");
+  let recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+  let recentlyClosedLength = recentlyClosed.length;
+  let recentlyClosedWindow = recentlyClosed[0].window;
+
+  // Check that forgetting a window works properly
+  extension.sendMessage("forget-window", recentlyClosedWindow.sessionId);
+  yield extension.awaitMessage("forgot-window");
+  extension.sendMessage("check-sessions");
+  let remainingClosed = yield extension.awaitMessage("recentlyClosed");
+  is(remainingClosed.length, recentlyClosedLength - 1,
+     "One window was forgotten.");
+  is(remainingClosed[0].window.sessionId, recentlyClosed[1].window.sessionId,
+     "The correct window was forgotten.");
+
+  // Check that re-forgetting the same window fails properly
+  extension.sendMessage("forget-window", recentlyClosedWindow.sessionId);
+  yield extension.awaitMessage("forget-reject");
+  extension.sendMessage("check-sessions");
+  remainingClosed = yield extension.awaitMessage("recentlyClosed");
+  is(remainingClosed.length, recentlyClosedLength - 1,
+     "No extra window was forgotten.");
+  is(remainingClosed[0].window.sessionId, recentlyClosed[1].window.sessionId,
+     "The correct window remains.");
+
+  yield extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_multiple.js
@@ -0,0 +1,50 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testExecuteScript() {
+  const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
+  const URL = BASE + "file_dummy.html";
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL, true);
+
+  async function background() {
+    try {
+      await browser.tabs.executeScript({code: "this.foo = 'bar'"});
+      await browser.tabs.executeScript({file: "script.js"});
+
+      let [result1] = await browser.tabs.executeScript({code: "[this.foo, this.bar]"});
+      let [result2] = await browser.tabs.executeScript({file: "script2.js"});
+
+      browser.test.assertEq("bar,baz", String(result1), "executeScript({code}) result");
+      browser.test.assertEq("bar,baz", String(result2), "executeScript({file}) result");
+
+      browser.test.notifyPass("executeScript-multiple");
+    } catch (e) {
+      browser.test.fail(`Error: ${e} :: ${e.stack}`);
+      browser.test.notifyFail("executeScript-multiple");
+    }
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["http://mochi.test/"],
+    },
+
+    background,
+
+    files: {
+      "script.js": function() {
+        this.bar = "baz";
+      },
+
+      "script2.js": "[this.foo, this.bar]",
+    },
+  });
+
+  yield extension.startup();
+
+  yield extension.awaitFinish("executeScript-multiple");
+
+  yield extension.unload();
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/dom/base/nsDocumentEncoder.cpp
+++ b/dom/base/nsDocumentEncoder.cpp
@@ -137,16 +137,35 @@ protected:
           return false;
       }
     }
     return true;
   }
 
   virtual bool IncludeInContext(nsINode *aNode);
 
+  class MOZ_STACK_CLASS AutoReleaseDocumentIfNeeded final
+  {
+  public:
+    explicit AutoReleaseDocumentIfNeeded(nsDocumentEncoder* aEncoder)
+      : mEncoder(aEncoder)
+    {
+    }
+
+    ~AutoReleaseDocumentIfNeeded()
+    {
+      if (mEncoder->mFlags & RequiresReinitAfterOutput) {
+        mEncoder->mDocument = nullptr;
+      }
+    }
+
+  private:
+    nsDocumentEncoder* mEncoder;
+  };
+
   nsCOMPtr<nsIDocument>          mDocument;
   nsCOMPtr<nsISelection>         mSelection;
   RefPtr<nsRange>              mRange;
   nsCOMPtr<nsINode>              mNode;
   nsCOMPtr<nsIOutputStream>      mStream;
   nsCOMPtr<nsIContentSerializer> mSerializer;
   nsCOMPtr<nsIUnicodeEncoder>    mUnicodeEncoder;
   nsCOMPtr<nsINode>              mCommonParent;
@@ -1004,16 +1023,18 @@ nsDocumentEncoder::EncodeToString(nsAStr
 
 NS_IMETHODIMP
 nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength,
                                                nsAString& aOutputString)
 {
   if (!mDocument)
     return NS_ERROR_NOT_INITIALIZED;
 
+  AutoReleaseDocumentIfNeeded autoReleaseDocument(this);
+
   aOutputString.Truncate();
 
   nsString output;
   static const size_t bufferSize = 2048;
   if (!mCachedBuffer) {
     mCachedBuffer = nsStringBuffer::Alloc(bufferSize).take();
     if (NS_WARN_IF(!mCachedBuffer)) {
       return NS_ERROR_OUT_OF_MEMORY;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -5502,17 +5502,17 @@ nsGlobalWindow::CSSToDevIntPixels(nsIntS
     presContext->CSSPixelsToDevPixels(px.height));
 }
 
 nsresult
 nsGlobalWindow::GetInnerSize(CSSIntSize& aSize)
 {
   MOZ_ASSERT(IsOuterWindow());
 
-  EnsureSizeUpToDate();
+  EnsureSizeAndPositionUpToDate();
 
   NS_ENSURE_STATE(mDocShell);
 
   RefPtr<nsPresContext> presContext;
   mDocShell->GetPresContext(getter_AddRefs(presContext));
   RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
 
   if (!presContext || !presShell) {
@@ -5978,20 +5978,17 @@ nsRect
 nsGlobalWindow::GetInnerScreenRect()
 {
   MOZ_ASSERT(IsOuterWindow());
 
   if (!mDocShell) {
     return nsRect();
   }
 
-  nsGlobalWindow* rootWindow = nsGlobalWindow::Cast(GetPrivateRoot());
-  if (rootWindow) {
-    rootWindow->FlushPendingNotifications(FlushType::Layout);
-  }
+  EnsureSizeAndPositionUpToDate();
 
   if (!mDocShell) {
     return nsRect();
   }
 
   nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
   if (!presShell) {
     return nsRect();
@@ -6458,17 +6455,17 @@ nsGlobalWindow::GetScrollMaxY(ErrorResul
 CSSPoint
 nsGlobalWindow::GetScrollXY(bool aDoFlush)
 {
   MOZ_ASSERT(IsOuterWindow());
 
   if (aDoFlush) {
     FlushPendingNotifications(FlushType::Layout);
   } else {
-    EnsureSizeUpToDate();
+    EnsureSizeAndPositionUpToDate();
   }
 
   nsIScrollableFrame *sf = GetScrollFrame();
   if (!sf) {
     return CSSIntPoint(0, 0);
   }
 
   nsPoint scrollPos = sf->GetScrollPosition();
@@ -13216,17 +13213,17 @@ void
 nsGlobalWindow::FlushPendingNotifications(FlushType aType)
 {
   if (mDoc) {
     mDoc->FlushPendingNotifications(aType);
   }
 }
 
 void
-nsGlobalWindow::EnsureSizeUpToDate()
+nsGlobalWindow::EnsureSizeAndPositionUpToDate()
 {
   MOZ_ASSERT(IsOuterWindow());
 
   // If we're a subframe, make sure our size is up to date.  It's OK that this
   // crosses the content/chrome boundary, since chrome can have pending reflows
   // too.
   nsGlobalWindow *parent = nsGlobalWindow::Cast(GetPrivateParent());
   if (parent) {
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -418,17 +418,17 @@ public:
 
   // Outer windows only.
   void DispatchDOMWindowCreated();
 
   virtual void SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
                                bool aOriginalOpener) override;
 
   // Outer windows only.
-  virtual void EnsureSizeUpToDate() override;
+  virtual void EnsureSizeAndPositionUpToDate() override;
 
   virtual void EnterModalState() override;
   virtual void LeaveModalState() override;
 
   // Outer windows only.
   virtual bool CanClose() override;
   virtual void ForceClose() override;
 
--- a/dom/base/nsIDocumentEncoder.idl
+++ b/dom/base/nsIDocumentEncoder.idl
@@ -243,16 +243,23 @@ interface nsIDocumentEncoder : nsISuppor
   /**
    * Disallow breaking of long character strings. This is important
    * for serializing e-mail which contains CJK strings. These must
    * not be broken just as "normal" longs strings aren't broken.
    */
   const unsigned long OutputDisallowLineBreaking = (1 << 27);
 
   /**
+   * Release reference of nsIDocument after using encodeTo* method to recycle
+   * this encoder without holding nsIDocument. To use this encoder again,
+   * we have to call init again.
+   */
+  const unsigned long RequiresReinitAfterOutput = (1 << 28);
+
+  /**
    * Initialize with a pointer to the document and the mime type.
    * @param aDocument Document to encode.
    * @param aMimeType MimeType to use. May also be set by SetMimeType.
    * @param aFlags Flags to use while encoding. May also be set by SetFlags.
    */
   void init(in nsIDOMDocument aDocument,
             in AString aMimeType,
             in unsigned long aFlags);
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -308,17 +308,22 @@ public:
    * original opener for the window.  That is, it can only be true at most once
    * during the life cycle of a window, and then only the first time
    * SetOpenerWindow is called.  It might never be true, of course, if the
    * window does not have an opener when it's created.
    */
   virtual void SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
                                bool aOriginalOpener) = 0;
 
-  virtual void EnsureSizeUpToDate() = 0;
+  /**
+   * Ensure the size and position of this window are up-to-date by doing
+   * a layout flush in the parent (which will in turn, do a layout flush
+   * in its parent, etc.).
+   */
+  virtual void EnsureSizeAndPositionUpToDate() = 0;
 
   /**
    * Callback for notifying a window about a modal dialog being
    * opened/closed with the window as a parent.
    */
   virtual void EnterModalState() = 0;
   virtual void LeaveModalState() = 0;
 
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -624,16 +624,17 @@ skip-if = toolkit == 'android' || e10s #
 [test_domparsing.html]
 [test_domrequest.html]
 [test_domwindowutils.html]
 [test_e4x_for_each.html]
 [test_element.matches.html]
 [test_element_closest.html]
 [test_elementTraversal.html]
 [test_encodeToStringWithMaxLength.html]
+[test_encodeToStringWithRequiresReinitAfterOutput.html]
 [test_error.html]
 [test_EventSource_redirects.html]
 [test_explicit_user_agent.html]
 skip-if = (toolkit == 'android') # Android: Bug 775227
 [test_getAttribute_after_createAttribute.html]
 [test_getElementById.html]
 [test_getTranslationNodes.html]
 [test_getTranslationNodes_limit.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_encodeToStringWithRequiresReinitAfterOutput.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1352882
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1352882 - RequiresReinitAfterOutput</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+  function getEncoder() {
+    const de = SpecialPowers.Ci.nsIDocumentEncoder;
+    const Cc = SpecialPowers.Cc;
+
+    // Create a plaintext encoder without flags.
+    var encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
+                  .createInstance(de);
+    encoder.init(document, "text/plain", encoder.RequiresReinitAfterOutput);
+    return encoder;
+  }
+
+  function testPlaintextSerializerWithRequiresReinitAfterOutput() {
+    var encoder = getEncoder();
+
+    var str = encoder.encodeToString();
+    ok(str, "encodingToString should be successful");
+
+    SimpleTest.doesThrow(() => {
+      encoder.encodeToString();
+    }, 'encodeToString should throw exception if it has RequiresReinitAfterOutput and it is called twice');
+
+    encoder.init(document, "text/plain", encoder.RequiresReinitAfterOutput);
+    str = encoder.encodeToString();
+    ok(str, "encodingToString should be successful after calling init again");
+
+    encoder = getEncoder();
+
+    str = encoder.encodeToStringWithMaxLength(1000);
+    ok(str, "encodingToString should be successful");
+
+    SimpleTest.doesThrow(() => {
+      encoder.encodeToStringWithMaxLength(1000);
+    }, 'encodeToStringWithMaxLength should throw exception if it has RequiresReinitAfterOutput and it is called twice');
+
+    encoder.init(document, "text/plain", encoder.RequiresReinitAfterOutput);
+    str = encoder.encodeToStringWithMaxLength(1000);
+    ok(str, "encodingToStringWithMaxLength should be successful after calling init again");
+
+    encoder = getEncoder();
+    var stream = {
+      close: function() {
+      },
+
+      flush: function() {
+      },
+
+      write: function(buffer, length) {
+        return length;
+      },
+
+      writeFrom: function(buffer, length) {
+        return length;
+      },
+    };
+
+    encoder.setCharset("utf-8");
+    encoder.encodeToStream(stream);
+    ok(str, "encodingToStream should be successful");
+
+    SimpleTest.doesThrow(() => {
+      encoder.encodeToStream(stream);
+    }, 'encodeToStream should throw exception if it has RequiresReinitAfterOutput and it is called twice');
+
+    encoder.init(document, "text/plain", encoder.RequiresReinitAfterOutput);
+    encoder.encodeToStream(stream);
+    ok(true, "encodingToStream should be successful after calling init again");
+
+    stream.close();
+
+    SimpleTest.finish();
+  }
+
+  addLoadEvent(testPlaintextSerializerWithRequiresReinitAfterOutput);
+  SimpleTest.waitForExplicitFinish();
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1352882">Mozilla Bug 1352882</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+The <em>Mozilla</em> project is a global community of <strong>people</strong> who believe that openness, innovation, and opportunity are key to the continued health of the Internet. We have worked together since 1998 to ensure that the Internet is developed in a way that benefits everyone. We are best known for creating the Mozilla Firefox web browser.
+
+The Mozilla project uses a community-based approach to create world-class open source software and to develop new types of collaborative activities. We create communities of people involved in making the Internet experience better for all of us.
+
+As a result of these efforts, we have distilled a set of principles that we believe are critical for the Internet to continue to benefit the public good as well as commercial aspects of life. We set out these principles below.
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3796,19 +3796,18 @@ CanvasRenderingContext2D::Ellipse(double
   ArcToBezier(this, Point(aX, aY), Size(aRadiusX, aRadiusY), aStartAngle, aEndAngle,
               aAnticlockwise, aRotation);
 }
 
 void
 CanvasRenderingContext2D::EnsureWritablePath()
 {
   EnsureTarget();
-  if (!IsTargetValid()) {
-    return;
-  }
+  // NOTE: IsTargetValid() may be false here (mTarget == sErrorTarget) but we
+  // go ahead and create a path anyway since callers depend on that.
 
   if (mDSPathBuilder) {
     return;
   }
 
   FillRule fillRule = CurrentState().fillRule;
 
   if (mPathBuilder) {
new file mode 100644
--- /dev/null
+++ b/dom/canvas/crashtests/1357092.html
@@ -0,0 +1,10 @@
+<html>
+    <head>
+        <script>
+            o1 = document.createElement("canvas");
+            o1.setAttribute("width", "100000");
+            o2 = o1.getContext("2d");
+            o2.bezierCurveTo(64, 1, 1, 1, 1, 1);
+        </script>
+    </head>
+</html>
--- a/dom/canvas/crashtests/crashtests.list
+++ b/dom/canvas/crashtests/crashtests.list
@@ -37,9 +37,9 @@ load 1288872-1.html
 load 1290628-1.html
 load 1283113-1.html
 load 1286458-1.html
 load 1299062-1.html
 load 1305312-1.html
 load 1298576-1.html
 load 1334366-1.html
 load 1334647-1.html
-
+load 1357092.html
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -332,18 +332,18 @@ DataTransfer::GetTypes(nsTArray<nsString
       continue;
     }
 
     // NOTE: The reason why we get the internal type here is because we want
     // kFileMime to appear in the types list for backwards compatibility
     // reasons.
     nsAutoString type;
     item->GetInternalType(type);
-    if (item->Kind() == DataTransferItem::KIND_STRING || type.EqualsASCII(kFileMime)) {
-      // If the entry has kind KIND_STRING, we want to add it to the list.
+    if (item->Kind() != DataTransferItem::KIND_FILE || type.EqualsASCII(kFileMime)) {
+      // If the entry has kind KIND_STRING or KIND_OTHER we want to add it to the list.
       aTypes.AppendElement(type);
     }
   }
 
   for (uint32_t i = 0; i < mItems->Length(); ++i) {
     bool found = false;
     DataTransferItem* item = mItems->IndexedGetter(i, found);
     MOZ_ASSERT(found);
--- a/dom/events/test/test_dragstart.html
+++ b/dom/events/test/test_dragstart.html
@@ -310,16 +310,33 @@ function test_DataTransfer(dt)
                    ["First Item", "Changed with setData"], 0, "changed with setData item at index 0");
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["Changed Second Item", "<em>Second Item</em>"], 1, "changed with setData item at index 1");
 
   dt.mozSetDataAt("application/-moz-node", "draggable", 2);
   is(dt.mozItemCount, 3, "setDataAt node itemCount");
   checkOneDataItem(dt, ["application/-moz-node"], ["draggable"], 2, "setDataAt node item at index 2");
 
+  // Try to add and then remove a non-string type to the DataTransfer and ensure
+  // that the type appears in DataTransfer.types. These calls need to be called
+  // with SpecialPowers.wrap(), as adding and removing non-string/file types to
+  // DataTransfer is chrome-only.
+  {
+    SpecialPowers.wrap(dt).mozSetDataAt("application/-x-body", document.body, 0);
+    let found = false;
+    for (let i = 0; i < dt.types.length; ++i) {
+      if (dt.types[i] == "application/-x-body") {
+        found = true;
+        break;
+      }
+    }
+    ok(found, "Data should appear in datatransfer.types despite having a non-string type");
+    SpecialPowers.wrap(dt).mozClearDataAt("application/-x-body", 0);
+  }
+
   dt.mozClearDataAt("text/html", 1);
   is(dt.mozItemCount, 3, "clearDataAt itemCount");
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["First Item", "Changed with setData"], 0, "clearDataAt item at index 0");
   checkOneDataItem(dt, ["text/plain"], ["Changed Second Item"], 1, "clearDataAt item at index 1");
 
   dt.mozClearDataAt("text/plain", 1);
   is(dt.mozItemCount, 2, "clearDataAt last type itemCount");
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -263,17 +263,16 @@ VRPose::VRPose(nsISupports* aParent, con
   mFrameId = aState.inputFrameID;
   mozilla::HoldJSObjects(this);
 }
 
 VRPose::VRPose(nsISupports* aParent)
   : Pose(aParent)
 {
   mFrameId = 0;
-  mVRState.Clear();
   mozilla::HoldJSObjects(this);
 }
 
 VRPose::~VRPose()
 {
   mozilla::DropJSObjects(this);
 }
 
@@ -856,17 +855,16 @@ VRFrameInfo::Update(const gfx::VRDisplay
   mLeftProjection = leftFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
   const gfx::VRFieldOfView rightFOV = aInfo.mEyeFOV[gfx::VRDisplayInfo::Eye_Right];
   mRightProjection = rightFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
 }
 
 VRFrameInfo::VRFrameInfo()
  : mTimeStampOffset(0.0f)
 {
-  mVRState.Clear();
 }
 
 bool
 VRFrameInfo::IsDirty()
 {
   return mVRState.timestamp == 0;
 }
 
--- a/dom/vr/VRServiceTest.cpp
+++ b/dom/vr/VRServiceTest.cpp
@@ -25,17 +25,16 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEvent
 
 NS_IMPL_ADDREF_INHERITED(VRMockDisplay, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(VRMockDisplay, DOMEventTargetHelper)
 
 VRMockDisplay::VRMockDisplay(const nsCString& aID, uint32_t aDeviceID)
  : mDeviceID(aDeviceID)
  , mTimestamp(TimeStamp::Now())
 {
-  mSensorState.Clear();
   mDisplayInfo.mDisplayName = aID;
   mDisplayInfo.mType = VRDeviceType::Puppet;
   mDisplayInfo.mIsConnected = true;
   mDisplayInfo.mIsMounted = false;
   mDisplayInfo.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None |
                                   VRDisplayCapabilityFlags::Cap_Orientation |
                                   VRDisplayCapabilityFlags::Cap_AngularAcceleration |
                                   VRDisplayCapabilityFlags::Cap_Position |
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -101,20 +101,22 @@ TextEditor::~TextEditor()
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TextEditor, EditorBase)
   if (tmp->mRules)
     tmp->mRules->DetachEditor();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mRules)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedDocumentEncoder)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextEditor, EditorBase)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRules)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedDocumentEncoder)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(TextEditor, EditorBase)
 NS_IMPL_RELEASE_INHERITED(TextEditor, EditorBase)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TextEditor)
   NS_INTERFACE_MAP_ENTRY(nsIPlaintextEditor)
   NS_INTERFACE_MAP_ENTRY(nsIEditorMailSupport)
@@ -1164,67 +1166,85 @@ NS_IMETHODIMP
 TextEditor::CanDelete(bool* aCanDelete)
 {
   NS_ENSURE_ARG_POINTER(aCanDelete);
   *aCanDelete = IsModifiable() && CanCutOrCopy(ePasswordFieldAllowed);
   return NS_OK;
 }
 
 // Shared between OutputToString and OutputToStream
-nsresult
+already_AddRefed<nsIDocumentEncoder>
 TextEditor::GetAndInitDocEncoder(const nsAString& aFormatType,
                                  uint32_t aFlags,
-                                 const nsACString& aCharset,
-                                 nsIDocumentEncoder** encoder)
+                                 const nsACString& aCharset)
 {
-  nsresult rv = NS_OK;
-
-  nsAutoCString formatType(NS_DOC_ENCODER_CONTRACTID_BASE);
-  LossyAppendUTF16toASCII(aFormatType, formatType);
-  nsCOMPtr<nsIDocumentEncoder> docEncoder (do_CreateInstance(formatType.get(), &rv));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIDocumentEncoder> docEncoder;
+  if (!mCachedDocumentEncoder ||
+      !mCachedDocumentEncoderType.Equals(aFormatType)) {
+    nsAutoCString formatType(NS_DOC_ENCODER_CONTRACTID_BASE);
+    LossyAppendUTF16toASCII(aFormatType, formatType);
+    docEncoder = do_CreateInstance(formatType.get());
+    if (NS_WARN_IF(!docEncoder)) {
+      return nullptr;
+    }
+    mCachedDocumentEncoder = docEncoder;
+    mCachedDocumentEncoderType = aFormatType;
+  } else {
+    docEncoder = mCachedDocumentEncoder;
+  }
 
   nsCOMPtr<nsIDOMDocument> domDoc = do_QueryReferent(mDocWeak);
   NS_ASSERTION(domDoc, "Need a document");
 
-  rv = docEncoder->Init(domDoc, aFormatType, aFlags);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv =
+    docEncoder->Init(domDoc, aFormatType,
+                     aFlags | nsIDocumentEncoder::RequiresReinitAfterOutput);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
 
   if (!aCharset.IsEmpty() && !aCharset.EqualsLiteral("null")) {
     docEncoder->SetCharset(aCharset);
   }
 
   int32_t wc;
   (void) GetWrapWidth(&wc);
   if (wc >= 0) {
     (void) docEncoder->SetWrapColumn(wc);
   }
 
   // Set the selection, if appropriate.
   // We do this either if the OutputSelectionOnly flag is set,
   // in which case we use our existing selection ...
   if (aFlags & nsIDocumentEncoder::OutputSelectionOnly) {
     RefPtr<Selection> selection = GetSelection();
-    NS_ENSURE_STATE(selection);
+    if (NS_WARN_IF(!selection)) {
+      return nullptr;
+    }
     rv = docEncoder->SetSelection(selection);
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
   }
   // ... or if the root element is not a body,
   // in which case we set the selection to encompass the root.
   else {
     dom::Element* rootElement = GetRoot();
-    NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
+    if (NS_WARN_IF(!rootElement)) {
+      return nullptr;
+    }
     if (!rootElement->IsHTMLElement(nsGkAtoms::body)) {
       rv = docEncoder->SetNativeContainerNode(rootElement);
-      NS_ENSURE_SUCCESS(rv, rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return nullptr;
+      }
     }
   }
 
-  docEncoder.forget(encoder);
-  return NS_OK;
+  return docEncoder.forget();
 }
 
 
 NS_IMETHODIMP
 TextEditor::OutputToString(const nsAString& aFormatType,
                            uint32_t aFlags,
                            nsAString& aOutputString)
 {
@@ -1249,19 +1269,22 @@ TextEditor::OutputToString(const nsAStri
   }
 
   nsAutoCString charsetStr;
   rv = GetDocumentCharacterSet(charsetStr);
   if (NS_FAILED(rv) || charsetStr.IsEmpty()) {
     charsetStr.AssignLiteral("ISO-8859-1");
   }
 
-  nsCOMPtr<nsIDocumentEncoder> encoder;
-  rv = GetAndInitDocEncoder(aFormatType, aFlags, charsetStr, getter_AddRefs(encoder));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIDocumentEncoder> encoder =
+    GetAndInitDocEncoder(aFormatType, aFlags, charsetStr);
+  if (NS_WARN_IF(!encoder)) {
+    return NS_ERROR_FAILURE;
+  }
+
   return encoder->EncodeToString(aOutputString);
 }
 
 NS_IMETHODIMP
 TextEditor::OutputToStream(nsIOutputStream* aOutputStream,
                            const nsAString& aFormatType,
                            const nsACString& aCharset,
                            uint32_t aFlags)
@@ -1276,21 +1299,21 @@ TextEditor::OutputToStream(nsIOutputStre
     rv = GetDocumentIsEmpty(&docEmpty);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (docEmpty) {
       return NS_OK; // Output nothing.
     }
   }
 
-  nsCOMPtr<nsIDocumentEncoder> encoder;
-  rv = GetAndInitDocEncoder(aFormatType, aFlags, aCharset,
-                            getter_AddRefs(encoder));
-
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIDocumentEncoder> encoder =
+    GetAndInitDocEncoder(aFormatType, aFlags, aCharset);
+  if (NS_WARN_IF(!encoder)) {
+    return NS_ERROR_FAILURE;
+  }
 
   return encoder->EncodeToStream(aOutputStream);
 }
 
 NS_IMETHODIMP
 TextEditor::InsertTextWithQuotations(const nsAString& aStringToInsert)
 {
   return InsertText(aStringToInsert);
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -179,20 +179,20 @@ public:
 
 protected:
   virtual ~TextEditor();
 
   NS_IMETHOD InitRules();
   void BeginEditorInit();
   nsresult EndEditorInit();
 
-  nsresult GetAndInitDocEncoder(const nsAString& aFormatType,
-                                uint32_t aFlags,
-                                const nsACString& aCharset,
-                                nsIDocumentEncoder** encoder);
+  already_AddRefed<nsIDocumentEncoder> GetAndInitDocEncoder(
+                                         const nsAString& aFormatType,
+                                         uint32_t aFlags,
+                                         const nsACString& aCharset);
 
   NS_IMETHOD CreateBR(nsIDOMNode* aNode, int32_t aOffset,
                       nsCOMPtr<nsIDOMNode>* outBRNode,
                       EDirection aSelect = eNone);
   already_AddRefed<Element> CreateBRImpl(nsCOMPtr<nsINode>* aInOutParent,
                                          int32_t* aInOutOffset,
                                          EDirection aSelect);
   nsresult CreateBRImpl(nsCOMPtr<nsIDOMNode>* aInOutParent,
@@ -227,16 +227,18 @@ protected:
                           int32_t aSelectionType,
                           bool* aActionTaken = nullptr);
 
   bool UpdateMetaCharset(nsIDocument& aDocument,
                          const nsACString& aCharacterSet);
 
 protected:
   nsCOMPtr<nsIEditRules> mRules;
+  nsCOMPtr<nsIDocumentEncoder> mCachedDocumentEncoder;
+  nsString mCachedDocumentEncoderType;
   int32_t mWrapColumn;
   int32_t mMaxTextLength;
   int32_t mInitTriggerCounter;
   int32_t mNewlineHandling;
   int32_t mCaretStyle;
 
   friend class AutoEditInitRulesTrigger;
   friend class HTMLEditRules;
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <initguid.h>
 #include "DrawTargetD2D1.h"
 #include "FilterNodeSoftware.h"
 #include "GradientStopsD2D.h"
 #include "SourceSurfaceD2D1.h"
+#include "SourceSurfaceDual.h"
 #include "RadialGradientEffectD2D1.h"
 
 #include "HelpersD2D.h"
 #include "FilterNodeD2D1.h"
 #include "ExtendInputEffectD2D1.h"
 #include "Tools.h"
 
 using namespace std;
@@ -1865,16 +1866,31 @@ DrawTargetD2D1::GetImageForSurface(Sourc
   switch (aSurface->GetType()) {
   case SurfaceType::D2D1_1_IMAGE:
     {
       SourceSurfaceD2D1 *surf = static_cast<SourceSurfaceD2D1*>(aSurface);
       image = surf->GetImage();
       AddDependencyOnSource(surf);
     }
     break;
+  case SurfaceType::DUAL_DT:
+    {
+      // Sometimes we have a dual drawtarget but the underlying targets
+      // are d2d surfaces. Let's not readback and reupload in those cases.
+      SourceSurfaceDual* surface = static_cast<SourceSurfaceDual*>(aSurface);
+      SourceSurface* first = surface->GetFirstSurface();
+      if (first->GetType() == SurfaceType::D2D1_1_IMAGE) {
+        MOZ_ASSERT(surface->SameSurfaceTypes());
+        SourceSurfaceD2D1* d2dSurface = static_cast<SourceSurfaceD2D1*>(first);
+        image = d2dSurface->GetImage();
+        AddDependencyOnSource(d2dSurface); 
+        break;
+      }
+      // Otherwise fall through
+  }
   default:
     {
       RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface();
       if (!dataSurf) {
         gfxWarning() << "Invalid surface type.";
         return nullptr;
       }
       Matrix transform = aUserSpace ? mTransform : Matrix();
--- a/gfx/2d/SourceSurfaceDual.h
+++ b/gfx/2d/SourceSurfaceDual.h
@@ -22,22 +22,32 @@ public:
     : mA(aDTA->Snapshot())
     , mB(aDTB->Snapshot())
   { }
 
   virtual SurfaceType GetType() const { return SurfaceType::DUAL_DT; }
   virtual IntSize GetSize() const { return mA->GetSize(); }
   virtual SurfaceFormat GetFormat() const { return mA->GetFormat(); }
 
-  // This is implemented for debugging purposes only (used by dumping
-  // client-side textures for paint dumps), for which we don't care about
-  // component alpha, so we just use the first of the two surfaces.
+  // TODO: This is probably wrong as this was originally only
+  // used for debugging purposes, but now has legacy relying on
+  // giving the first type only.
   virtual already_AddRefed<DataSourceSurface> GetDataSurface() {
     return mA->GetDataSurface();
   }
+
+  SourceSurface* GetFirstSurface() {
+    MOZ_ASSERT(mA->GetType() == mB->GetType());
+    return mA;
+  }
+
+  bool SameSurfaceTypes() {
+    return mA->GetType() == mB->GetType();
+  }
+
 private:
   friend class DualSurface;
   friend class DualPattern;
 
   RefPtr<SourceSurface> mA;
   RefPtr<SourceSurface> mB;
 };
 
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1145,27 +1145,29 @@ nsEventStatus AsyncPanZoomController::On
     case TOUCHING: {
       ScreenCoord panThreshold = GetTouchStartTolerance();
       UpdateWithTouchAtDevicePoint(aEvent);
 
       if (PanDistance() < panThreshold) {
         return nsEventStatus_eIgnore;
       }
 
+      ParentLayerPoint touchPoint = GetFirstTouchPoint(aEvent);
+
       MOZ_ASSERT(GetCurrentTouchBlock());
       if (gfxPrefs::TouchActionEnabled() && GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) {
         // User tries to trigger a touch behavior. If allowed touch behavior is vertical pan
         // + horizontal pan (touch-action value is equal to AUTO) we can return ConsumeNoDefault
         // status immediately to trigger cancel event further. It should happen independent of
         // the parent type (whether it is scrolling or not).
-        StartPanning(aEvent);
+        StartPanning(touchPoint);
         return nsEventStatus_eConsumeNoDefault;
       }
 
-      return StartPanning(aEvent);
+      return StartPanning(touchPoint);
     }
 
     case PANNING:
     case PANNING_LOCKED_X:
     case PANNING_LOCKED_Y:
     case PAN_MOMENTUM:
       TrackTouch(aEvent);
       return nsEventStatus_eConsumeNoDefault;
@@ -1246,53 +1248,19 @@ nsEventStatus AsyncPanZoomController::On
     return nsEventStatus_eIgnore;
 
   case PANNING:
   case PANNING_LOCKED_X:
   case PANNING_LOCKED_Y:
   case PAN_MOMENTUM:
   {
     MOZ_ASSERT(GetCurrentTouchBlock());
-    GetCurrentTouchBlock()->GetOverscrollHandoffChain()->FlushRepaints();
     mX.EndTouch(aEvent.mTime);
     mY.EndTouch(aEvent.mTime);
-    ParentLayerPoint flingVelocity = GetVelocityVector();
-    // Clear our velocities; if DispatchFling() gives the fling to us,
-    // the fling velocity gets *added* to our existing velocity in
-    // AcceptFling().
-    mX.SetVelocity(0);
-    mY.SetVelocity(0);
-    // Clear our state so that we don't stay in the PANNING state
-    // if DispatchFling() gives the fling to somone else. However,
-    // don't send the state change notification until we've determined
-    // what our final state is to avoid notification churn.
-    StateChangeNotificationBlocker blocker(this);
-    SetState(NOTHING);
-
-    APZC_LOG("%p starting a fling animation if %f >= %f\n", this,
-        flingVelocity.Length().value, gfxPrefs::APZFlingMinVelocityThreshold());
-
-    if (flingVelocity.Length() < gfxPrefs::APZFlingMinVelocityThreshold()) {
-      // Relieve overscroll now if needed, since we will not transition to a fling
-      // animation and then an overscroll animation, and relieve it then.
-      GetCurrentTouchBlock()->GetOverscrollHandoffChain()->SnapBackOverscrolledApzc(this);
-      return nsEventStatus_eConsumeNoDefault;
-    }
-
-    // Make a local copy of the tree manager pointer and check that it's not
-    // null before calling DispatchFling(). This is necessary because Destroy(),
-    // which nulls out mTreeManager, could be called concurrently.
-    if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
-      FlingHandoffState handoffState{flingVelocity,
-                                     GetCurrentTouchBlock()->GetOverscrollHandoffChain(),
-                                     false /* not handoff */,
-                                     GetCurrentTouchBlock()->GetScrolledApzc()};
-      treeManagerLocal->DispatchFling(this, handoffState);
-    }
-    return nsEventStatus_eConsumeNoDefault;
+    return HandleEndOfPan();
   }
   case PINCHING:
     SetState(NOTHING);
     // Scale gesture listener should have handled this.
     NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd.");
     return nsEventStatus_eIgnore;
 
   case WHEEL_SCROLL:
@@ -1319,16 +1287,23 @@ nsEventStatus AsyncPanZoomController::On
 
   mPinchPaintTimerSet = false;
   // Note that there may not be a touch block at this point, if we received the
   // PinchGestureEvent directly from widget code without any touch events.
   if (HasReadyTouchBlock() && !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
     return nsEventStatus_eIgnore;
   }
 
+  // If zooming is not allowed, this is a two-finger pan.
+  // Start tracking panning distance and velocity.
+  if (!mZoomConstraints.mAllowZoom) {
+    mX.StartTouch(aEvent.mLocalFocusPoint.x, aEvent.mTime);
+    mY.StartTouch(aEvent.mLocalFocusPoint.y, aEvent.mTime);
+  }
+
   // For platforms that don't support APZ zooming, dispatch a message to the
   // content controller, it may want to do something else with this gesture.
   if (!gfxPrefs::APZAllowZooming()) {
     if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
       controller->NotifyPinchGesture(aEvent.mType, GetGuid(), 0, aEvent.modifiers);
     }
   }
 
@@ -1346,16 +1321,23 @@ nsEventStatus AsyncPanZoomController::On
   if (HasReadyTouchBlock() && !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
     return nsEventStatus_eIgnore;
   }
 
   if (mState != PINCHING) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
+  // If zooming is not allowed, this is a two-finger pan.
+  // Tracking panning distance and velocity.
+  if (!mZoomConstraints.mAllowZoom) {
+    mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.x, 0, aEvent.mTime);
+    mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.y, 0, aEvent.mTime);
+  }
+
   if (!gfxPrefs::APZAllowZooming()) {
     if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
       controller->NotifyPinchGesture(aEvent.mType, GetGuid(),
           ViewAs<LayoutDevicePixel>(aEvent.mCurrentSpan - aEvent.mPreviousSpan,
             PixelCastJustification::LayoutDeviceIsParentLayerForRCDRSF),
           aEvent.modifiers);
     }
   }
@@ -1472,53 +1454,109 @@ nsEventStatus AsyncPanZoomController::On
   }
 
   if (!gfxPrefs::APZAllowZooming()) {
     if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) {
       controller->NotifyPinchGesture(aEvent.mType, GetGuid(), 0, aEvent.modifiers);
     }
   }
 
-  SetState(NOTHING);
-
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
     ScheduleComposite();
     RequestContentRepaint();
     UpdateSharedCompositorFrameMetrics();
   }
 
   // Non-negative focus point would indicate that one finger is still down
   if (aEvent.mLocalFocusPoint.x != -1 && aEvent.mLocalFocusPoint.y != -1) {
-    mPanDirRestricted = false;
-    mX.StartTouch(aEvent.mLocalFocusPoint.x, aEvent.mTime);
-    mY.StartTouch(aEvent.mLocalFocusPoint.y, aEvent.mTime);
-    SetState(TOUCHING);
+    if (mZoomConstraints.mAllowZoom) {
+      mPanDirRestricted = false;
+      mX.StartTouch(aEvent.mLocalFocusPoint.x, aEvent.mTime);
+      mY.StartTouch(aEvent.mLocalFocusPoint.y, aEvent.mTime);
+      SetState(TOUCHING);
+    } else {
+      // If zooming isn't allowed, StartTouch() was already called
+      // in OnScaleBegin().
+      StartPanning(aEvent.mLocalFocusPoint);
+    }
   } else {
     // Otherwise, handle the fingers being lifted.
-    ReentrantMonitorAutoEnter lock(mMonitor);
-
-    // We can get into a situation where we are overscrolled at the end of a
-    // pinch if we go into overscroll with a two-finger pan, and then turn
-    // that into a pinch by increasing the span sufficiently. In such a case,
-    // there is no snap-back animation to get us out of overscroll, so we need
-    // to get out of it somehow.
-    // Moreover, in cases of scroll handoff, the overscroll can be on an APZC
-    // further up in the handoff chain rather than on the current APZC, so
-    // we need to clear overscroll along the entire handoff chain.
-    if (HasReadyTouchBlock()) {
-      GetCurrentTouchBlock()->GetOverscrollHandoffChain()->ClearOverscroll();
+    if (mZoomConstraints.mAllowZoom) {
+      ReentrantMonitorAutoEnter lock(mMonitor);
+
+      // We can get into a situation where we are overscrolled at the end of a
+      // pinch if we go into overscroll with a two-finger pan, and then turn
+      // that into a pinch by increasing the span sufficiently. In such a case,
+      // there is no snap-back animation to get us out of overscroll, so we need
+      // to get out of it somehow.
+      // Moreover, in cases of scroll handoff, the overscroll can be on an APZC
+      // further up in the handoff chain rather than on the current APZC, so
+      // we need to clear overscroll along the entire handoff chain.
+      if (HasReadyTouchBlock()) {
+        GetCurrentTouchBlock()->GetOverscrollHandoffChain()->ClearOverscroll();
+      } else {
+        ClearOverscroll();
+      }
+      // Along with clearing the overscroll, we also want to snap to the nearest
+      // snap point as appropriate.
+      ScrollSnap();
     } else {
-      ClearOverscroll();
+      // when zoom is not allowed
+      mX.EndTouch(aEvent.mTime);
+      mY.EndTouch(aEvent.mTime);
+      if (mState == PINCHING) {
+        // still pinching
+        if (HasReadyTouchBlock()) {
+          return HandleEndOfPan();
+        }
+      }
     }
-    // Along with clearing the overscroll, we also want to snap to the nearest
-    // snap point as appropriate.
-    ScrollSnap();
   }
-
+  return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::HandleEndOfPan()
+{
+  MOZ_ASSERT(GetCurrentTouchBlock());
+  GetCurrentTouchBlock()->GetOverscrollHandoffChain()->FlushRepaints();
+  ParentLayerPoint flingVelocity = GetVelocityVector();
+
+  // Clear our velocities; if DispatchFling() gives the fling to us,
+  // the fling velocity gets *added* to our existing velocity in
+  // AcceptFling().
+  mX.SetVelocity(0);
+  mY.SetVelocity(0);
+  // Clear our state so that we don't stay in the PANNING state
+  // if DispatchFling() gives the fling to somone else. However,
+  // don't send the state change notification until we've determined
+  // what our final state is to avoid notification churn.
+  StateChangeNotificationBlocker blocker(this);
+  SetState(NOTHING);
+
+  APZC_LOG("%p starting a fling animation if %f >= %f\n", this,
+      flingVelocity.Length().value, gfxPrefs::APZFlingMinVelocityThreshold());
+
+  if (flingVelocity.Length() < gfxPrefs::APZFlingMinVelocityThreshold()) {
+    // Relieve overscroll now if needed, since we will not transition to a fling
+    // animation and then an overscroll animation, and relieve it then.
+    GetCurrentTouchBlock()->GetOverscrollHandoffChain()->SnapBackOverscrolledApzc(this);
+    return nsEventStatus_eConsumeNoDefault;
+  }
+
+  // Make a local copy of the tree manager pointer and check that it's not
+  // null before calling DispatchFling(). This is necessary because Destroy(),
+  // which nulls out mTreeManager, could be called concurrently.
+  if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
+    FlingHandoffState handoffState{flingVelocity,
+                                  GetCurrentTouchBlock()->GetOverscrollHandoffChain(),
+                                  false /* not handoff */,
+                                  GetCurrentTouchBlock()->GetScrolledApzc()};
+    treeManagerLocal->DispatchFling(this, handoffState);
+  }
   return nsEventStatus_eConsumeNoDefault;
 }
 
 bool
 AsyncPanZoomController::ConvertToGecko(const ScreenIntPoint& aPoint, LayoutDevicePoint* aOut)
 {
   if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
     ScreenToScreenMatrix4x4 transformScreenToGecko =
@@ -2319,22 +2357,22 @@ void AsyncPanZoomController::HandlePanni
           mX.SetAxisLocked(false);
           SetState(PANNING);
         }
       }
     }
   }
 }
 
-nsEventStatus AsyncPanZoomController::StartPanning(const MultiTouchInput& aEvent) {
+nsEventStatus
+AsyncPanZoomController::StartPanning(const ParentLayerPoint& aStartPoint) {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
-  ParentLayerPoint point = GetFirstTouchPoint(aEvent);
-  float dx = mX.PanDistance(point.x);
-  float dy = mY.PanDistance(point.y);
+  float dx = mX.PanDistance(aStartPoint.x);
+  float dy = mY.PanDistance(aStartPoint.y);
 
   double angle = atan2(dy, dx); // range [-pi, pi]
   angle = fabs(angle); // range [0, pi]
 
   if (gfxPrefs::TouchActionEnabled()) {
     HandlePanningWithTouchAction(angle);
   } else {
     if (GetAxisLockMode() == FREE) {
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -452,16 +452,17 @@ protected:
    */
   nsEventStatus OnPanMayBegin(const PanGestureInput& aEvent);
   nsEventStatus OnPanCancelled(const PanGestureInput& aEvent);
   nsEventStatus OnPanBegin(const PanGestureInput& aEvent);
   nsEventStatus OnPan(const PanGestureInput& aEvent, bool aFingersOnTouchpad);
   nsEventStatus OnPanEnd(const PanGestureInput& aEvent);
   nsEventStatus OnPanMomentumStart(const PanGestureInput& aEvent);
   nsEventStatus OnPanMomentumEnd(const PanGestureInput& aEvent);
+  nsEventStatus HandleEndOfPan();
 
   /**
    * Helper methods for handling scroll wheel events.
    */
   nsEventStatus OnScrollWheel(const ScrollWheelInput& aEvent);
 
   ParentLayerPoint GetScrollWheelDelta(const ScrollWheelInput& aEvent) const;
 
@@ -571,17 +572,17 @@ protected:
    * Update the panning state and axis locks.
    */
   void HandlePanningUpdate(const ScreenPoint& aDelta);
 
   /**
    * Sets up anything needed for panning. This takes us out of the "TOUCHING"
    * state and starts actually panning us.
    */
-  nsEventStatus StartPanning(const MultiTouchInput& aStartPoint);
+  nsEventStatus StartPanning(const ParentLayerPoint& aStartPoint);
 
   /**
    * Wrapper for Axis::UpdateWithTouchAtDevicePoint(). Calls this function for
    * both axes and factors in the time delta from the last update.
    */
   void UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent);
 
   /**
--- a/gfx/layers/apz/src/GestureEventListener.h
+++ b/gfx/layers/apz/src/GestureEventListener.h
@@ -28,19 +28,16 @@ class AsyncPanZoomController;
  * determines whether or not they are part of a gesture.
  *
  * For example, seeing that two fingers are on the screen means that the user
  * wants to do a pinch gesture, so we don't forward the touches along to
  * AsyncPanZoomController since it will think that they are just trying to pan
  * the screen. Instead, we generate a PinchGestureInput and send that. If the
  * touch event is not part of a gesture, we just return nsEventStatus_eIgnore
  * and AsyncPanZoomController is expected to handle it.
- *
- * Android doesn't use this class because it has its own built-in gesture event
- * listeners that should generally be preferred.
  */
 class GestureEventListener final {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GestureEventListener)
 
   explicit GestureEventListener(AsyncPanZoomController* aAsyncPanZoomController);
 
   // --------------------------------------------------------------------------
--- a/gfx/layers/apz/test/gtest/InputUtils.h
+++ b/gfx/layers/apz/test/gtest/InputUtils.h
@@ -133,17 +133,17 @@ PinchWithPinchInput(const RefPtr<InputRe
       nullptr);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[1] = actualStatus;
   }
   actualStatus = aTarget->ReceiveInputEvent(
       CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
                               // note: negative values here tell APZC
                               //       not to turn the pinch into a pan
-                              aFocus, -1.0, -1.0),
+                              ScreenIntPoint(-1, -1), 10.0 * aScale, 10.0 * aScale),
       nullptr);
   if (aOutEventStatuses) {
     (*aOutEventStatuses)[2] = actualStatus;
   }
 }
 
 template<class InputReceiver>
 void
--- a/gfx/vr/VRDisplayHost.cpp
+++ b/gfx/vr/VRDisplayHost.cpp
@@ -21,20 +21,16 @@ using namespace mozilla::layers;
 
 VRDisplayHost::VRDisplayHost(VRDeviceType aType)
   : mInputFrameID(0)
 {
   MOZ_COUNT_CTOR(VRDisplayHost);
   mDisplayInfo.mType = aType;
   mDisplayInfo.mDisplayID = VRSystemManager::AllocateDisplayID();
   mDisplayInfo.mIsPresenting = false;
-
-  for (int i = 0; i < kMaxLatencyFrames; i++) {
-    mLastSensorState[i].Clear();
-  }
 }
 
 VRDisplayHost::~VRDisplayHost()
 {
   MOZ_COUNT_DTOR(VRDisplayHost);
 }
 
 void
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -185,16 +185,20 @@ struct VRDisplayInfo
   }
 
   bool operator!=(const VRDisplayInfo& other) const {
     return !(*this == other);
   }
 };
 
 struct VRHMDSensorState {
+  VRHMDSensorState()
+  {
+    Clear();
+  }
   double timestamp;
   int32_t inputFrameID;
   VRDisplayCapabilityFlags flags;
   float orientation[4];
   float position[3];
   float angularVelocity[3];
   float angularAcceleration[3];
   float linearVelocity[3];
--- a/gfx/vr/gfxVROSVR.cpp
+++ b/gfx/vr/gfxVROSVR.cpp
@@ -287,17 +287,16 @@ VRDisplayOSVR::GetSensorState()
 {
 
   //update client context before anything
   //this usually goes into app's mainloop
   osvr_ClientUpdate(*m_ctx);
 
   VRHMDSensorState result;
   OSVR_TimeValue timestamp;
-  result.Clear();
 
   OSVR_OrientationState orientation;
 
   OSVR_ReturnCode ret =
     osvr_GetOrientationState(*m_iface, &timestamp, &orientation);
 
   result.timestamp = timestamp.seconds;
 
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -450,17 +450,16 @@ VRDisplayOculus::GetSensorState()
   result.position[1] -= mEyeHeight;
   return result;
 }
 
 VRHMDSensorState
 VRDisplayOculus::GetSensorState(double timeOffset)
 {
   VRHMDSensorState result;
-  result.Clear();
 
   ovrTrackingState state = ovr_GetTrackingState(mSession, timeOffset, true);
   ovrPoseStatef& pose(state.HeadPose);
 
   result.timestamp = pose.TimeInSeconds;
 
   if (state.StatusFlags & ovrStatus_OrientationTracked) {
     result.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -205,17 +205,16 @@ VRDisplayOpenVR::GetSensorState(double t
 {
   PollEvents();
 
   ::vr::TrackedDevicePose_t poses[::vr::k_unMaxTrackedDeviceCount];
   // Note: We *must* call WaitGetPoses in order for any rendering to happen at all
   mVRCompositor->WaitGetPoses(poses, ::vr::k_unMaxTrackedDeviceCount, nullptr, 0);
 
   VRHMDSensorState result;
-  result.Clear();
 
   ::vr::Compositor_FrameTiming timing;
   timing.m_nSize = sizeof(::vr::Compositor_FrameTiming);
   if (mVRCompositor->GetFrameTiming(&timing)) {
     result.timestamp = timing.m_flSystemTimeInSeconds;
   } else {
     // This should not happen, but log it just in case
     NS_WARNING("OpenVR - IVRCompositor::GetFrameTiming failed");
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -85,18 +85,16 @@ VRDisplayPuppet::VRDisplayPuppet()
   mDisplayInfo.mSittingToStandingTransform._32 = 0.0f;
   mDisplayInfo.mSittingToStandingTransform._33 = 1.0f;
   mDisplayInfo.mSittingToStandingTransform._34 = 0.0f;
 
   mDisplayInfo.mSittingToStandingTransform._41 = 0.0f;
   mDisplayInfo.mSittingToStandingTransform._42 = 0.75f;
   mDisplayInfo.mSittingToStandingTransform._43 = 0.0f;
 
-  mSensorState.Clear();
-
   gfx::Quaternion rot;
 
   mSensorState.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
   mSensorState.orientation[0] = rot.x;
   mSensorState.orientation[1] = rot.y;
   mSensorState.orientation[2] = rot.z;
   mSensorState.orientation[3] = rot.w;
   mSensorState.angularVelocity[0] = 0.0f;
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -589,16 +589,19 @@ platform = notwin
 description =
 platform = notwin
 [PDocAccessible::DOMNodeID]
 description =
 platform = notwin
 [PDocAccessible::GetWindowedPluginIAccessible]
 description =
 platform = win
+[PDocAccessible::SyncTextChangeEvent]
+description =
+platform = win
 
 # CPOWs
 [PBrowser::RpcMessage]
 description =
 [PContent::RpcMessage]
 description =
 [PJavaScript::PreventExtensions]
 description =
--- a/ipc/mscom/AsyncInvoker.h
+++ b/ipc/mscom/AsyncInvoker.h
@@ -264,17 +264,17 @@ public:
    *                 If this object is not a proxy to the synchronous variant
    *                 of AsyncInterface, then it will be invoked synchronously
    *                 instead (because it is an in-process virtual method call).
    * @param aIsProxy An optional hint as to whether or not aSyncObj is a proxy.
    *                 If not specified, AsyncInvoker will automatically detect
    *                 whether aSyncObj is a proxy, however there may be a
    *                 performance penalty associated with that.
    */
-  explicit AsyncInvoker(SyncInterface* aSyncObj, Maybe<bool> aIsProxy = Nothing())
+  explicit AsyncInvoker(SyncInterface* aSyncObj, const Maybe<bool>& aIsProxy = Nothing())
     : mSyncObj(ResolveIsProxy(aSyncObj, aIsProxy) ? nullptr : aSyncObj)
   {
     MOZ_ASSERT(aSyncObj);
 
     if (mSyncObj) {
       return;
     }
 
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -56,17 +56,23 @@ class MOZ_STACK_CLASS BytecodeCompiler
   private:
     JSScript* compileScript(HandleObject environment, SharedContext* sc);
     bool checkLength();
     bool createScriptSource(const Maybe<uint32_t>& parameterListEnd);
     bool enqueueOffThreadSourceCompression();
     bool canLazilyParse();
     bool createParser();
     bool createSourceAndParser(const Maybe<uint32_t>& parameterListEnd = Nothing());
-    bool createScript(uint32_t preludeStart = 0);
+
+    // If toString{Start,End} are not explicitly passed, assume the script's
+    // offsets in the source used to parse it are the same as what should be
+    // used to compute its Function.prototype.toString() value.
+    bool createScript();
+    bool createScript(uint32_t toStringStart, uint32_t toStringEnd);
+
     bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext);
     bool handleParseFailure(const Directives& newDirectives);
     bool deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment);
 
     AutoKeepAtoms keepAtoms;
 
     JSContext* cx;
     LifoAlloc& alloc;
@@ -287,21 +293,27 @@ BytecodeCompiler::createParser()
 bool
 BytecodeCompiler::createSourceAndParser(const Maybe<uint32_t>& parameterListEnd /* = Nothing() */)
 {
     return createScriptSource(parameterListEnd) &&
            createParser();
 }
 
 bool
-BytecodeCompiler::createScript(uint32_t preludeStart /* = 0 */)
+BytecodeCompiler::createScript()
+{
+    return createScript(0, sourceBuffer.length());
+}
+
+bool
+BytecodeCompiler::createScript(uint32_t toStringStart, uint32_t toStringEnd)
 {
     script = JSScript::Create(cx, options,
                               sourceObject, /* sourceStart = */ 0, sourceBuffer.length(),
-                              preludeStart);
+                              toStringStart, toStringEnd);
     return script != nullptr;
 }
 
 bool
 BytecodeCompiler::emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext)
 {
     BytecodeEmitter::EmitterMode emitterMode =
         options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
@@ -503,17 +515,17 @@ BytecodeCompiler::compileStandaloneFunct
                                         asyncKind, directives, &newDirectives);
         if (!fn && !handleParseFailure(newDirectives))
             return false;
     } while (!fn);
 
     if (fn->pn_funbox->function()->isInterpreted()) {
         MOZ_ASSERT(fun == fn->pn_funbox->function());
 
-        if (!createScript(fn->pn_funbox->preludeStart))
+        if (!createScript(fn->pn_funbox->toStringStart, fn->pn_funbox->toStringEnd))
             return false;
 
         Maybe<BytecodeEmitter> emitter;
         if (!emplaceEmitter(emitter, fn->pn_funbox))
             return false;
         if (!emitter->emitFunctionScript(fn->pn_body))
             return false;
     } else {
@@ -724,17 +736,17 @@ frontend::CompileLazyFunction(JSContext*
     if (!pn)
         return false;
 
     RootedScriptSource sourceObject(cx, lazy->sourceObject());
     MOZ_ASSERT(sourceObject);
 
     Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject,
                                                   lazy->begin(), lazy->end(),
-                                                  lazy->preludeStart()));
+                                                  lazy->toStringStart(), lazy->toStringEnd()));
     if (!script)
         return false;
 
     if (lazy->isLikelyConstructorWrapper())
         script->setLikelyConstructorWrapper();
     if (lazy->hasBeenCloned())
         script->setHasBeenCloned();
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -7944,17 +7944,18 @@ BytecodeEmitter::emitFunction(ParseNode*
             MOZ_ASSERT(parent->getVersion() == parser->options().version);
             MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors());
             const TransitiveCompileOptions& transitiveOptions = parser->options();
             CompileOptions options(cx, transitiveOptions);
 
             Rooted<JSObject*> sourceObject(cx, script->sourceObject());
             Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject,
                                                           funbox->bufStart, funbox->bufEnd,
-                                                          funbox->preludeStart));
+                                                          funbox->toStringStart,
+                                                          funbox->toStringEnd));
             if (!script)
                 return false;
 
             BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr,
                                  pn->pn_pos, emitterMode);
             if (!bce2.init())
                 return false;
 
@@ -10464,16 +10465,23 @@ BytecodeEmitter::emitClass(ParseNode* pn
     if (constructor) {
         if (!emitFunction(constructor, !!heritageExpression))
             return false;
         if (constructor->pn_funbox->needsHomeObject()) {
             if (!emit2(JSOP_INITHOMEOBJECT, 0))
                 return false;
         }
     } else {
+        // In the case of default class constructors, emit the start and end
+        // offsets in the source buffer as source notes so that when we
+        // actually make the constructor during execution, we can give it the
+        // correct toString output.
+        if (!newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(pn->pn_pos.begin), ptrdiff_t(pn->pn_pos.end)))
+            return false;
+
         JSAtom *name = names ? names->innerBinding()->pn_atom : cx->names().empty;
         if (heritageExpression) {
             if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR))
                 return false;
         } else {
             if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR))
                 return false;
         }
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -318,18 +318,20 @@ class FullParseHandler
     ParseNode* newObjectLiteral(uint32_t begin) {
         ParseNode* literal = new_<ListNode>(PNK_OBJECT, TokenPos(begin, begin + 1));
         // Later in this stack: remove dependency on this opcode.
         if (literal)
             literal->setOp(JSOP_NEWINIT);
         return literal;
     }
 
-    ParseNode* newClass(ParseNode* name, ParseNode* heritage, ParseNode* methodBlock) {
-        return new_<ClassNode>(name, heritage, methodBlock);
+    ParseNode* newClass(ParseNode* name, ParseNode* heritage, ParseNode* methodBlock,
+                        const TokenPos& pos)
+    {
+        return new_<ClassNode>(name, heritage, methodBlock, pos);
     }
     ParseNode* newClassMethodList(uint32_t begin) {
         return new_<ListNode>(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1));
     }
     ParseNode* newClassNames(ParseNode* outer, ParseNode* inner, const TokenPos& pos) {
         return new_<ClassNames>(outer, inner, pos);
     }
     ParseNode* newNewTarget(ParseNode* newHolder, ParseNode* targetHolder) {
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -1306,18 +1306,19 @@ struct ClassNames : public BinaryNode {
         return pn_u.binary.left;
     }
     ParseNode* innerBinding() const {
         return pn_u.binary.right;
     }
 };
 
 struct ClassNode : public TernaryNode {
-    ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock)
-      : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock)
+    ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock,
+              const TokenPos& pos)
+      : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock, pos)
     {
         MOZ_ASSERT_IF(names, names->is<ClassNames>());
         MOZ_ASSERT(methodsOrBlock->is<LexicalScopeNode>() ||
                    methodsOrBlock->isKind(PNK_CLASSMETHODLIST));
     }
 
     static bool test(const ParseNode& node) {
         bool match = node.isKind(PNK_CLASS);
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -137,17 +137,18 @@ DeclarationKindString(DeclarationKind ki
 
 static bool
 StatementKindIsBraced(StatementKind kind)
 {
     return kind == StatementKind::Block ||
            kind == StatementKind::Switch ||
            kind == StatementKind::Try ||
            kind == StatementKind::Catch ||
-           kind == StatementKind::Finally;
+           kind == StatementKind::Finally ||
+           kind == StatementKind::Class;
 }
 
 void
 ParseContext::Scope::dump(ParseContext* pc)
 {
     JSContext* cx = pc->sc()->context;
 
     fprintf(stdout, "ParseScope %p", this);
@@ -453,31 +454,32 @@ UsedNameTracker::rewind(RewindToken toke
     scriptCounter_ = token.scriptId;
     scopeCounter_ = token.scopeId;
 
     for (UsedNameMap::Range r = map_.all(); !r.empty(); r.popFront())
         r.front().value().resetToScope(token.scriptId, token.scopeId);
 }
 
 FunctionBox::FunctionBox(JSContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead,
-                         JSFunction* fun, uint32_t preludeStart,
+                         JSFunction* fun, uint32_t toStringStart,
                          Directives directives, bool extraWarnings,
                          GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
   : ObjectBox(fun, traceListHead),
     SharedContext(cx, Kind::ObjectBox, directives, extraWarnings),
     enclosingScope_(nullptr),
     namedLambdaBindings_(nullptr),
     functionScopeBindings_(nullptr),
     extraVarScopeBindings_(nullptr),
     functionNode(nullptr),
     bufStart(0),
     bufEnd(0),
     startLine(1),
     startColumn(0),
-    preludeStart(preludeStart),
+    toStringStart(toStringStart),
+    toStringEnd(0),
     length(0),
     generatorKindBits_(GeneratorKindAsBits(generatorKind)),
     asyncKindBits_(AsyncKindAsBits(asyncKind)),
     isGenexpLambda(false),
     hasDestructuringArgs(false),
     hasParameterExprs(false),
     hasDirectEvalInParameterExpr(false),
     hasDuplicateParameters(false),
@@ -538,20 +540,26 @@ FunctionBox::initWithEnclosingParseConte
         allowSuperProperty_ = sc->allowSuperProperty();
         allowSuperCall_ = sc->allowSuperCall();
         needsThisTDZChecks_ = sc->needsThisTDZChecks();
         thisBinding_ = sc->thisBinding();
     } else {
         allowNewTarget_ = true;
         allowSuperProperty_ = fun->allowSuperProperty();
 
-        if (kind == DerivedClassConstructor) {
-            setDerivedClassConstructor();
-            allowSuperCall_ = true;
-            needsThisTDZChecks_ = true;
+        if (kind == ClassConstructor || kind == DerivedClassConstructor) {
+            auto stmt = enclosing->findInnermostStatement<ParseContext::ClassStatement>();
+            MOZ_ASSERT(stmt);
+            stmt->setConstructorBox(this);
+
+            if (kind == DerivedClassConstructor) {
+                setDerivedClassConstructor();
+                allowSuperCall_ = true;
+                needsThisTDZChecks_ = true;
+            }
         }
 
         if (isGenexpLambda)
             thisBinding_ = sc->thisBinding();
         else
             thisBinding_ = ThisBinding::Function;
     }
 
@@ -562,16 +570,26 @@ FunctionBox::initWithEnclosingParseConte
             return stmt->kind() == StatementKind::With;
         };
 
         inWith_ = enclosing->findInnermostStatement(isWith);
     }
 }
 
 void
+FunctionBox::resetForAbortedSyntaxParse(ParseContext* enclosing, FunctionSyntaxKind kind)
+{
+    if (kind == ClassConstructor || kind == DerivedClassConstructor) {
+        auto stmt = enclosing->findInnermostStatement<ParseContext::ClassStatement>();
+        MOZ_ASSERT(stmt);
+        stmt->clearConstructorBoxForAbortedSyntaxParse(this);
+    }
+}
+
+void
 FunctionBox::initWithEnclosingScope(Scope* enclosingScope)
 {
     if (!function()->isArrow()) {
         allowNewTarget_ = true;
         allowSuperProperty_ = function()->allowSuperProperty();
 
         if (isDerivedClassConstructor()) {
             setDerivedClassConstructor();
@@ -887,33 +905,33 @@ ParserBase::newObjectBox(JSObject* obj)
 
     traceListHead = objbox;
 
     return objbox;
 }
 
 template <typename ParseHandler>
 FunctionBox*
-Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, uint32_t preludeStart,
+Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, uint32_t toStringStart,
                                      Directives inheritedDirectives,
                                      GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
                                      bool tryAnnexB)
 {
     MOZ_ASSERT(fun);
     MOZ_ASSERT_IF(tryAnnexB, !pc->sc()->strict());
 
     /*
      * We use JSContext.tempLifoAlloc to allocate parsed objects and place them
      * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
      * arenas containing the entries must be alive until we are done with
      * scanning, parsing and code generation for the whole script or top-level
      * function.
      */
     FunctionBox* funbox =
-        alloc.new_<FunctionBox>(context, alloc, traceListHead, fun, preludeStart,
+        alloc.new_<FunctionBox>(context, alloc, traceListHead, fun, toStringStart,
                                 inheritedDirectives, options().extraWarningsOption,
                                 generatorKind, asyncKind);
     if (!funbox) {
         ReportOutOfMemory(context);
         return nullptr;
     }
 
     traceListHead = funbox;
@@ -2428,17 +2446,17 @@ Parser<SyntaxParseHandler>::finishFuncti
         return false;
     }
 
     FunctionBox* funbox = pc->functionBox();
     RootedFunction fun(context, funbox->function());
     LazyScript* lazy = LazyScript::Create(context, fun, pc->closedOverBindingsForLazy(),
                                           pc->innerFunctionsForLazy, versionNumber(),
                                           funbox->bufStart, funbox->bufEnd,
-                                          funbox->preludeStart,
+                                          funbox->toStringStart,
                                           funbox->startLine, funbox->startColumn);
     if (!lazy)
         return false;
 
     // Flags that need to be copied into the JSScript when we do the full
     // parse.
     if (pc->sc()->strict())
         lazy->setStrict();
@@ -2518,17 +2536,17 @@ Parser<FullParseHandler>::standaloneFunc
     if (!fn)
         return null();
 
     ParseNode* argsbody = handler.newList(PNK_PARAMSBODY, pos());
     if (!argsbody)
         return null();
     fn->pn_body = argsbody;
 
-    FunctionBox* funbox = newFunctionBox(fn, fun, /* preludeStart = */ 0, inheritedDirectives,
+    FunctionBox* funbox = newFunctionBox(fn, fun, /* toStringStart = */ 0, inheritedDirectives,
                                          generatorKind, asyncKind, /* tryAnnexB = */ false);
     if (!funbox)
         return null();
     funbox->initStandaloneFunction(enclosingScope);
 
     ParseContext funpc(this, funbox, newDirectives);
     if (!funpc.init())
         return null();
@@ -3180,27 +3198,27 @@ Parser<ParseHandler>::functionArguments(
         return false;
     }
 
     return true;
 }
 
 template <>
 bool
-Parser<FullParseHandler>::skipLazyInnerFunction(ParseNode* pn, uint32_t preludeStart,
+Parser<FullParseHandler>::skipLazyInnerFunction(ParseNode* pn, uint32_t toStringStart,
                                                 FunctionSyntaxKind kind, bool tryAnnexB)
 {
     // When a lazily-parsed function is called, we only fully parse (and emit)
     // that function, not any of its nested children. The initial syntax-only
     // parse recorded the free variables of nested functions and their extents,
     // so we can skip over them after accounting for their free variables.
 
     RootedFunction fun(context, handler.nextLazyInnerFunction());
     MOZ_ASSERT(!fun->isLegacyGenerator());
-    FunctionBox* funbox = newFunctionBox(pn, fun, preludeStart, Directives(/* strict = */ false),
+    FunctionBox* funbox = newFunctionBox(pn, fun, toStringStart, Directives(/* strict = */ false),
                                          fun->generatorKind(), fun->asyncKind(), tryAnnexB);
     if (!funbox)
         return false;
 
     LazyScript* lazy = fun->lazyScript();
     if (lazy->needsHomeObject())
         funbox->setNeedsHomeObject();
     if (lazy->isExprBody())
@@ -3227,17 +3245,17 @@ Parser<FullParseHandler>::skipLazyInnerF
     }
 #endif
 
     return true;
 }
 
 template <>
 bool
-Parser<SyntaxParseHandler>::skipLazyInnerFunction(Node pn, uint32_t preludeStart,
+Parser<SyntaxParseHandler>::skipLazyInnerFunction(Node pn, uint32_t toStringStart,
                                                   FunctionSyntaxKind kind, bool tryAnnexB)
 {
     MOZ_CRASH("Cannot skip lazy inner functions when syntax parsing");
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::addExprAndGetNextTemplStrToken(YieldHandling yieldHandling, Node nodeList,
@@ -3304,30 +3322,30 @@ Parser<ParseHandler>::templateLiteral(Yi
 
         handler.addList(nodeList, pn);
     } while (tt == TOK_TEMPLATE_HEAD);
     return nodeList;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
-Parser<ParseHandler>::functionDefinition(Node pn, uint32_t preludeStart,
+Parser<ParseHandler>::functionDefinition(Node pn, uint32_t toStringStart,
                                          InHandling inHandling,
                                          YieldHandling yieldHandling, HandleAtom funName,
                                          FunctionSyntaxKind kind,
                                          GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
                                          bool tryAnnexB /* = false */)
 {
     MOZ_ASSERT_IF(kind == Statement, funName);
 
     // When fully parsing a LazyScript, we do not fully reparse its inner
     // functions, which are also lazy. Instead, their free variables and
     // source extents are recorded and may be skipped.
     if (handler.canSkipLazyInnerFunctions()) {
-        if (!skipLazyInnerFunction(pn, preludeStart, kind, tryAnnexB))
+        if (!skipLazyInnerFunction(pn, toStringStart, kind, tryAnnexB))
             return null();
         return pn;
     }
 
     RootedObject proto(context);
     if (generatorKind == StarGenerator || asyncKind == AsyncFunction) {
         // If we are off thread, the generator meta-objects have
         // already been created by js::StartOffThreadParseTask, so cx will not
@@ -3350,17 +3368,17 @@ Parser<ParseHandler>::functionDefinition
 
     TokenStream::Position start(keepAtoms);
     tokenStream.tell(&start);
 
     // Parse the inner function. The following is a loop as we may attempt to
     // reparse a function due to failed syntax parsing and encountering new
     // "use foo" directives.
     while (true) {
-        if (trySyntaxParseInnerFunction(pn, fun, preludeStart, inHandling, yieldHandling, kind,
+        if (trySyntaxParseInnerFunction(pn, fun, toStringStart, inHandling, yieldHandling, kind,
                                         generatorKind, asyncKind, tryAnnexB, directives,
                                         &newDirectives))
         {
             break;
         }
 
         // Return on error.
         if (tokenStream.hadError() || directives == newDirectives)
@@ -3379,17 +3397,17 @@ Parser<ParseHandler>::functionDefinition
     }
 
     return pn;
 }
 
 template <>
 bool
 Parser<FullParseHandler>::trySyntaxParseInnerFunction(ParseNode* pn, HandleFunction fun,
-                                                      uint32_t preludeStart,
+                                                      uint32_t toStringStart,
                                                       InHandling inHandling,
                                                       YieldHandling yieldHandling,
                                                       FunctionSyntaxKind kind,
                                                       GeneratorKind generatorKind,
                                                       FunctionAsyncKind asyncKind,
                                                       bool tryAnnexB,
                                                       Directives inheritedDirectives,
                                                       Directives* newDirectives)
@@ -3413,32 +3431,33 @@ Parser<FullParseHandler>::trySyntaxParse
         TokenStream::Position position(keepAtoms);
         tokenStream.tell(&position);
         if (!parser->tokenStream.seek(position, tokenStream))
             return false;
 
         // Make a FunctionBox before we enter the syntax parser, because |pn|
         // still expects a FunctionBox to be attached to it during BCE, and
         // the syntax parser cannot attach one to it.
-        FunctionBox* funbox = newFunctionBox(pn, fun, preludeStart, inheritedDirectives,
+        FunctionBox* funbox = newFunctionBox(pn, fun, toStringStart, inheritedDirectives,
                                              generatorKind, asyncKind, tryAnnexB);
         if (!funbox)
             return false;
         funbox->initWithEnclosingParseContext(pc, kind);
 
-        if (!parser->innerFunction(SyntaxParseHandler::NodeGeneric, pc, funbox, preludeStart,
+        if (!parser->innerFunction(SyntaxParseHandler::NodeGeneric, pc, funbox, toStringStart,
                                    inHandling, yieldHandling, kind,
                                    inheritedDirectives, newDirectives))
         {
             if (parser->hadAbortedSyntaxParse()) {
                 // Try again with a full parse. UsedNameTracker needs to be
                 // rewound to just before we tried the syntax parse for
                 // correctness.
                 parser->clearAbortedSyntaxParse();
                 usedNames.rewind(token);
+                funbox->resetForAbortedSyntaxParse(pc, kind);
                 MOZ_ASSERT_IF(!parser->context->helperThread(),
                               !parser->context->isExceptionPending());
                 break;
             }
             return false;
         }
 
         // Advance this parser over tokens processed by the syntax parser.
@@ -3447,42 +3466,42 @@ Parser<FullParseHandler>::trySyntaxParse
             return false;
 
         // Update the end position of the parse node.
         pn->pn_pos.end = tokenStream.currentToken().pos.end;
         return true;
     } while (false);
 
     // We failed to do a syntax parse above, so do the full parse.
-    return innerFunction(pn, pc, fun, preludeStart, inHandling, yieldHandling, kind,
+    return innerFunction(pn, pc, fun, toStringStart, inHandling, yieldHandling, kind,
                          generatorKind, asyncKind, tryAnnexB, inheritedDirectives, newDirectives);
 }
 
 template <>
 bool
 Parser<SyntaxParseHandler>::trySyntaxParseInnerFunction(Node pn, HandleFunction fun,
-                                                        uint32_t preludeStart,
+                                                        uint32_t toStringStart,
                                                         InHandling inHandling,
                                                         YieldHandling yieldHandling,
                                                         FunctionSyntaxKind kind,
                                                         GeneratorKind generatorKind,
                                                         FunctionAsyncKind asyncKind,
                                                         bool tryAnnexB,
                                                         Directives inheritedDirectives,
                                                         Directives* newDirectives)
 {
     // This is already a syntax parser, so just parse the inner function.
-    return innerFunction(pn, pc, fun, preludeStart, inHandling, yieldHandling, kind,
+    return innerFunction(pn, pc, fun, toStringStart, inHandling, yieldHandling, kind,
                          generatorKind, asyncKind, tryAnnexB, inheritedDirectives, newDirectives);
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox,
-                                    uint32_t preludeStart,
+                                    uint32_t toStringStart,
                                     InHandling inHandling, YieldHandling yieldHandling,
                                     FunctionSyntaxKind kind, Directives inheritedDirectives,
                                     Directives* newDirectives)
 {
     // Note that it is possible for outerpc != this->pc, as we may be
     // attempting to syntax parse an inner function from an outer full
     // parser. In that case, outerpc is a ParseContext from the full parser
     // instead of the current top of the stack of the syntax parser.
@@ -3496,35 +3515,35 @@ Parser<ParseHandler>::innerFunction(Node
         return false;
 
     return leaveInnerFunction(outerpc);
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun,
-                                    uint32_t preludeStart,
+                                    uint32_t toStringStart,
                                     InHandling inHandling, YieldHandling yieldHandling,
                                     FunctionSyntaxKind kind,
                                     GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
                                     bool tryAnnexB,
                                     Directives inheritedDirectives, Directives* newDirectives)
 {
     // Note that it is possible for outerpc != this->pc, as we may be
     // attempting to syntax parse an inner function from an outer full
     // parser. In that case, outerpc is a ParseContext from the full parser
     // instead of the current top of the stack of the syntax parser.
 
-    FunctionBox* funbox = newFunctionBox(pn, fun, preludeStart, inheritedDirectives,
+    FunctionBox* funbox = newFunctionBox(pn, fun, toStringStart, inheritedDirectives,
                                          generatorKind, asyncKind, tryAnnexB);
     if (!funbox)
         return false;
     funbox->initWithEnclosingParseContext(outerpc, kind);
 
-    return innerFunction(pn, outerpc, funbox, preludeStart, inHandling, yieldHandling, kind,
+    return innerFunction(pn, outerpc, funbox, toStringStart, inHandling, yieldHandling, kind,
                          inheritedDirectives, newDirectives);
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::appendToCallSiteObj(Node callSiteObj)
 {
     Node cookedNode = noSubstitutionTaggedTemplate();
@@ -3550,17 +3569,17 @@ Parser<FullParseHandler>::standaloneLazy
 {
     MOZ_ASSERT(checkOptionsCalled);
 
     Node pn = handler.newFunctionStatement(pos());
     if (!pn)
         return null();
 
     Directives directives(strict);
-    FunctionBox* funbox = newFunctionBox(pn, fun, /* preludeStart = */ 0, directives,
+    FunctionBox* funbox = newFunctionBox(pn, fun, /* toStringStart = */ 0, directives,
                                          generatorKind, asyncKind, /* tryAnnexB = */ false);
     if (!funbox)
         return null();
     funbox->initFromLazyFunction();
 
     Directives newDirectives = directives;
     ParseContext funpc(this, funbox, &newDirectives);
     if (!funpc.init())
@@ -3720,24 +3739,24 @@ Parser<ParseHandler>::functionFormalPara
             return false;
         }
     }
 
     if (bodyType == StatementListBody) {
         MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
                                          reportMissingClosing(JSMSG_CURLY_AFTER_BODY,
                                                               JSMSG_CURLY_OPENED, openedPos));
-        funbox->bufEnd = pos().end;
+        funbox->setEnd(pos().end);
     } else {
 #if !JS_HAS_EXPR_CLOSURES
         MOZ_ASSERT(kind == Arrow);
 #endif
         if (tokenStream.hadError())
             return false;
-        funbox->bufEnd = pos().end;
+        funbox->setEnd(pos().end);
         if (kind == Statement && !matchOrInsertSemicolonAfterExpression())
             return false;
     }
 
     if (IsMethodDefinitionKind(kind) && pc->superScopeNeedsHomeObject())
         funbox->setNeedsHomeObject();
 
     if (!finishFunction(isStandaloneFunction))
@@ -3747,17 +3766,17 @@ Parser<ParseHandler>::functionFormalPara
     handler.setEndPosition(pn, pos().end);
     handler.setFunctionBody(pn, body);
 
     return true;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
-Parser<ParseHandler>::functionStmt(uint32_t preludeStart, YieldHandling yieldHandling,
+Parser<ParseHandler>::functionStmt(uint32_t toStringStart, YieldHandling yieldHandling,
                                    DefaultHandling defaultHandling, FunctionAsyncKind asyncKind)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
 
     // In sloppy mode, Annex B.3.2 allows labelled function declarations.
     // Otherwise it's a parse error.
     ParseContext::Statement* declaredInStmt = pc->innermostStatement();
     if (declaredInStmt && declaredInStmt->kind() == StatementKind::Label) {
@@ -3833,23 +3852,23 @@ Parser<ParseHandler>::functionStmt(uint3
             return null();
     }
 
     Node pn = handler.newFunctionStatement(pos());
     if (!pn)
         return null();
 
     YieldHandling newYieldHandling = GetYieldHandling(generatorKind);
-    return functionDefinition(pn, preludeStart, InAllowed, newYieldHandling, name, Statement,
+    return functionDefinition(pn, toStringStart, InAllowed, newYieldHandling, name, Statement,
                               generatorKind, asyncKind, tryAnnexB);
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
-Parser<ParseHandler>::functionExpr(uint32_t preludeStart, InvokedPrediction invoked,
+Parser<ParseHandler>::functionExpr(uint32_t toStringStart, InvokedPrediction invoked,
                                    FunctionAsyncKind asyncKind)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
 
     AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, asyncKind == AsyncFunction);
     GeneratorKind generatorKind = NotGenerator;
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
@@ -3880,17 +3899,17 @@ Parser<ParseHandler>::functionExpr(uint3
 
     Node pn = handler.newFunctionExpression(pos());
     if (!pn)
         return null();
 
     if (invoked)
         pn = handler.setLikelyIIFE(pn);
 
-    return functionDefinition(pn, preludeStart, InAllowed, yieldHandling, name, Expression,
+    return functionDefinition(pn, toStringStart, InAllowed, yieldHandling, name, Expression,
                               generatorKind, asyncKind);
 }
 
 /*
  * Return true if this node, known to be an unparenthesized string literal,
  * could be the string of a directive in a Directive Prologue. Directive
  * strings never contain escape sequences or line continuations.
  * isEscapeFreeStringLiteral, below, checks whether the node itself could be
@@ -5408,25 +5427,25 @@ Parser<ParseHandler>::exportVariableStat
     if (!processExport(node))
         return null();
 
     return node;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
-Parser<ParseHandler>::exportFunctionDeclaration(uint32_t begin, uint32_t preludeStart,
+Parser<ParseHandler>::exportFunctionDeclaration(uint32_t begin, uint32_t toStringStart,
                                                 FunctionAsyncKind asyncKind /* = SyncFunction */)
 {
     if (!abortIfSyntaxParser())
         return null();
 
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
 
-    Node kid = functionStmt(preludeStart, YieldIsKeyword, NameRequired, asyncKind);
+    Node kid = functionStmt(toStringStart, YieldIsKeyword, NameRequired, asyncKind);
     if (!kid)
         return null();
 
     if (!checkExportedNameForFunction(kid))
         return null();
 
     Node node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end));
     if (!node)
@@ -5488,26 +5507,26 @@ Parser<ParseHandler>::exportLexicalDecla
     if (!processExport(node))
         return null();
 
     return node;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
-Parser<ParseHandler>::exportDefaultFunctionDeclaration(uint32_t begin, uint32_t preludeStart,
+Parser<ParseHandler>::exportDefaultFunctionDeclaration(uint32_t begin, uint32_t toStringStart,
                                                        FunctionAsyncKind asyncKind
                                                        /* = SyncFunction */)
 {
     if (!abortIfSyntaxParser())
         return null();
 
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
 
-    Node kid = functionStmt(preludeStart, YieldIsKeyword, AllowDefaultName, asyncKind);
+    Node kid = functionStmt(toStringStart, YieldIsKeyword, AllowDefaultName, asyncKind);
     if (!kid)
         return null();
 
     Node node = handler.newExportDefaultDeclaration(kid, null(), TokenPos(begin, pos().end));
     if (!node)
         return null();
 
     if (!processExport(node))
@@ -5590,19 +5609,19 @@ Parser<ParseHandler>::exportDefault(uint
         return exportDefaultFunctionDeclaration(begin, pos().begin);
 
       case TOK_ASYNC: {
         TokenKind nextSameLine = TOK_EOF;
         if (!tokenStream.peekTokenSameLine(&nextSameLine))
             return null();
 
         if (nextSameLine == TOK_FUNCTION) {
-            uint32_t preludeStart = pos().begin;
+            uint32_t toStringStart = pos().begin;
             tokenStream.consumeKnownToken(TOK_FUNCTION);
-            return exportDefaultFunctionDeclaration(begin, preludeStart, AsyncFunction);
+            return exportDefaultFunctionDeclaration(begin, toStringStart, AsyncFunction);
         }
 
         tokenStream.ungetToken();
         return exportDefaultAssignExpr(begin);
       }
 
       case TOK_CLASS:
         return exportDefaultClassDeclaration(begin);
@@ -5646,19 +5665,19 @@ Parser<ParseHandler>::exportDeclaration(
         return exportFunctionDeclaration(begin, pos().begin);
 
       case TOK_ASYNC: {
         TokenKind nextSameLine = TOK_EOF;
         if (!tokenStream.peekTokenSameLine(&nextSameLine))
             return null();
 
         if (nextSameLine == TOK_FUNCTION) {
-            uint32_t preludeStart = pos().begin;
+            uint32_t toStringStart = pos().begin;
             tokenStream.consumeKnownToken(TOK_FUNCTION);
-            return exportFunctionDeclaration(begin, preludeStart, AsyncFunction);
+            return exportFunctionDeclaration(begin, toStringStart, AsyncFunction);
         }
 
         error(JSMSG_DECLARATION_AFTER_EXPORT);
         return null();
       }
 
       case TOK_CLASS:
         return exportClassDeclaration(begin);
@@ -7018,16 +7037,17 @@ JSOpFromPropertyType(PropertyType propTy
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
                                       ClassContext classContext,
                                       DefaultHandling defaultHandling)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS));
 
+    uint32_t classStartOffset = pos().begin;
     bool savedStrictness = setLocalStrictMode(true);
 
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
 
     RootedPropertyName name(context);
     if (TokenKindIsPossibleIdentifier(tt)) {
@@ -7043,26 +7063,30 @@ Parser<ParseHandler>::classDefinition(Yi
             error(JSMSG_UNNAMED_CLASS_STMT);
             return null();
         }
     } else {
         // Make sure to put it back, whatever it was
         tokenStream.ungetToken();
     }
 
+    // Push a ParseContext::ClassStatement to keep track of the constructor
+    // funbox.
+    ParseContext::ClassStatement classStmt(pc);
+
     RootedAtom propAtom(context);
 
     // A named class creates a new lexical scope with a const binding of the
-    // class name.
-    Maybe<ParseContext::Statement> classStmt;
-    Maybe<ParseContext::Scope> classScope;
+    // class name for the "inner name".
+    Maybe<ParseContext::Statement> innerScopeStmt;
+    Maybe<ParseContext::Scope> innerScope;
     if (name) {
-        classStmt.emplace(pc, StatementKind::Block);
-        classScope.emplace(this);
-        if (!classScope->init(pc))
+        innerScopeStmt.emplace(pc, StatementKind::Block);
+        innerScope.emplace(this);
+        if (!innerScope->init(pc))
             return null();
     }
 
     // Because the binding definitions keep track of their blockId, we need to
     // create at least the inner binding later. Keep track of the name's position
     // in order to provide it for the nodes created later.
     TokenPos namePos = pos();
 
@@ -7079,17 +7103,16 @@ Parser<ParseHandler>::classDefinition(Yi
     }
 
     MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CLASS);
 
     Node classMethods = handler.newClassMethodList(pos().begin);
     if (!classMethods)
         return null();
 
-    bool seenConstructor = false;
     for (;;) {
         TokenKind tt;
         if (!tokenStream.getToken(&tt))
             return null();
         if (tt == TOK_RC)
             break;
 
         if (tt == TOK_SEMI)
@@ -7130,26 +7153,27 @@ Parser<ParseHandler>::classDefinition(Yi
             errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
             return null();
         }
 
         if (propType == PropertyType::Getter)
             propType = PropertyType::GetterNoExpressionClosure;
         if (propType == PropertyType::Setter)
             propType = PropertyType::SetterNoExpressionClosure;
-        if (!isStatic && propAtom == context->names().constructor) {
+
+        bool isConstructor = !isStatic && propAtom == context->names().constructor;
+        if (isConstructor) {
             if (propType != PropertyType::Method) {
                 errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
                 return null();
             }
-            if (seenConstructor) {
+            if (classStmt.constructorBox()) {
                 errorAt(nameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor");
                 return null();
             }
-            seenConstructor = true;
             propType = hasHeritage ? PropertyType::DerivedConstructor : PropertyType::Constructor;
         } else if (isStatic && propAtom == context->names().prototype) {
             errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
             return null();
         }
 
         RootedAtom funName(context);
         switch (propType) {
@@ -7164,47 +7188,61 @@ Parser<ParseHandler>::classDefinition(Yi
           case PropertyType::Constructor:
           case PropertyType::DerivedConstructor:
             funName = name;
             break;
           default:
             if (!tokenStream.isCurrentTokenType(TOK_RB))
                 funName = propAtom;
         }
-        Node fn = methodDefinition(nameOffset, propType, funName);
+
+        // Calling toString on constructors need to return the source text for
+        // the entire class. The end offset is unknown at this point in
+        // parsing and will be amended when class parsing finishes below.
+        Node fn = methodDefinition(isConstructor ? classStartOffset : nameOffset,
+                                   propType, funName);
         if (!fn)
             return null();
 
         handler.checkAndSetIsDirectRHSAnonFunction(fn);
 
         JSOp op = JSOpFromPropertyType(propType);
         if (!handler.addClassMethodDefinition(classMethods, propName, fn, op, isStatic))
             return null();
     }
 
+    // Amend the toStringEnd offset for the constructor now that we've
+    // finished parsing the class.
+    uint32_t classEndOffset = pos().end;
+    if (FunctionBox* ctorbox = classStmt.constructorBox()) {
+        if (ctorbox->function()->isInterpretedLazy())
+            ctorbox->function()->lazyScript()->setToStringEnd(classEndOffset);
+        ctorbox->toStringEnd = classEndOffset;
+    }
+
     Node nameNode = null();
     Node methodsOrBlock = classMethods;
     if (name) {
         // The inner name is immutable.
         if (!noteDeclaredName(name, DeclarationKind::Const, namePos))
             return null();
 
         Node innerName = newName(name, namePos);
         if (!innerName)
             return null();
 
-        Node classBlock = finishLexicalScope(*classScope, classMethods);
+        Node classBlock = finishLexicalScope(*innerScope, classMethods);
         if (!classBlock)
             return null();
 
         methodsOrBlock = classBlock;
 
         // Pop the inner scope.
-        classScope.reset();
-        classStmt.reset();
+        innerScope.reset();
+        innerScopeStmt.reset();
 
         Node outerName = null();
         if (classContext == ClassStatement) {
             // The outer name is mutable.
             if (!noteDeclaredName(name, DeclarationKind::Let, namePos))
                 return null();
 
             outerName = newName(name, namePos);
@@ -7214,17 +7252,18 @@ Parser<ParseHandler>::classDefinition(Yi
 
         nameNode = handler.newClassNames(outerName, innerName, namePos);
         if (!nameNode)
             return null();
     }
 
     MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness));
 
-    return handler.newClass(nameNode, classHeritage, methodsOrBlock);
+    return handler.newClass(nameNode, classHeritage, methodsOrBlock,
+                            TokenPos(classStartOffset, classEndOffset));
 }
 
 template <class ParseHandler>
 bool
 Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LET));
 
@@ -7557,19 +7596,19 @@ Parser<ParseHandler>::statementListItem(
         if (tt == TOK_LET && nextTokenContinuesLetDeclaration(next, yieldHandling))
             return lexicalDeclaration(yieldHandling, DeclarationKind::Let);
 
         if (tt == TOK_ASYNC) {
             TokenKind nextSameLine = TOK_EOF;
             if (!tokenStream.peekTokenSameLine(&nextSameLine))
                 return null();
             if (nextSameLine == TOK_FUNCTION) {
-                uint32_t preludeStart = pos().begin;
+                uint32_t toStringStart = pos().begin;
                 tokenStream.consumeKnownToken(TOK_FUNCTION);
-                return functionStmt(preludeStart, yieldHandling, NameRequired, AsyncFunction);
+                return functionStmt(toStringStart, yieldHandling, NameRequired, AsyncFunction);
             }
         }
 
         if (next == TOK_COLON)
             return labeledStatement(yieldHandling);
 
         return expressionStatement(yieldHandling);
       }
@@ -8104,17 +8143,17 @@ Parser<ParseHandler>::assignExpr(InHandl
             return null();
         if (next == TOK_LC)
             isBlock = true;
 
         tokenStream.seek(start);
 
         if (!tokenStream.getToken(&next, TokenStream::Operand))
             return null();
-        uint32_t preludeStart = pos().begin;
+        uint32_t toStringStart = pos().begin;
         tokenStream.ungetToken();
 
         FunctionAsyncKind asyncKind = SyncFunction;
 
         if (next == TOK_ASYNC) {
             tokenStream.consumeKnownToken(next, TokenStream::Operand);
 
             TokenKind nextSameLine = TOK_EOF;
@@ -8129,17 +8168,17 @@ Parser<ParseHandler>::assignExpr(InHandl
             else
                 tokenStream.ungetToken();
         }
 
         Node pn = handler.newArrowFunction(pos());
         if (!pn)
             return null();
 
-        Node arrowFunc = functionDefinition(pn, preludeStart, inHandling, yieldHandling, nullptr,
+        Node arrowFunc = functionDefinition(pn, toStringStart, inHandling, yieldHandling, nullptr,
                                             Arrow, NotGenerator, asyncKind);
         if (!arrowFunc)
             return null();
 
         if (isBlock) {
             // This arrow function could be a non-trailing member of a comma
             // expression or a semicolon terminating a full expression.  If so,
             // the next token is that comma/semicolon, gotten with None:
@@ -8443,17 +8482,17 @@ Parser<ParseHandler>::generatorComprehen
 
     RootedFunction fun(context, newFunction(/* atom = */ nullptr, Expression,
                                             StarGenerator, SyncFunction, proto));
     if (!fun)
         return null();
 
     // Create box for fun->object early to root it.
     Directives directives(/* strict = */ outerpc->sc()->strict());
-    FunctionBox* genFunbox = newFunctionBox(genfn, fun, /* preludeStart = */ 0, directives,
+    FunctionBox* genFunbox = newFunctionBox(genfn, fun, /* toStringStart = */ 0, directives,
                                             StarGenerator, SyncFunction, /* tryAnnexB = */ false);
     if (!genFunbox)
         return null();
     genFunbox->isGenexpLambda = true;
     genFunbox->initWithEnclosingParseContext(outerpc, Expression);
 
     ParseContext genpc(this, genFunbox, /* newDirectives = */ nullptr);
     if (!genpc.init())
@@ -8479,17 +8518,17 @@ Parser<ParseHandler>::generatorComprehen
     if (!comp)
         return null();
 
     MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN);
 
     uint32_t end = pos().end;
     handler.setBeginPosition(comp, begin);
     handler.setEndPosition(comp, end);
-    genFunbox->bufEnd = end;
+    genFunbox->setEnd(end);
     handler.addStatementToList(body, comp);
     handler.setEndPosition(body, end);
     handler.setBeginPosition(genfn, begin);
     handler.setEndPosition(genfn, end);
 
     Node generator = newDotGeneratorName();
     if (!generator)
         return null();
@@ -9715,17 +9754,17 @@ Parser<ParseHandler>::objectLiteral(Yiel
     }
 
     handler.setEndPosition(literal, pos().end);
     return literal;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
-Parser<ParseHandler>::methodDefinition(uint32_t preludeStart, PropertyType propType,
+Parser<ParseHandler>::methodDefinition(uint32_t toStringStart, PropertyType propType,
                                        HandleAtom funName)
 {
     FunctionSyntaxKind kind;
     switch (propType) {
       case PropertyType::Getter:
         kind = Getter;
         break;
 
@@ -9771,17 +9810,17 @@ Parser<ParseHandler>::methodDefinition(u
                                   : SyncFunction;
 
     YieldHandling yieldHandling = GetYieldHandling(generatorKind);
 
     Node pn = handler.newFunctionExpression(pos());
     if (!pn)
         return null();
 
-    return functionDefinition(pn, preludeStart, InAllowed, yieldHandling, funName, kind,
+    return functionDefinition(pn, toStringStart, InAllowed, yieldHandling, funName, kind,
                               generatorKind, asyncKind);
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::tryNewTarget(Node &newTarget)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NEW));
@@ -9901,19 +9940,19 @@ Parser<ParseHandler>::primaryExpr(YieldH
         }
 
         if (tt == TOK_ASYNC) {
             TokenKind nextSameLine = TOK_EOF;
             if (!tokenStream.peekTokenSameLine(&nextSameLine))
                 return null();
 
             if (nextSameLine == TOK_FUNCTION) {
-                uint32_t preludeStart = pos().begin;
+                uint32_t toStringStart = pos().begin;
                 tokenStream.consumeKnownToken(TOK_FUNCTION);
-                return functionExpr(preludeStart, PredictUninvoked, AsyncFunction);
+                return functionExpr(toStringStart, PredictUninvoked, AsyncFunction);
             }
         }
 
         Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
         if (!name)
             return null();
 
         return identifierReference(name);
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -82,16 +82,41 @@ class ParseContext : public Nestable<Par
             label_(pc->sc_->context, label)
         { }
 
         HandleAtom label() const {
             return label_;
         }
     };
 
+    class ClassStatement : public Statement
+    {
+        FunctionBox* constructorBox_;
+
+      public:
+        explicit ClassStatement(ParseContext* pc)
+          : Statement(pc, StatementKind::Class),
+            constructorBox_(nullptr)
+        { }
+
+        void clearConstructorBoxForAbortedSyntaxParse(FunctionBox* funbox) {
+            MOZ_ASSERT(constructorBox_ == funbox);
+            constructorBox_ = nullptr;
+        }
+
+        void setConstructorBox(FunctionBox* funbox) {
+            MOZ_ASSERT(!constructorBox_);
+            constructorBox_ = funbox;
+        }
+
+        FunctionBox* constructorBox() const {
+            return constructorBox_;
+        }
+    };
+
     // The intra-function scope stack.
     //
     // Tracks declared and used names within a scope.
     class Scope : public Nestable<Scope>
     {
         // Names declared in this scope. Corresponds to the union of
         // VarDeclaredNames and LexicallyDeclaredNames in the ES spec.
         //
@@ -437,16 +462,21 @@ class ParseContext : public Nestable<Par
         return Statement::findNearest(innermostStatement_, predicate);
     }
 
     template <typename T, typename Predicate /* (Statement*) -> bool */>
     T* findInnermostStatement(Predicate predicate) {
         return Statement::findNearest<T>(innermostStatement_, predicate);
     }
 
+    template <typename T>
+    T* findInnermostStatement() {
+        return Statement::findNearest<T>(innermostStatement_);
+    }
+
     AtomVector& positionalFormalParameterNames() {
         return *positionalFormalParameterNames_;
     }
 
     AtomVector& closedOverBindingsForLazy() {
         return *closedOverBindingsForLazy_;
     }
 
@@ -537,16 +567,23 @@ class ParseContext : public Nestable<Par
 
 template <>
 inline bool
 ParseContext::Statement::is<ParseContext::LabelStatement>() const
 {
     return kind_ == StatementKind::Label;
 }
 
+template <>
+inline bool
+ParseContext::Statement::is<ParseContext::ClassStatement>() const
+{
+    return kind_ == StatementKind::Class;
+}
+
 template <typename T>
 inline T&
 ParseContext::Statement::as()
 {
     MOZ_ASSERT(is<T>());
     return static_cast<T&>(*this);
 }
 
@@ -1076,17 +1113,17 @@ class Parser final : public ParserBase, 
 
     friend void js::frontend::TraceParser(JSTracer* trc, JS::AutoGCRooter* parser);
 
     /*
      * Parse a top-level JS script.
      */
     Node parse();
 
-    FunctionBox* newFunctionBox(Node fn, JSFunction* fun, uint32_t preludeStart,
+    FunctionBox* newFunctionBox(Node fn, JSFunction* fun, uint32_t toStringStart,
                                 Directives directives,
                                 GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
                                 bool tryAnnexB);
 
     void trace(JSTracer* trc);
 
   private:
     Parser* thisForCtor() { return this; }
@@ -1135,17 +1172,17 @@ class Parser final : public ParserBase, 
 
     // Parse a function, given only its arguments and body. Used for lazily
     // parsed functions.
     Node standaloneLazyFunction(HandleFunction fun, bool strict,
                                 GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
 
     // Parse an inner function given an enclosing ParseContext and a
     // FunctionBox for the inner function.
-    bool innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox, uint32_t preludeStart,
+    bool innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox, uint32_t toStringStart,
                        InHandling inHandling, YieldHandling yieldHandling,
                        FunctionSyntaxKind kind,
                        Directives inheritedDirectives, Directives* newDirectives);
 
     // Parse a function's formal parameters and its body assuming its function
     // ParseContext is already on the stack.
     bool functionFormalParametersAndBody(InHandling inHandling, YieldHandling yieldHandling,
                                          Node pn, FunctionSyntaxKind kind,
@@ -1169,20 +1206,20 @@ class Parser final : public ParserBase, 
      * Parsers whose name has a '1' suffix leave the TokenStream state
      * pointing to the token one past the end of the parsed fragment.  For a
      * number of the parsers this is convenient and avoids a lot of
      * unnecessary ungetting and regetting of tokens.
      *
      * Some parsers have two versions:  an always-inlined version (with an 'i'
      * suffix) and a never-inlined version (with an 'n' suffix).
      */
-    Node functionStmt(uint32_t preludeStart,
+    Node functionStmt(uint32_t toStringStart,
                       YieldHandling yieldHandling, DefaultHandling defaultHandling,
                       FunctionAsyncKind asyncKind = SyncFunction);
-    Node functionExpr(uint32_t preludeStart, InvokedPrediction invoked = PredictUninvoked,
+    Node functionExpr(uint32_t toStringStart, InvokedPrediction invoked = PredictUninvoked,
                       FunctionAsyncKind asyncKind = SyncFunction);
 
     Node statementList(YieldHandling yieldHandling);
 
     Node blockStatement(YieldHandling yieldHandling,
                         unsigned errorNumber = JSMSG_CURLY_IN_COMPOUND);
     Node doWhileStatement(YieldHandling yieldHandling);
     Node whileStatement(YieldHandling yieldHandling);
@@ -1225,22 +1262,22 @@ class Parser final : public ParserBase, 
 
     bool processExport(Node node);
     bool processExportFrom(Node node);
 
     Node exportFrom(uint32_t begin, Node specList);
     Node exportBatch(uint32_t begin);
     bool checkLocalExportNames(Node node);
     Node exportClause(uint32_t begin);
-    Node exportFunctionDeclaration(uint32_t begin, uint32_t preludeStart,
+    Node exportFunctionDeclaration(uint32_t begin, uint32_t toStringStart,
                                    FunctionAsyncKind asyncKind = SyncFunction);
     Node exportVariableStatement(uint32_t begin);
     Node exportClassDeclaration(uint32_t begin);
     Node exportLexicalDeclaration(uint32_t begin, DeclarationKind kind);
-    Node exportDefaultFunctionDeclaration(uint32_t begin, uint32_t preludeStart,
+    Node exportDefaultFunctionDeclaration(uint32_t begin, uint32_t toStringStart,
                                           FunctionAsyncKind asyncKind = SyncFunction);
     Node exportDefaultClassDeclaration(uint32_t begin);
     Node exportDefaultAssignExpr(uint32_t begin);
     Node exportDefault(uint32_t begin);
     Node exportDeclaration();
 
     Node expressionStatement(YieldHandling yieldHandling,
                              InvokedPrediction invoked = PredictUninvoked);
@@ -1323,25 +1360,25 @@ class Parser final : public ParserBase, 
                      TokenKind tt, PossibleError* possibleError,
                      InvokedPrediction invoked = PredictUninvoked);
     Node exprInParens(InHandling inHandling, YieldHandling yieldHandling,
                       TripledotHandling tripledotHandling, PossibleError* possibleError = nullptr);
 
     bool tryNewTarget(Node& newTarget);
     bool checkAndMarkSuperScope();
 
-    Node methodDefinition(uint32_t preludeStart, PropertyType propType, HandleAtom funName);
+    Node methodDefinition(uint32_t toStringStart, PropertyType propType, HandleAtom funName);
 
     /*
      * Additional JS parsers.
      */
     bool functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind,
                            Node funcpn);
 
-    Node functionDefinition(Node func, uint32_t preludeStart,
+    Node functionDefinition(Node func, uint32_t toStringStart,
                             InHandling inHandling, YieldHandling yieldHandling,
                             HandleAtom name, FunctionSyntaxKind kind,
                             GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
                             bool tryAnnexB = false);
 
     // Parse a function body.  Pass StatementListBody if the body is a list of
     // statements; pass ExpressionBody if the body is a single expression.
     enum FunctionBodyType { StatementListBody, ExpressionBody };
@@ -1414,24 +1451,24 @@ class Parser final : public ParserBase, 
     bool hasUsedFunctionSpecialName(HandlePropertyName name);
     bool declareFunctionArgumentsObject();
     bool declareFunctionThis();
     Node newInternalDotName(HandlePropertyName name);
     Node newThisName();
     Node newDotGeneratorName();
     bool declareDotGeneratorName();
 
-    bool skipLazyInnerFunction(Node pn, uint32_t preludeStart, FunctionSyntaxKind kind,
+    bool skipLazyInnerFunction(Node pn, uint32_t toStringStart, FunctionSyntaxKind kind,
                                bool tryAnnexB);
-    bool innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun, uint32_t preludeStart,
+    bool innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun, uint32_t toStringStart,
                        InHandling inHandling, YieldHandling yieldHandling,
                        FunctionSyntaxKind kind,
                        GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
                        Directives inheritedDirectives, Directives* newDirectives);
-    bool trySyntaxParseInnerFunction(Node pn, HandleFunction fun, uint32_t preludeStart,
+    bool trySyntaxParseInnerFunction(Node pn, HandleFunction fun, uint32_t toStringStart,
                                      InHandling inHandling, YieldHandling yieldHandling,
                                      FunctionSyntaxKind kind,
                                      GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
                                      bool tryAnnexB,
                                      Directives inheritedDirectives, Directives* newDirectives);
     bool finishFunctionScopes(bool isStandaloneFunction);
     bool finishFunction(bool isStandaloneFunction = false);
     bool leaveInnerFunction(ParseContext* outerpc);
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -33,16 +33,17 @@ enum class StatementKind : uint8_t
     Try,
     Finally,
     ForLoopLexicalHead,
     ForLoop,
     ForInLoop,
     ForOfLoop,
     DoLoop,
     WhileLoop,
+    Class,
 
     // Used only by BytecodeEmitter.
     Spread
 };
 
 static inline bool
 StatementKindIsLoop(StatementKind kind)
 {
@@ -445,17 +446,18 @@ class FunctionBox : public ObjectBox, pu
     void initWithEnclosingScope(Scope* enclosingScope);
 
   public:
     ParseNode*      functionNode;           /* back pointer used by asm.js for error messages */
     uint32_t        bufStart;
     uint32_t        bufEnd;
     uint32_t        startLine;
     uint32_t        startColumn;
-    uint32_t        preludeStart;
+    uint32_t        toStringStart;
+    uint32_t        toStringEnd;
     uint16_t        length;
 
     uint8_t         generatorKindBits_;     /* The GeneratorKind of this function. */
     uint8_t         asyncKindBits_;         /* The FunctionAsyncKind of this function. */
 
     bool            isGenexpLambda:1;       /* lambda from generator expression */
     bool            hasDestructuringArgs:1; /* parameter list contains destructuring expression */
     bool            hasParameterExprs:1;    /* parameter list contains expressions */
@@ -475,17 +477,17 @@ class FunctionBox : public ObjectBox, pu
     bool            hasRest_:1;             /* has rest parameter */
     bool            isExprBody_:1;          /* arrow function with expression
                                              * body or expression closure:
                                              * function(x) x*x */
 
     FunctionContextFlags funCxFlags;
 
     FunctionBox(JSContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, JSFunction* fun,
-                uint32_t preludeStart, Directives directives, bool extraWarnings,
+                uint32_t toStringStart, Directives directives, bool extraWarnings,
                 GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
 
     MutableHandle<LexicalScope::Data*> namedLambdaBindings() {
         MOZ_ASSERT(context->keepAtoms);
         return MutableHandle<LexicalScope::Data*>::fromMarkedLocation(&namedLambdaBindings_);
     }
 
     MutableHandle<FunctionScope::Data*> functionScopeBindings() {
@@ -496,16 +498,17 @@ class FunctionBox : public ObjectBox, pu
     MutableHandle<VarScope::Data*> extraVarScopeBindings() {
         MOZ_ASSERT(context->keepAtoms);
         return MutableHandle<VarScope::Data*>::fromMarkedLocation(&extraVarScopeBindings_);
     }
 
     void initFromLazyFunction();
     void initStandaloneFunction(Scope* enclosingScope);
     void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind);
+    void resetForAbortedSyntaxParse(ParseContext* enclosing, FunctionSyntaxKind kind);
 
     ObjectBox* toObjectBox() override { return this; }
     JSFunction* function() const { return &object->as<JSFunction>(); }
 
     Scope* compilationEnclosingScope() const override {
         // This method is used to distinguish the outermost SharedContext. If
         // a FunctionBox is the outermost SharedContext, it must be a lazy
         // function.
@@ -610,16 +613,24 @@ class FunctionBox : public ObjectBox, pu
         return useAsm || insideUseAsm;
     }
 
     void setStart(const TokenStream& tokenStream) {
         bufStart = tokenStream.currentToken().pos.begin;
         tokenStream.srcCoords.lineNumAndColumnIndex(bufStart, &startLine, &startColumn);
     }
 
+    void setEnd(uint32_t end) {
+        // For all functions except class constructors, the buffer and
+        // toString ending positions are the same. Class constructors override
+        // the toString ending position with the end of the class definition.
+        bufEnd = end;
+        toStringEnd = end;
+    }
+
     void trace(JSTracer* trc) override;
 };
 
 inline FunctionBox*
 SharedContext::asFunctionBox()
 {
     MOZ_ASSERT(isFunctionBox());
     return static_cast<FunctionBox*>(this);
--- a/js/src/frontend/SourceNotes.h
+++ b/js/src/frontend/SourceNotes.h
@@ -51,23 +51,24 @@ namespace js {
     M(SRC_BREAK2LABEL,  "break2label", 0)  /* JSOP_GOTO for 'break label'. */                      \
     M(SRC_SWITCHBREAK,  "switchbreak", 0)  /* JSOP_GOTO is a break in a switch. */                 \
     M(SRC_TABLESWITCH,  "tableswitch", 1)  /* JSOP_TABLESWITCH; offset points to end of switch. */ \
     M(SRC_CONDSWITCH,   "condswitch",  2)  /* JSOP_CONDSWITCH; 1st offset points to end of switch, \
                                               2nd points to first JSOP_CASE. */                    \
     M(SRC_NEXTCASE,     "nextcase",    1)  /* Distance forward from one CASE in a CONDSWITCH to    \
                                               the next. */                                         \
     M(SRC_ASSIGNOP,     "assignop",    0)  /* += or another assign-op follows. */                  \
+    M(SRC_CLASS_SPAN,   "class",       2)  /* The starting and ending offsets for the class, used  \
+                                              for toString correctness for default ctors. */       \
     M(SRC_TRY,          "try",         1)  /* JSOP_TRY, offset points to goto at the end of the    \
                                               try block. */                                        \
     /* All notes above here are "gettable".  See SN_IS_GETTABLE below. */                          \
     M(SRC_COLSPAN,      "colspan",     1)  /* Number of columns this opcode spans. */              \
     M(SRC_NEWLINE,      "newline",     0)  /* Bytecode follows a source newline. */                \
     M(SRC_SETLINE,      "setline",     1)  /* A file-absolute source line number note. */          \
-    M(SRC_UNUSED20,     "unused20",    0)  /* Unused. */                                           \
     M(SRC_UNUSED21,     "unused21",    0)  /* Unused. */                                           \
     M(SRC_UNUSED22,     "unused22",    0)  /* Unused. */                                           \
     M(SRC_UNUSED23,     "unused23",    0)  /* Unused. */                                           \
     M(SRC_XDELTA,       "xdelta",      0)  /* 24-31 are for extended delta notes. */
 
 enum SrcNoteType {
 #define DEFINE_SRC_NOTE_TYPE(sym, name, arity) sym,
     FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_TYPE)
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -282,17 +282,17 @@ class SyntaxParseHandler
     void addArrayElement(Node literal, Node element) { }
 
     Node newCall(const TokenPos& pos) { return NodeFunctionCall; }
     Node newTaggedTemplate(const TokenPos& pos) { return NodeGeneric; }
 
     Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; }
     Node newClassMethodList(uint32_t begin) { return NodeGeneric; }
     Node newClassNames(Node outer, Node inner, const TokenPos& pos) { return NodeGeneric; }
-    Node newClass(Node name, Node heritage, Node methodBlock) { return NodeGeneric; }
+    Node newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { return NodeGeneric; }
 
     Node newNewTarget(Node newHolder, Node targetHolder) { return NodeGeneric; }
     Node newPosHolder(const TokenPos& pos) { return NodeGeneric; }
     Node newSuperBase(Node thisName, const TokenPos& pos) { return NodeSuperBase; }
 
     MOZ_MUST_USE bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; }
     MOZ_MUST_USE bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; }
     MOZ_MUST_USE bool addShorthand(Node literal, Node name, Node expr) { return true; }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -7032,21 +7032,20 @@ JS_PUBLIC_API(RefPtr<JS::WasmModule>)
 JS::DeserializeWasmModule(PRFileDesc* bytecode, PRFileDesc* maybeCompiled,
                           JS::BuildIdCharVector&& buildId, UniqueChars file,
                           unsigned line, unsigned column)
 {
     return wasm::DeserializeModule(bytecode, maybeCompiled, Move(buildId), Move(file), line, column);
 }
 
 JS_PUBLIC_API(void)
-JS::SetLargeAllocationFailureCallback(JSContext* cx, JS::LargeAllocationFailureCallback lafc,
-                                      void* data)
-{
-    cx->runtime()->largeAllocationFailureCallback = lafc;
-    cx->runtime()->largeAllocationFailureCallbackData = data;
+JS::SetProcessLargeAllocationFailureCallback(JS::LargeAllocationFailureCallback lafc)
+{
+    MOZ_ASSERT(!OnLargeAllocationFailure);
+    OnLargeAllocationFailure = lafc;
 }
 
 JS_PUBLIC_API(void)
 JS::SetOutOfMemoryCallback(JSContext* cx, OutOfMemoryCallback cb, void* data)
 {
     cx->runtime()->oomCallback = cb;
     cx->runtime()->oomCallbackData = data;
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -6424,26 +6424,27 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForO
   private:
     inline bool nextFromOptimizedArray(MutableHandleValue val, bool* done);
     bool materializeArrayIterator();
 };
 
 
 /**
  * If a large allocation fails when calling pod_{calloc,realloc}CanGC, the JS
- * engine may call the large-allocation- failure callback, if set, to allow the
+ * engine may call the large-allocation-failure callback, if set, to allow the
  * embedding to flush caches, possibly perform shrinking GCs, etc. to make some
- * room. The allocation will then be retried (and may still fail.)
+ * room. The allocation will then be retried (and may still fail.) This callback
+ * can be called on any thread and must be set at most once in a process.
  */
 
 typedef void
-(* LargeAllocationFailureCallback)(void* data);
+(* LargeAllocationFailureCallback)();
 
 extern JS_PUBLIC_API(void)
-SetLargeAllocationFailureCallback(JSContext* cx, LargeAllocationFailureCallback afc, void* data);
+SetProcessLargeAllocationFailureCallback(LargeAllocationFailureCallback afc);
 
 /**
  * Unlike the error reporter, which is only called if the exception for an OOM
  * bubbles up and is not caught, the OutOfMemoryCallback is called immediately
  * at the OOM site to allow the embedding to capture the current state of heap
  * allocation before anything is freed. If the large-allocation-failure callback
  * is called at all (not all allocation sites call the large-allocation-failure
  * callback on failure), it is called before the out-of-memory callback; the
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -877,17 +877,18 @@ CreateFunctionPrototype(JSContext* cx, J
     if (!sourceObject || !ScriptSourceObject::initFromOptions(cx, sourceObject, options))
         return nullptr;
 
     RootedScript script(cx, JSScript::Create(cx,
                                              options,
                                              sourceObject,
                                              begin,
                                              ss->length(),
-                                             0));
+                                             0,
+                                             ss->length()));
     if (!script || !JSScript::initFunctionPrototype(cx, script, functionProto))
         return nullptr;
 
     functionProto->initScript(script);
     ObjectGroup* protoGroup = JSObject::getGroup(cx, functionProto);
     if (!protoGroup)
         return nullptr;
 
@@ -1014,17 +1015,23 @@ js::FunctionToString(JSContext* cx, Hand
             {
                 return nullptr;
             }
             return out.finishString();
         }
     }
 
     bool funIsNonArrowLambda = fun->isLambda() && !fun->isArrow();
-    bool haveSource = fun->isInterpreted() && !fun->isSelfHostedBuiltin();
+
+    // Default class constructors are self-hosted, but have their source
+    // objects overridden to refer to the span of the class statement or
+    // expression. Non-default class constructors are never self-hosted. So,
+    // all class constructors always have source.
+    bool haveSource = fun->isInterpreted() && (fun->isClassConstructor() ||
+                                               !fun->isSelfHostedBuiltin());
 
     // If we're not in pretty mode, put parentheses around lambda functions
     // so that eval returns lambda, not function statement.
     if (haveSource && !prettyPrint && funIsNonArrowLambda) {
         if (!out.append("("))
             return nullptr;
     }
 
@@ -1055,17 +1062,17 @@ js::FunctionToString(JSContext* cx, Hand
                 return false;
             if (!out.append(fun->explicitName()))
                 return false;
         }
         return true;
     };
 
     if (haveSource) {
-        Rooted<JSFlatString*> src(cx, JSScript::sourceDataWithPrelude(cx, script));
+        Rooted<JSFlatString*> src(cx, JSScript::sourceDataForToString(cx, script));
         if (!src)
             return nullptr;
 
         if (!out.append(src))
             return nullptr;
 
         if (!prettyPrint && funIsNonArrowLambda) {
             if (!out.append(")"))
@@ -1075,37 +1082,28 @@ js::FunctionToString(JSContext* cx, Hand
         if (!AppendPrelude() ||
             !out.append("() {\n    ") ||
             !out.append("[sourceless code]") ||
             !out.append("\n}"))
         {
             return nullptr;
         }
     } else {
-        bool derived = fun->infallibleIsDefaultClassConstructor(cx);
-        if (derived && fun->isDerivedClassConstructor()) {
-            if (!AppendPrelude() ||
-                !out.append("(...args) {\n    ") ||
-                !out.append("super(...args);\n}"))
-            {
-                return nullptr;
-            }
-        } else {
-            if (!AppendPrelude() ||
-                !out.append("() {\n    "))
-                return nullptr;
+        // Default class constructors should always haveSource.
+        MOZ_ASSERT(!fun->infallibleIsDefaultClassConstructor(cx));
 
-            if (!derived) {
-                if (!out.append("[native code]"))
-                    return nullptr;
-            }
+        if (!AppendPrelude() ||
+            !out.append("() {\n    "))
+            return nullptr;
 
-            if (!out.append("\n}"))
-                return nullptr;
-        }
+        if (!out.append("[native code]"))
+            return nullptr;
+
+        if (!out.append("\n}"))
+            return nullptr;
     }
     return out.finishString();
 }
 
 JSString*
 fun_toStringHelper(JSContext* cx, HandleObject obj, unsigned indent)
 {
     if (!obj->is<JSFunction>()) {
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -232,40 +232,46 @@ XDRRelazificationInfo(XDRState<mode>* xd
     MOZ_ASSERT_IF(mode == XDR_ENCODE, !lazy->numInnerFunctions());
 
     JSContext* cx = xdr->cx();
 
     uint64_t packedFields;
     {
         uint32_t begin = script->sourceStart();
         uint32_t end = script->sourceEnd();
-        uint32_t preludeStart = script->preludeStart();
+        uint32_t toStringStart = script->toStringStart();
+        uint32_t toStringEnd = script->toStringEnd();
         uint32_t lineno = script->lineno();
         uint32_t column = script->column();
 
         if (mode == XDR_ENCODE) {
             packedFields = lazy->packedFields();
             MOZ_ASSERT(begin == lazy->begin());
             MOZ_ASSERT(end == lazy->end());
-            MOZ_ASSERT(preludeStart == lazy->preludeStart());
+            MOZ_ASSERT(toStringStart == lazy->toStringStart());
+            MOZ_ASSERT(toStringEnd == lazy->toStringEnd());
             MOZ_ASSERT(lineno == lazy->lineno());
             MOZ_ASSERT(column == lazy->column());
             // We can assert we have no inner functions because we don't
             // relazify scripts with inner functions.  See
             // JSFunction::createScriptForLazilyInterpretedFunction.
             MOZ_ASSERT(lazy->numInnerFunctions() == 0);
         }
 
         if (!xdr->codeUint64(&packedFields))
             return false;
 
         if (mode == XDR_DECODE) {
             RootedScriptSource sourceObject(cx, &script->scriptSourceUnwrap());
             lazy.set(LazyScript::Create(cx, fun, script, enclosingScope, sourceObject,
-                                        packedFields, begin, end, preludeStart, lineno, column));
+                                        packedFields, begin, end, toStringStart, lineno, column));
+            if (!lazy)
+                return false;
+
+            lazy->setToStringEnd(toStringEnd);
 
             // As opposed to XDRLazyScript, we need to restore the runtime bits
             // of the script, as we are trying to match the fact this function
             // has already been parsed and that it would need to be re-lazified.
             lazy->initRuntimeFields(packedFields);
         }
     }
 
@@ -540,17 +546,17 @@ js::XDRScript(XDRState<mode>* xdr, Handl
                 if (!sourceObject ||
                     !ScriptSourceObject::initFromOptions(cx, sourceObject, *options))
                 {
                     return false;
                 }
             }
         }
 
-        script = JSScript::Create(cx, *options, sourceObject, 0, 0, 0);
+        script = JSScript::Create(cx, *options, sourceObject, 0, 0, 0, 0);
         if (!script)
             return false;
 
         // Set the script in its function now so that inner scripts to be
         // decoded may iterate the static scope chain.
         if (fun)
             fun->initScript(script);
     } else {
@@ -629,17 +635,19 @@ js::XDRScript(XDRState<mode>* xdr, Handl
     if (scriptBits & (1 << OwnSource)) {
         if (!sourceObject->source()->performXDR<mode>(xdr))
             return false;
     }
     if (!xdr->codeUint32(&script->sourceStart_))
         return false;
     if (!xdr->codeUint32(&script->sourceEnd_))
         return false;
-    if (!xdr->codeUint32(&script->preludeStart_))
+    if (!xdr->codeUint32(&script->toStringStart_))
+        return false;
+    if (!xdr->codeUint32(&script->toStringEnd_))
         return false;
 
     if (!xdr->codeUint32(&lineno) ||
         !xdr->codeUint32(&column) ||
         !xdr->codeUint32(&nfixed) ||
         !xdr->codeUint32(&nslots))
     {
         return false;
@@ -950,49 +958,53 @@ js::XDRLazyScript(XDRState<mode>* xdr, H
                   HandleScriptSource sourceObject, HandleFunction fun,
                   MutableHandle<LazyScript*> lazy)
 {
     JSContext* cx = xdr->cx();
 
     {
         uint32_t begin;
         uint32_t end;
-        uint32_t preludeStart;
+        uint32_t toStringStart;
+        uint32_t toStringEnd;
         uint32_t lineno;
         uint32_t column;
         uint64_t packedFields;
 
         if (mode == XDR_ENCODE) {
             // Note: it's possible the LazyScript has a non-null script_ pointer
             // to a JSScript. We don't encode it: we can just delazify the
             // lazy script.
 
             MOZ_ASSERT(fun == lazy->functionNonDelazifying());
 
             begin = lazy->begin();
             end = lazy->end();
-            preludeStart = lazy->preludeStart();
+            toStringStart = lazy->toStringStart();
+            toStringEnd = lazy->toStringEnd();
             lineno = lazy->lineno();
             column = lazy->column();
             packedFields = lazy->packedFields();
         }
 
         if (!xdr->codeUint32(&begin) || !xdr->codeUint32(&end) ||
-            !xdr->codeUint32(&preludeStart) ||
+            !xdr->codeUint32(&toStringStart) ||
+            !xdr->codeUint32(&toStringEnd) ||
             !xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) ||
             !xdr->codeUint64(&packedFields))
         {
             return false;
         }
 
         if (mode == XDR_DECODE) {
             lazy.set(LazyScript::Create(cx, fun, nullptr, enclosingScope, sourceObject,
-                                        packedFields, begin, end, preludeStart, lineno, column));
+                                        packedFields, begin, end, toStringStart, lineno, column));
             if (!lazy)
                 return false;
+            lazy->setToStringEnd(toStringEnd);
             fun->initLazyScript(lazy);
         }
     }
 
     // Code closed-over bindings.
     if (!XDRLazyClosedOverBindings(xdr, lazy))
         return false;
 
@@ -1026,16 +1038,25 @@ js::XDRLazyScript(XDRState<XDR_DECODE>*,
 
 void
 JSScript::setSourceObject(JSObject* object)
 {
     MOZ_ASSERT(compartment() == object->compartment());
     sourceObject_ = object;
 }
 
+void
+JSScript::setDefaultClassConstructorSpan(JSObject* sourceObject, uint32_t start, uint32_t end)
+{
+    MOZ_ASSERT(isDefaultClassConstructor());
+    setSourceObject(sourceObject);
+    toStringStart_ = start;
+    toStringEnd_ = end;
+}
+
 js::ScriptSourceObject&
 JSScript::scriptSourceUnwrap() const {
     return UncheckedUnwrap(sourceObject())->as<ScriptSourceObject>();
 }
 
 js::ScriptSource*
 JSScript::scriptSource() const {
     return scriptSourceUnwrap().source();
@@ -1454,20 +1475,20 @@ JSScript::loadSource(JSContext* cx, Scri
 /* static */ JSFlatString*
 JSScript::sourceData(JSContext* cx, HandleScript script)
 {
     MOZ_ASSERT(script->scriptSource()->hasSourceData());
     return script->scriptSource()->substring(cx, script->sourceStart(), script->sourceEnd());
 }
 
 /* static */ JSFlatString*
-JSScript::sourceDataWithPrelude(JSContext* cx, HandleScript script)
+JSScript::sourceDataForToString(JSContext* cx, HandleScript script)
 {
     MOZ_ASSERT(script->scriptSource()->hasSourceData());
-    return script->scriptSource()->substring(cx, script->preludeStart(), script->sourceEnd());
+    return script->scriptSource()->substring(cx, script->toStringStart(), script->toStringEnd());
 }
 
 UncompressedSourceCache::AutoHoldEntry::AutoHoldEntry()
   : cache_(nullptr), sourceChunk_()
 {
 }
 
 void
@@ -2544,19 +2565,25 @@ void
 JSScript::initCompartment(JSContext* cx)
 {
     compartment_ = cx->compartment();
 }
 
 /* static */ JSScript*
 JSScript::Create(JSContext* cx, const ReadOnlyCompileOptions& options,
                  HandleObject sourceObject, uint32_t bufStart, uint32_t bufEnd,
-                 uint32_t preludeStart)
+                 uint32_t toStringStart, uint32_t toStringEnd)
 {
+    // bufStart and bufEnd specify the range of characters parsed by the
+    // Parser to produce this script. toStringStart and toStringEnd specify
+    // the range of characters to be returned for Function.prototype.toString.
     MOZ_ASSERT(bufStart <= bufEnd);
+    MOZ_ASSERT(toStringStart <= toStringEnd);
+    MOZ_ASSERT(toStringStart <= bufStart);
+    MOZ_ASSERT(toStringEnd >= bufEnd);
 
     RootedScript script(cx, Allocate<JSScript>(cx));
     if (!script)
         return nullptr;
 
     PodZero(script.get());
 
     script->initCompartment(cx);
@@ -2566,17 +2593,18 @@ JSScript::Create(JSContext* cx, const Re
     script->treatAsRunOnce_ = options.isRunOnce;
 
     script->version = options.version;
     MOZ_ASSERT(script->getVersion() == options.version);     // assert that no overflow occurred
 
     script->setSourceObject(sourceObject);
     script->sourceStart_ = bufStart;
     script->sourceEnd_ = bufEnd;
-    script->preludeStart_ = preludeStart;
+    script->toStringStart_ = toStringStart;
+    script->toStringEnd_ = toStringEnd;
 
 #ifdef MOZ_VTUNE
     script->vtuneMethodId_ = vtune::GenerateUniqueMethodID();
 #endif
 
     return script;
 }
 
@@ -3516,17 +3544,17 @@ CreateEmptyScriptForClone(JSContext* cx,
 
     CompileOptions options(cx);
     options.setMutedErrors(src->mutedErrors())
            .setSelfHostingMode(src->selfHosted())
            .setNoScriptRval(src->noScriptRval())
            .setVersion(src->getVersion());
 
     return JSScript::Create(cx, options, sourceObject, src->sourceStart(), src->sourceEnd(),
-                            src->preludeStart());
+                            src->toStringStart(), src->toStringEnd());
 }
 
 JSScript*
 js::CloneGlobalScript(JSContext* cx, ScopeKind scopeKind, HandleScript src)
 {
     MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
 
     RootedScript dst(cx, CreateEmptyScriptForClone(cx, src));
@@ -4069,30 +4097,32 @@ JSScript::formalIsAliased(unsigned argSl
 bool
 JSScript::formalLivesInArgumentsObject(unsigned argSlot)
 {
     return argsObjAliasesFormals() && !formalIsAliased(argSlot);
 }
 
 LazyScript::LazyScript(JSFunction* fun, void* table, uint64_t packedFields,
                        uint32_t begin, uint32_t end,
-                       uint32_t preludeStart,  uint32_t lineno, uint32_t column)
+                       uint32_t toStringStart, uint32_t lineno, uint32_t column)
   : script_(nullptr),
     function_(fun),
     enclosingScope_(nullptr),
     sourceObject_(nullptr),
     table_(table),
     packedFields_(packedFields),
     begin_(begin),
     end_(end),
-    preludeStart_(preludeStart),
+    toStringStart_(toStringStart),
+    toStringEnd_(end),
     lineno_(lineno),
     column_(column)
 {
     MOZ_ASSERT(begin <= end);
+    MOZ_ASSERT(toStringStart <= begin);
 }
 
 void
 LazyScript::initScript(JSScript* script)
 {
     MOZ_ASSERT(script);
     MOZ_ASSERT(!script_.unbarrieredGet());
     script_.set(script);
@@ -4128,17 +4158,17 @@ ScriptSource*
 LazyScript::maybeForwardedScriptSource() const
 {
     return UncheckedUnwrap(MaybeForwarded(sourceObject()))->as<ScriptSourceObject>().source();
 }
 
 /* static */ LazyScript*
 LazyScript::CreateRaw(JSContext* cx, HandleFunction fun,
                       uint64_t packedFields, uint32_t begin, uint32_t end,
-                      uint32_t preludeStart, uint32_t lineno, uint32_t column)
+                      uint32_t toStringStart, uint32_t lineno, uint32_t column)
 {
     union {
         PackedView p;
         uint64_t packed;
     };
 
     packed = packedFields;
 
@@ -4157,26 +4187,26 @@ LazyScript::CreateRaw(JSContext* cx, Han
 
     LazyScript* res = Allocate<LazyScript>(cx);
     if (!res)
         return nullptr;
 
     cx->compartment()->scheduleDelazificationForDebugger();
 
     return new (res) LazyScript(fun, table.forget(), packed, begin, end,
-                                preludeStart, lineno, column);
+                                toStringStart, lineno, column);
 }
 
 /* static */ LazyScript*
 LazyScript::Create(JSContext* cx, HandleFunction fun,
                    const frontend::AtomVector& closedOverBindings,
                    Handle<GCVector<JSFunction*, 8>> innerFunctions,
                    JSVersion version,
                    uint32_t begin, uint32_t end,
-                   uint32_t preludeStart, uint32_t lineno, uint32_t column)
+                   uint32_t toStringStart, uint32_t lineno, uint32_t column)
 {
     union {
         PackedView p;
         uint64_t packedFields;
     };
 
     p.version = version;
     p.shouldDeclareArguments = false;
@@ -4190,17 +4220,17 @@ LazyScript::Create(JSContext* cx, Handle
     p.strict = false;
     p.bindingsAccessedDynamically = false;
     p.hasDebuggerStatement = false;
     p.hasDirectEval = false;
     p.isLikelyConstructorWrapper = false;
     p.isDerivedClassConstructor = false;
     p.needsHomeObject = false;
 
-    LazyScript* res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, preludeStart,
+    LazyScript* res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, toStringStart,
                                             lineno, column);
     if (!res)
         return nullptr;
 
     JSAtom** resClosedOverBindings = res->closedOverBindings();
     for (size_t i = 0; i < res->numClosedOverBindings(); i++)
         resClosedOverBindings[i] = closedOverBindings[i];
 
@@ -4212,26 +4242,26 @@ LazyScript::Create(JSContext* cx, Handle
     return res;
 }
 
 /* static */ LazyScript*
 LazyScript::Create(JSContext* cx, HandleFunction fun,
                    HandleScript script, HandleScope enclosingScope,
                    HandleScriptSource sourceObject,
                    uint64_t packedFields, uint32_t begin, uint32_t end,
-                   uint32_t preludeStart, uint32_t lineno, uint32_t column)
+                   uint32_t toStringStart, uint32_t lineno, uint32_t column)
 {
     // Dummy atom which is not a valid property name.
     RootedAtom dummyAtom(cx, cx->names().comma);
 
     // Dummy function which is not a valid function as this is the one which is
     // holding this lazy script.
     HandleFunction dummyFun = fun;
 
-    LazyScript* res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, preludeStart,
+    LazyScript* res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, toStringStart,
                                             lineno, column);
     if (!res)
         return nullptr;
 
     // Fill with dummies, to be GC-safe after the initialization of the free
     // variables and inner functions.
     size_t i, num;
     JSAtom** closedOverBindings = res->closedOverBindings();
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -934,29 +934,46 @@ class JSScript : public js::gc::TenuredC
     uint32_t        mainOffset_;/* offset of main entry point from code, after
                                    predef'ing prologue */
 
     uint32_t        nfixed_;    /* fixed frame slots */
     uint32_t        nslots_;    /* slots plus maximum stack depth */
 
     uint32_t        bodyScopeIndex_; /* index into the scopes array of the body scope */
 
-    // Range of characters in scriptSource which contains this script's source.
-    // each field points the following location.
+    // Range of characters in scriptSource which contains this script's
+    // source, that is, the range used by the Parser to produce this script.
+    //
+    // Most scripted functions have sourceStart_ == toStringStart_ and
+    // sourceEnd_ == toStringEnd_. However, for functions with extra
+    // qualifiers (e.g. generators, async) and for class constructors (which
+    // need to return the entire class source), their values differ.
+    //
+    // Each field points the following locations.
     //
     //   function * f(a, b) { return a + b; }
     //   ^          ^                        ^
     //   |          |                        |
     //   |          sourceStart_             sourceEnd_
-    //   |
-    //   preludeStart_
+    //   |                                   |
+    //   toStringStart_                      toStringEnd_
+    //
+    // And, in the case of class constructors, an additional toStringEnd
+    // offset is used.
     //
+    //   class C { constructor() { this.field = 42; } }
+    //   ^         ^                                 ^ ^
+    //   |         |                                 | `---------`
+    //   |         sourceStart_                      sourceEnd_  |
+    //   |                                                       |
+    //   toStringStart_                                          toStringEnd_
     uint32_t        sourceStart_;
     uint32_t        sourceEnd_;
-    uint32_t        preludeStart_;
+    uint32_t        toStringStart_;
+    uint32_t        toStringEnd_;
 
 #ifdef MOZ_VTUNE
     // Unique Method ID passed to the VTune profiler, or 0 if unset.
     // Allows attribution of different jitcode to the same source script.
     uint32_t        vtuneMethodId_;
     // Extra padding to maintain JSScript as a multiple of gc::CellSize.
     uint32_t        __vtune_unused_padding_;
 #endif
@@ -1116,28 +1133,29 @@ class JSScript : public js::gc::TenuredC
 
     bool hasRest_:1;
     bool isExprBody_:1;
 
     // Add padding so JSScript is gc::Cell aligned. Make padding protected
     // instead of private to suppress -Wunused-private-field compiler warnings.
   protected:
 #if JS_BITS_PER_WORD == 32
-    uint32_t padding;
+    // Currently no padding is needed.
 #endif
 
     //
     // End of fields.  Start methods.
     //
 
   public:
     static JSScript* Create(JSContext* cx,
                             const JS::ReadOnlyCompileOptions& options,
-                            js::HandleObject sourceObject, uint32_t sourceStart,
-                            uint32_t sourceEnd, uint32_t preludeStart);
+                            js::HandleObject sourceObject,
+                            uint32_t sourceStart, uint32_t sourceEnd,
+                            uint32_t toStringStart, uint32_t toStringEnd);
 
     void initCompartment(JSContext* cx);
 
     // Three ways ways to initialize a JSScript. Callers of partiallyInit()
     // are responsible for notifying the debugger after successfully creating
     // any kind (function or other) of new JSScript.  However, callers of
     // fullyInitFromEmitter() do not need to do this.
     static bool partiallyInit(JSContext* cx, JS::Handle<JSScript*> script,
@@ -1278,18 +1296,22 @@ class JSScript : public js::gc::TenuredC
     uint32_t sourceStart() const {
         return sourceStart_;
     }
 
     uint32_t sourceEnd() const {
         return sourceEnd_;
     }
 
-    size_t preludeStart() const {
-        return preludeStart_;
+    uint32_t toStringStart() const {
+        return toStringStart_;
+    }
+
+    uint32_t toStringEnd() const {
+        return toStringEnd_;
     }
 
     bool noScriptRval() const {
         return noScriptRval_;
     }
 
     bool strict() const {
         return strict_;
@@ -1615,27 +1637,30 @@ class JSScript : public js::gc::TenuredC
         return bodyScope()->is<js::GlobalScope>();
     }
 
     // Returns true if the script may read formal arguments on the stack
     // directly, via lazy arguments or a rest parameter.
     bool mayReadFrameArgsDirectly();
 
     static JSFlatString* sourceData(JSContext* cx, JS::HandleScript script);
-    static JSFlatString* sourceDataWithPrelude(JSContext* cx, JS::HandleScript script);
+    static JSFlatString* sourceDataForToString(JSContext* cx, JS::HandleScript script);
 
     static bool loadSource(JSContext* cx, js::ScriptSource* ss, bool* worked);
 
     void setSourceObject(JSObject* object);
     JSObject* sourceObject() const {
         return sourceObject_;
     }
     js::ScriptSourceObject& scriptSourceUnwrap() const;
     js::ScriptSource* scriptSource() const;
     js::ScriptSource* maybeForwardedScriptSource() const;
+
+    void setDefaultClassConstructorSpan(JSObject* sourceObject, uint32_t start, uint32_t end);
+
     bool mutedErrors() const { return scriptSource()->mutedErrors(); }
     const char* filename() const { return scriptSource()->filename(); }
     const char* maybeForwardedFilename() const { return maybeForwardedScriptSource()->filename(); }
 
 #ifdef MOZ_VTUNE
     uint32_t vtuneMethodID() const { return vtuneMethodId_; }
 #endif
 
@@ -2053,17 +2078,17 @@ class LazyScript : public gc::TenuredCel
 
     // Heap allocated table with any free variables or inner functions.
     void* table_;
 
     // Add padding so LazyScript is gc::Cell aligned. Make padding protected
     // instead of private to suppress -Wunused-private-field compiler warnings.
   protected:
 #if JS_BITS_PER_WORD == 32
-    // Currently no padding is needed.
+    uint32_t padding;
 #endif
 
   private:
     static const uint32_t NumClosedOverBindingsBits = 20;
     static const uint32_t NumInnerFunctionsBits = 20;
 
     struct PackedView {
         // Assorted bits that should really be in ScriptSourceObject.
@@ -2101,59 +2126,60 @@ class LazyScript : public gc::TenuredCel
         PackedView p_;
         uint64_t packedFields_;
     };
 
     // Source location for the script.
     // See the comment in JSScript for the details
     uint32_t begin_;
     uint32_t end_;
-    uint32_t preludeStart_;
+    uint32_t toStringStart_;
+    uint32_t toStringEnd_;
     // Line and column of |begin_| position, that is the position where we
     // start parsing.
     uint32_t lineno_;
     uint32_t column_;
 
     LazyScript(JSFunction* fun, void* table, uint64_t packedFields,
-               uint32_t begin, uint32_t end, uint32_t preludeStart,
+               uint32_t begin, uint32_t end, uint32_t toStringStart,
                uint32_t lineno, uint32_t column);
 
     // Create a LazyScript without initializing the closedOverBindings and the
     // innerFunctions. To be GC-safe, the caller must initialize both vectors
     // with valid atoms and functions.
     static LazyScript* CreateRaw(JSContext* cx, HandleFunction fun,
                                  uint64_t packedData, uint32_t begin, uint32_t end,
-                                 uint32_t preludeStart, uint32_t lineno, uint32_t column);
+                                 uint32_t toStringStart, uint32_t lineno, uint32_t column);
 
   public:
     static const uint32_t NumClosedOverBindingsLimit = 1 << NumClosedOverBindingsBits;
     static const uint32_t NumInnerFunctionsLimit = 1 << NumInnerFunctionsBits;
 
     // Create a LazyScript and initialize closedOverBindings and innerFunctions
     // with the provided vectors.
     static LazyScript* Create(JSContext* cx, HandleFunction fun,
                               const frontend::AtomVector& closedOverBindings,
                               Handle<GCVector<JSFunction*, 8>> innerFunctions,
                               JSVersion version, uint32_t begin, uint32_t end,
-                              uint32_t preludeStart, uint32_t lineno, uint32_t column);
+                              uint32_t toStringStart, uint32_t lineno, uint32_t column);
 
     // Create a LazyScript and initialize the closedOverBindings and the
     // innerFunctions with dummy values to be replaced in a later initialization
     // phase.
     //
     // The "script" argument to this function can be null.  If it's non-null,
     // then this LazyScript should be associated with the given JSScript.
     //
     // The sourceObject and enclosingScope arguments may be null if the
     // enclosing function is also lazy.
     static LazyScript* Create(JSContext* cx, HandleFunction fun,
                               HandleScript script, HandleScope enclosingScope,
                               HandleScriptSource sourceObject,
                               uint64_t packedData, uint32_t begin, uint32_t end,
-                              uint32_t preludeStart, uint32_t lineno, uint32_t column);
+                              uint32_t toStringStart, uint32_t lineno, uint32_t column);
 
     void initRuntimeFields(uint64_t packedFields);
 
     static inline JSFunction* functionDelazifying(JSContext* cx, Handle<LazyScript*>);
     JSFunction* functionNonDelazifying() const {
         return function_;
     }
 
@@ -2324,26 +2350,35 @@ class LazyScript : public gc::TenuredCel
         return scriptSource()->filename();
     }
     uint32_t begin() const {
         return begin_;
     }
     uint32_t end() const {
         return end_;
     }
-    uint32_t preludeStart() const {
-        return preludeStart_;
+    uint32_t toStringStart() const {
+        return toStringStart_;
+    }
+    uint32_t toStringEnd() const {
+        return toStringEnd_;
     }
     uint32_t lineno() const {
         return lineno_;
     }
     uint32_t column() const {
         return column_;
     }
 
+    void setToStringEnd(uint32_t toStringEnd) {
+        MOZ_ASSERT(toStringStart_ <= toStringEnd);
+        MOZ_ASSERT(toStringEnd_ >= end_);
+        toStringEnd_ = toStringEnd;
+    }
+
     bool hasUncompiledEnclosingScript() const;
 
     friend class GCMarker;
     void traceChildren(JSTracer* trc);
     void finalize(js::FreeOp* fop);
 
     static const JS::TraceKind TraceKind = JS::TraceKind::LazyScript;
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1516,28 +1516,26 @@ ParseCompileOptions(JSContext* cx, Compi
         return false;
     if (v.isBoolean())
         options.setSourceIsLazy(v.toBoolean());
 
     return true;
 }
 
 static void
-my_LargeAllocFailCallback(void* data)
-{
-    JSContext* cx = (JSContext*)data;
-    JSRuntime* rt = cx->runtime();
-
-    if (cx->helperThread())
+my_LargeAllocFailCallback()
+{
+    JSContext* cx = TlsContext.get();
+    if (!cx || cx->helperThread())
         return;
 
     MOZ_ASSERT(!JS::CurrentThreadIsHeapBusy());
 
     JS::PrepareForFullGC(cx);
-    rt->gc.gc(GC_NORMAL, JS::gcreason::SHARED_MEMORY_LIMIT);
+    cx->runtime()->gc.gc(GC_NORMAL, JS::gcreason::SHARED_MEMORY_LIMIT);
 }
 
 static const uint32_t CacheEntry_SOURCE = 0;
 static const uint32_t CacheEntry_BYTECODE = 1;
 
 static const JSClass CacheEntry_class = {
     "CacheEntryObject", JSCLASS_HAS_RESERVED_SLOTS(2)
 };
@@ -2671,16 +2669,24 @@ SrcNotes(JSContext* cx, HandleScript scr
           }
 
           case SRC_TRY:
             MOZ_ASSERT(JSOp(script->code()[offset]) == JSOP_TRY);
             if (!sp->jsprintf(" offset to jump %u", unsigned(GetSrcNoteOffset(sn, 0))))
                 return false;
             break;
 
+          case SRC_CLASS_SPAN: {
+            unsigned startOffset = GetSrcNoteOffset(sn, 0);
+            unsigned endOffset = GetSrcNoteOffset(sn, 1);
+            if (!sp->jsprintf(" %u %u", startOffset, endOffset))
+                return false;
+            break;
+          }
+
           default:
             MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
         }
         if (!sp->put("\n"))
             return false;
     }
 
     return true;
@@ -3595,18 +3601,16 @@ WorkerMain(void* arg)
         if (!JS::InitSelfHostedCode(cx))
             return;
 
         JS::SetEnqueuePromiseJobCallback(cx, ShellEnqueuePromiseJobCallback);
         JS::SetGetIncumbentGlobalCallback(cx, ShellGetIncumbentGlobalCallback);
         JS::SetAsyncTaskCallbacks(cx, ShellStartAsyncTaskCallback, ShellFinishAsyncTaskCallback);
 
         environmentPreparer.emplace(cx);
-
-        JS::SetLargeAllocationFailureCallback(cx, my_LargeAllocFailCallback, (void*)cx);
     } else {
         JS_AddInterruptCallback(cx, ShellInterruptCallback);
     }
 
     do {
         JSAutoRequest ar(cx);
 
         JS::CompartmentOptions compartmentOptions;
@@ -3628,18 +3632,16 @@ WorkerMain(void* arg)
         RootedScript script(cx);
         if (!JS::Compile(cx, options, input->chars, input->length, &script))
             break;
         RootedValue result(cx);
         JS_ExecuteScript(cx, script, &result);
     } while (0);
 
     if (input->parentRuntime) {
-        JS::SetLargeAllocationFailureCallback(cx, nullptr, nullptr);
-
         JS::SetGetIncumbentGlobalCallback(cx, nullptr);
         JS::SetEnqueuePromiseJobCallback(cx, nullptr);
     }
 
     sc->jobQueue.reset();
 
     KillWatchdog(cx);
     JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
@@ -8655,17 +8657,17 @@ main(int argc, char** argv, char** envp)
     JS::SetEnqueuePromiseJobCallback(cx, ShellEnqueuePromiseJobCallback);
     JS::SetGetIncumbentGlobalCallback(cx, ShellGetIncumbentGlobalCallback);
     JS::SetAsyncTaskCallbacks(cx, ShellStartAsyncTaskCallback, ShellFinishAsyncTaskCallback);
 
     EnvironmentPreparer environmentPreparer(cx);
 
     JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
 
-    JS::SetLargeAllocationFailureCallback(cx, my_LargeAllocFailCallback, (void*)cx);
+    JS::SetProcessLargeAllocationFailureCallback(my_LargeAllocFailCallback);
 
     // Set some parameters to allow incremental GC in low memory conditions,
     // as is done for the browser, except in more-deterministic builds or when
     // disabled by command line options.
 #ifndef JS_MORE_DETERMINISTIC
     if (!op.getBoolOption("no-incremental-gc")) {
         JS_SetGCParameter(cx, JSGC_DYNAMIC_HEAP_GROWTH, 1);
         JS_SetGCParameter(cx, JSGC_DYNAMIC_MARK_SLICE, 1);
@@ -8682,18 +8684,16 @@ main(int argc, char** argv, char** envp)
 
     result = Shell(cx, &op, envp);
 
 #ifdef DEBUG
     if (OOM_printAllocationCount)
         printf("OOM max count: %" PRIu64 "\n", js::oom::counter);
 #endif
 
-    JS::SetLargeAllocationFailureCallback(cx, nullptr, nullptr);
-
     JS::SetGetIncumbentGlobalCallback(cx, nullptr);
     JS::SetEnqueuePromiseJobCallback(cx, nullptr);
     JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
 
     // Must clear out some of sc's pointer containers before JS_DestroyContext.
     sc->markObservers.reset();
     sc->jobQueue.reset();
 
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -374,23 +374,16 @@ skip script test262/built-ins/TypedArray
 skip script test262/built-ins/TypedArrays/no-args-proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrays/object-arg-proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrays/typedarray-arg-other-ctor-buffer-ctor-custom-species-proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrays/typedarray-arg-proto-from-ctor-realm.js
 skip script test262/built-ins/TypedArrays/typedarray-arg-same-ctor-buffer-ctor-species-custom-proto-from-ctor-realm.js
 skip script test262/built-ins/WeakMap/proto-from-ctor-realm.js
 skip script test262/built-ins/WeakSet/proto-from-ctor-realm.js
 
-# https://bugzilla.mozilla.org/show_bug.cgi?id=1216630
-skip script test262/built-ins/Function/prototype/toString/class-declaration-complex-heritage.js
-skip script test262/built-ins/Function/prototype/toString/class-declaration-explicit-ctor.js
-skip script test262/built-ins/Function/prototype/toString/class-declaration-implicit-ctor.js
-skip script test262/built-ins/Function/prototype/toString/class-expression-explicit-ctor.js
-skip script test262/built-ins/Function/prototype/toString/class-expression-implicit-ctor.js
-
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317395
 skip script test262/built-ins/ArrayBuffer/prototype/byteLength/detached-buffer.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317394
 skip script test262/built-ins/DataView/prototype/byteOffset/detached-buffer.js
 skip script test262/built-ins/DataView/prototype/byteLength/detached-buffer.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317391
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -241,40 +241,55 @@ SetPropertyOperation(JSContext* cx, JSOp
         return false;
 
     ObjectOpResult result;
     return SetProperty(cx, obj, id, rval, lval, result) &&
            result.checkStrictErrorOrWarning(cx, obj, id, op == JSOP_STRICTSETPROP);
 }
 
 static JSFunction*
-MakeDefaultConstructor(JSContext* cx, JSOp op, JSAtom* atom, HandleObject proto)
-{
+MakeDefaultConstructor(JSContext* cx, HandleScript script, jsbytecode* pc, HandleObject proto)
+{
+    JSOp op = JSOp(*pc);
+    JSAtom* atom = script->getAtom(pc);
     bool derived = op == JSOP_DERIVEDCONSTRUCTOR;
     MOZ_ASSERT(derived == !!proto);
 
+    jssrcnote* classNote = GetSrcNote(cx, script, pc);
+    MOZ_ASSERT(classNote && SN_TYPE(classNote) == SRC_CLASS_SPAN);
+
     PropertyName* lookup = derived ? cx->names().DefaultDerivedClassConstructor
                                    : cx->names().DefaultBaseClassConstructor;
 
     RootedPropertyName selfHostedName(cx, lookup);
     RootedAtom name(cx, atom == cx->names().empty ? nullptr : atom);
 
     RootedFunction ctor(cx);
     if (!cx->runtime()->createLazySelfHostedFunctionClone(cx, selfHostedName, name,
                                                           /* nargs = */ !!derived,
                                                           proto, TenuredObject, &ctor))
     {
         return nullptr;
     }
 
     ctor->setIsConstructor();
     ctor->setIsClassConstructor();
-
     MOZ_ASSERT(ctor->infallibleIsDefaultClassConstructor(cx));
 
+    // Create the script now, as the source span needs to be overridden for
+    // toString. Calling toString on a class constructor must not return the
+    // source for just the constructor function.
+    JSScript *ctorScript = JSFunction::getOrCreateScript(cx, ctor);
+    if (!ctorScript)
+        return nullptr;
+    uint32_t classStartOffset = GetSrcNoteOffset(classNote, 0);
+    uint32_t classEndOffset = GetSrcNoteOffset(classNote, 1);
+    ctorScript->setDefaultClassConstructorSpan(script->sourceObject(), classStartOffset,
+                                               classEndOffset);
+
     return ctor;
 }
 
 bool
 js::ReportIsNotFunction(JSContext* cx, HandleValue v, int numToSkip, MaybeConstruct construct)
 {
     unsigned error = construct ? JSMSG_NOT_CONSTRUCTOR : JSMSG_NOT_FUNCTION;
     int spIndex = numToSkip >= 0 ? -(numToSkip + 1) : JSDVG_SEARCH_STACK;
@@ -4211,29 +4226,27 @@ CASE(JSOP_SUPERFUN)
 }
 END_CASE(JSOP_SUPERFUN)
 
 CASE(JSOP_DERIVEDCONSTRUCTOR)
 {
     MOZ_ASSERT(REGS.sp[-1].isObject());
     ReservedRooted<JSObject*> proto(&rootObject0, &REGS.sp[-1].toObject());
 
-    JSFunction* constructor = MakeDefaultConstructor(cx, JSOp(*REGS.pc), script->getAtom(REGS.pc),
-                                                     proto);
+    JSFunction* constructor = MakeDefaultConstructor(cx, script, REGS.pc, proto);
     if (!constructor)
         goto error;
 
     REGS.sp[-1].setObject(*constructor);
 }
 END_CASE(JSOP_DERIVEDCONSTRUCTOR)
 
 CASE(JSOP_CLASSCONSTRUCTOR)
 {
-    JSFunction* constructor = MakeDefaultConstructor(cx, JSOp(*REGS.pc), script->getAtom(REGS.pc),
-                                                     nullptr);
+    JSFunction* constructor = MakeDefaultConstructor(cx, script, REGS.pc, nullptr);
     if (!constructor)
         goto error;
     PUSH_OBJECT(*constructor);
 }
 END_CASE(JSOP_CLASSCONSTRUCTOR)
 
 CASE(JSOP_CHECKOBJCOERCIBLE)
 {
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -58,16 +58,17 @@ using mozilla::NegativeInfinity;
 using mozilla::PodZero;
 using mozilla::PodArrayZero;
 using mozilla::PositiveInfinity;
 using JS::GenericNaN;
 using JS::DoubleNaNValue;
 
 /* static */ MOZ_THREAD_LOCAL(JSContext*) js::TlsContext;
 /* static */ Atomic<size_t> JSRuntime::liveRuntimesCount;
+Atomic<JS::LargeAllocationFailureCallback> js::OnLargeAllocationFailure;
 
 namespace js {
     bool gCanUseExtraThreads = true;
 } // namespace js
 
 void
 js::DisableExtraThreads()
 {
@@ -164,17 +165,16 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     permanentAtoms(nullptr),
     wellKnownSymbols(nullptr),
     jitSupportsFloatingPoint(false),
     jitSupportsUnalignedAccesses(false),
     jitSupportsSimd(false),
     offthreadIonCompilationEnabled_(true),
     parallelParsingEnabled_(true),
     autoWritableJitCodeActive_(false),
-    largeAllocationFailureCallback(nullptr),
     oomCallback(nullptr),
     debuggerMallocSizeOf(ReturnZeroSize),
     lastAnimationTime(0),
     performanceMonitoring_(thisFromCtor()),
     stackFormat_(parentRuntime ? js::StackFormat::Default
                                : js::StackFormat::SpiderMonkey)
 {
     liveRuntimesCount++;
@@ -824,18 +824,18 @@ JSRuntime::onOutOfMemory(AllocFunction a
     if (maybecx)
         ReportOutOfMemory(maybecx);
     return nullptr;
 }
 
 void*
 JSRuntime::onOutOfMemoryCanGC(AllocFunction allocFunc, size_t bytes, void* reallocPtr)
 {
-    if (largeAllocationFailureCallback && bytes >= LARGE_ALLOCATION)
-        largeAllocationFailureCallback(largeAllocationFailureCallbackData);
+    if (OnLargeAllocationFailure && bytes >= LARGE_ALLOCATION)
+        OnLargeAllocationFailure();
     return onOutOfMemory(allocFunc, bytes, reallocPtr);
 }
 
 bool
 JSRuntime::activeGCInAtomsZone()
 {
     Zone* zone = atomsCompartment_->zone();
     return (zone->needsIncrementalBarrier() && !gc.isVerifyPreBarriersEnabled()) ||
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -936,17 +936,17 @@ struct JSRuntime : public js::MallocProv
      * to try to recove some memory or to report an error.  For realloc, the
      * original pointer must be passed as reallocPtr.
      *
      * The function must be called outside the GC lock.
      */
     JS_FRIEND_API(void*) onOutOfMemory(js::AllocFunction allocator, size_t nbytes,
                                        void* reallocPtr = nullptr, JSContext* maybecx = nullptr);
 
-    /*  onOutOfMemory but can call the largeAllocationFailureCallback. */
+    /*  onOutOfMemory but can call OnLargeAllocationFailure. */
     JS_FRIEND_API(void*) onOutOfMemoryCanGC(js::AllocFunction allocator, size_t nbytes,
                                             void* reallocPtr = nullptr);
 
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes* runtime);
 
   private:
     // Settings for how helper threads can be used.
     mozilla::Atomic<bool> offthreadIonCompilationEnabled_;
@@ -971,20 +971,16 @@ struct JSRuntime : public js::MallocProv
         return parallelParsingEnabled_;
     }
 
     void toggleAutoWritableJitCodeActive(bool b) {
         MOZ_ASSERT(autoWritableJitCodeActive_ != b, "AutoWritableJitCode should not be nested.");
         autoWritableJitCodeActive_ = b;
     }
 
-    /* See comment for JS::SetLargeAllocationFailureCallback in jsapi.h. */
-    js::ActiveThreadData<JS::LargeAllocationFailureCallback> largeAllocationFailureCallback;
-    js::ActiveThreadData<void*> largeAllocationFailureCallbackData;
-
     /* See comment for JS::SetOutOfMemoryCallback in jsapi.h. */
     js::ActiveThreadData<JS::OutOfMemoryCallback> oomCallback;
     js::ActiveThreadData<void*> oomCallbackData;
 
     /*
      * These variations of malloc/calloc/realloc will call the
      * large-allocation-failure callback on OOM and retry the allocation.
      */
@@ -1336,15 +1332,19 @@ ZoneGroup::storeBuffer()
 }
 
 inline void
 ZoneGroup::callAfterMinorGC(void (*thunk)(void* data), void* data)
 {
     nursery().queueSweepAction(thunk, data);
 }
 
+// This callback is set by JS::SetProcessLargeAllocationFailureCallback
+// and may be null. See comment in jsapi.h.
+extern mozilla::Atomic<JS::LargeAllocationFailureCallback> OnLargeAllocationFailure;
+
 } /* namespace js */
 
 #ifdef _MSC_VER
 #pragma warning(pop)
 #endif
 
 #endif /* vm_Runtime_h */
--- a/js/src/vm/SharedArrayObject.cpp
+++ b/js/src/vm/SharedArrayObject.cpp
@@ -118,19 +118,18 @@ SharedArrayRawBuffer::New(JSContext* cx,
 
     bool preparedForAsmJS = jit::JitOptions.asmJSAtomicsEnable && IsValidAsmJSHeapLength(length);
 
     void* p = nullptr;
     if (preparedForAsmJS) {
         // Test >= to guard against the case where multiple extant runtimes
         // race to allocate.
         if (++numLive >= maxLive) {
-            JSRuntime* rt = cx->runtime();
-            if (rt->largeAllocationFailureCallback)
-                rt->largeAllocationFailureCallback(rt->largeAllocationFailureCallbackData);
+            if (OnLargeAllocationFailure)
+                OnLargeAllocationFailure();
             if (numLive >= maxLive) {
                 numLive--;
                 return nullptr;
             }
         }
 
         uint32_t mappedSize = SharedArrayMappedSize(allocSize);
 
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -320,17 +320,17 @@ struct js::AsmJSMetadata : Metadata, Asm
     // deserialization contexts. Thus, they must be set explicitly using the
     // ambient Parser/ScriptSource after deserialization.
     //
     // srcStart refers to the offset in the ScriptSource to the beginning of
     // the asm.js module function. If the function has been created with the
     // Function constructor, this will be the first character in the function
     // source. Otherwise, it will be the opening parenthesis of the arguments
     // list.
-    uint32_t                preludeStart;
+    uint32_t                toStringStart;
     uint32_t                srcStart;
     uint32_t                srcBodyStart;
     bool                    strict;
     ScriptSourceHolder      scriptSource;
 
     uint32_t srcEndBeforeCurly() const {
         return srcStart + srcLength;
     }
@@ -1774,17 +1774,17 @@ class MOZ_STACK_CLASS ModuleValidator
     }
 
   public:
     bool init() {
         asmJSMetadata_ = cx_->new_<AsmJSMetadata>();
         if (!asmJSMetadata_)
             return false;
 
-        asmJSMetadata_->preludeStart = moduleFunctionNode_->pn_funbox->preludeStart;
+        asmJSMetadata_->toStringStart = moduleFunctionNode_->pn_funbox->toStringStart;
         asmJSMetadata_->srcStart = moduleFunctionNode_->pn_body->pn_pos.begin;
         asmJSMetadata_->srcBodyStart = parser_.tokenStream.currentToken().pos.end;
         asmJSMetadata_->strict = parser_.pc->sc()->strict() &&
                                  !parser_.pc->sc()->hasExplicitUseStrict();
         asmJSMetadata_->scriptSource.reset(parser_.ss);
 
         if (!globalMap_.init() || !sigMap_.init() || !importMap_.init())
             return false;
@@ -7069,17 +7069,17 @@ CheckStatement(FunctionValidator& f, Par
 }
 
 static bool
 ParseFunction(ModuleValidator& m, ParseNode** fnOut, unsigned* line)
 {
     TokenStream& tokenStream = m.tokenStream();
 
     tokenStream.consumeKnownToken(TOK_FUNCTION, TokenStream::Operand);
-    uint32_t preludeStart = tokenStream.currentToken().pos.begin;
+    uint32_t toStringStart = tokenStream.currentToken().pos.begin;
     *line = tokenStream.srcCoords.lineNum(tokenStream.currentToken().pos.end);
 
     TokenKind tk;
     if (!tokenStream.getToken(&tk, TokenStream::Operand))
         return false;
     if (!TokenKindIsPossibleIdentifier(tk))
         return false;  // The regular parser will throw a SyntaxError, no need to m.fail.
 
@@ -7092,18 +7092,19 @@ ParseFunction(ModuleValidator& m, ParseN
         return false;
 
     RootedFunction& fun = m.dummyFunction();
     fun->setAtom(name);
     fun->setArgCount(0);
 
     ParseContext* outerpc = m.parser().pc;
     Directives directives(outerpc);
-    FunctionBox* funbox = m.parser().newFunctionBox(fn, fun, preludeStart, directives, NotGenerator,
-                                                    SyncFunction, /* tryAnnexB = */ false);
+    FunctionBox* funbox = m.parser().newFunctionBox(fn, fun, toStringStart, directives,
+                                                    NotGenerator, SyncFunction,
+                                                    /* tryAnnexB = */ false);
     if (!funbox)
         return false;
     funbox->initWithEnclosingParseContext(outerpc, frontend::Statement);
 
     Directives newDirectives = directives;
     ParseContext funpc(&m.parser(), funbox, &newDirectives);
     if (!funpc.init())
         return false;
@@ -8089,17 +8090,17 @@ HandleInstantiationFailure(JSContext* cx
     bool haveSource = source->hasSourceData();
     if (!haveSource && !JSScript::loadSource(cx, source, &haveSource))
         return false;
     if (!haveSource) {
         JS_ReportErrorASCII(cx, "asm.js link failure with source discarding enabled");
         return false;
     }
 
-    uint32_t begin = metadata.preludeStart;
+    uint32_t begin = metadata.toStringStart;
     uint32_t end = metadata.srcEndAfterCurly();
     Rooted<JSFlatString*> src(cx, source->substringDontDeflate(cx, begin, end));
     if (!src)
         return false;
 
     RootedFunction fun(cx, NewScriptedFunction(cx, 0, JSFunction::INTERPRETED_NORMAL,
                                                name, /* proto = */ nullptr, gc::AllocKind::FUNCTION,
                                                TenuredObject));
@@ -8571,17 +8572,17 @@ LookupAsmJSModuleInCache(JSContext* cx, 
     // here, just gracefully return with a cache miss.
 #ifdef NIGHTLY_BUILD
     MOZ_RELEASE_ASSERT(cursor == entry.memory + entry.serializedSize);
 #endif
     if (cursor != entry.memory + entry.serializedSize)
         return true;
 
     // See AsmJSMetadata comment as well as ModuleValidator::init().
-    asmJSMetadata->preludeStart = parser.pc->functionBox()->preludeStart;
+    asmJSMetadata->toStringStart = parser.pc->functionBox()->toStringStart;
     asmJSMetadata->srcStart = parser.pc->functionBox()->functionNode->pn_body->pn_pos.begin;
     asmJSMetadata->srcBodyStart = parser.tokenStream.currentToken().pos.end;
     asmJSMetadata->strict = parser.pc->sc()->strict() && !parser.pc->sc()->hasExplicitUseStrict();
     asmJSMetadata->scriptSource.reset(parser.ss);
 
     if (!parser.tokenStream.advance(asmJSMetadata->srcEndBeforeCurly()))
         return false;
 
@@ -8872,17 +8873,17 @@ js::IsAsmJSModuleLoadedFromCache(JSConte
 // asm.js toString/toSource support
 
 JSString*
 js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda)
 {
     MOZ_ASSERT(IsAsmJSModule(fun));
 
     const AsmJSMetadata& metadata = AsmJSModuleFunctionToModule(fun).metadata().asAsmJS();
-    uint32_t begin = metadata.preludeStart;
+    uint32_t begin = metadata.toStringStart;
     uint32_t end = metadata.srcEndAfterCurly();
     ScriptSource* source = metadata.scriptSource.get();
 
     StringBuffer out(cx);
 
     if (addParenToLambda && fun->isLambda() && !out.append("("))
         return nullptr;
 
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -72,19 +72,18 @@ AllocateCodeSegment(JSContext* cx, uint3
     codeLength = JS_ROUNDUP(codeLength, ExecutableCodePageSize);
 
     void* p = AllocateExecutableMemory(codeLength, ProtectionSetting::Writable);
 
     // If the allocation failed and the embedding gives us a last-ditch attempt
     // to purge all memory (which, in gecko, does a purging GC/CC/GC), do that
     // then retry the allocation.
     if (!p) {
-        JSRuntime* rt = cx->runtime();
-        if (rt->largeAllocationFailureCallback) {
-            rt->largeAllocationFailureCallback(rt->largeAllocationFailureCallbackData);
+        if (OnLargeAllocationFailure) {
+            OnLargeAllocationFailure();
             p = AllocateExecutableMemory(codeLength, ProtectionSetting::Writable);
         }
     }
 
     if (!p) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1442,22 +1442,95 @@ XPCJSContext::CustomOutOfMemoryCallback(
 
     // If this fails, it fails silently.
     dumper->DumpMemoryInfoToTempDir(NS_LITERAL_STRING("due-to-JS-OOM"),
                                     /* anonymize = */ false,
                                     /* minimizeMemoryUsage = */ false);
 }
 
 void
-XPCJSContext::CustomLargeAllocationFailureCallback()
+XPCJSContext::OnLargeAllocationFailure()
 {
+    CycleCollectedJSContext::SetLargeAllocationFailure(OOMState::Reporting);
+
     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
     if (os) {
         os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
     }
+
+    CycleCollectedJSContext::SetLargeAllocationFailure(OOMState::Reported);
+}
+
+class LargeAllocationFailureRunnable final : public Runnable
+{
+    Mutex mMutex;
+    CondVar mCondVar;
+    bool mWaiting;
+
+    virtual ~LargeAllocationFailureRunnable()
+    {
+        MOZ_ASSERT(!mWaiting);
+    }
+
+  protected:
+    NS_IMETHOD Run() override
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+
+        XPCJSContext::Get()->OnLargeAllocationFailure();
+
+        MutexAutoLock lock(mMutex);
+        MOZ_ASSERT(mWaiting);
+
+        mWaiting = false;
+        mCondVar.Notify();
+        return NS_OK;
+    }
+
+  public:
+    LargeAllocationFailureRunnable()
+      : mMutex("LargeAllocationFailureRunnable::mMutex"),
+        mCondVar(mMutex, "LargeAllocationFailureRunnable::mCondVar"),
+        mWaiting(true)
+    {
+        MOZ_ASSERT(!NS_IsMainThread());
+    }
+
+    void BlockUntilDone()
+    {
+        MOZ_ASSERT(!NS_IsMainThread());
+
+        MutexAutoLock lock(mMutex);
+        while (mWaiting) {
+            mCondVar.Wait();
+        }
+    }
+};
+
+static void
+OnLargeAllocationFailureCallback()
+{
+    // This callback can be called from any thread, including internal JS helper
+    // and DOM worker threads. We need to send the low-memory event via the
+    // observer service which can only be called on the main thread, so proxy to
+    // the main thread if we're not there already. The purpose of this callback
+    // is to synchronously free some memory so the caller can retry a failed
+    // allocation, so block on the completion.
+
+    if (NS_IsMainThread()) {
+        XPCJSContext::Get()->OnLargeAllocationFailure();
+        return;
+    }
+
+    RefPtr<LargeAllocationFailureRunnable> r = new LargeAllocationFailureRunnable;
+    if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) {
+        return;
+    }
+
+    r->BlockUntilDone();
 }
 
 size_t
 XPCJSContext::SizeOfIncludingThis(MallocSizeOf mallocSizeOf)
 {
     size_t n = 0;
     n += mallocSizeOf(this);
     n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf);
@@ -3536,16 +3609,17 @@ XPCJSContext::Initialize()
     js::SetPreserveWrapperCallback(cx, PreserveWrapper);
 #ifdef MOZ_GECKO_PROFILER
     profiler_set_js_context(cx);
 #endif
     JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback);
     js::SetActivityCallback(cx, ActivityCallback, this);
     JS_AddInterruptCallback(cx, InterruptCallback);
     js::SetWindowProxyClass(cx, &OuterWindowProxyClass);
+    JS::SetProcessLargeAllocationFailureCallback(OnLargeAllocationFailureCallback);
 
     // The JS engine needs to keep the source code around in order to implement
     // Function.prototype.toSource(). It'd be nice to not have to do this for
     // chrome code and simply stub out requests for source on it. Life is not so
     // easy, unfortunately. Nobody relies on chrome toSource() working in core
     // browser code, but chrome tests use it. The worst offenders are addons,
     // which like to monkeypatch chrome functions by calling toSource() on them
     // and using regular expressions to modify them. We avoid keeping most browser
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -524,17 +524,17 @@ public:
     void UnmarkSkippableJSHolders();
     void PrepareForForgetSkippable() override;
     void BeginCycleCollectionCallback() override;
     void EndCycleCollectionCallback(mozilla::CycleCollectorResults& aResults) override;
     void DispatchDeferredDeletion(bool aContinuation, bool aPurge = false) override;
 
     void CustomGCCallback(JSGCStatus status) override;
     void CustomOutOfMemoryCallback() override;
-    void CustomLargeAllocationFailureCallback() override;
+    void OnLargeAllocationFailure();
     static void GCSliceCallback(JSContext* cx,
                                 JS::GCProgress progress,
                                 const JS::GCDescription& desc);
     static void DoCycleCollectionCallback(JSContext* cx);
     static void FinalizeCallback(JSFreeOp* fop,
                                  JSFinalizeStatus status,
                                  bool isZoneGC,
                                  void* data);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -7139,17 +7139,17 @@ nsLayoutUtils::GetDeviceContextForScreen
     // the caller.  It will also make sure that our prescontext has been created,
     // if we're supposed to have one.
     nsCOMPtr<nsPIDOMWindowOuter> win = docShell->GetWindow();
     if (!win) {
       // No reason to go on
       return nullptr;
     }
 
-    win->EnsureSizeUpToDate();
+    win->EnsureSizeAndPositionUpToDate();
 
     RefPtr<nsPresContext> presContext;
     docShell->GetPresContext(getter_AddRefs(presContext));
     if (presContext) {
       nsDeviceContext* context = presContext->DeviceContext();
       if (context) {
         return context;
       }
--- a/netwerk/base/Predictor.cpp
+++ b/netwerk/base/Predictor.cpp
@@ -44,16 +44,17 @@
 #include "mozilla/net/NeckoParent.h"
 
 #include "LoadContextInfo.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "SerializedLoadContext.h"
 #include "mozilla/net/NeckoChild.h"
 
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/ClearOnShutdown.h"
 
 using namespace mozilla;
 
 namespace mozilla {
 namespace net {
 
 Predictor *Predictor::sSelf = nullptr;
 
@@ -2198,27 +2199,34 @@ Predictor::Resetter::Complete()
     return;
   }
 
   obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
 }
 
 // Helper functions to make using the predictor easier from native code
 
+static StaticRefPtr<nsINetworkPredictor> sPredictor;
+
 static nsresult
 EnsureGlobalPredictor(nsINetworkPredictor **aPredictor)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsresult rv;
-  nsCOMPtr<nsINetworkPredictor> predictor =
-    do_GetService("@mozilla.org/network/predictor;1",
-                  &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
+  if (!sPredictor) {
+    nsresult rv;
+    nsCOMPtr<nsINetworkPredictor> predictor =
+      do_GetService("@mozilla.org/network/predictor;1",
+                    &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    sPredictor = predictor;
+    ClearOnShutdown(&sPredictor);
+  }
+
+  nsCOMPtr<nsINetworkPredictor> predictor = sPredictor.get();
   predictor.forget(aPredictor);
   return NS_OK;
 }
 
 nsresult
 PredictorPredict(nsIURI *targetURI, nsIURI *sourceURI,
                  PredictorPredictReason reason,
                  const OriginAttributes& originAttributes,
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -614,17 +614,17 @@ this.ExtensionContent = {
 
   initExtensionContext(extension, window) {
     DocumentManager.initExtensionContext(extension, window);
   },
 
   getContext(extension, window) {
     let extensions = DocumentManager.getContexts(window);
 
-    let context = extensions.get(this);
+    let context = extensions.get(extension);
     if (!context) {
       context = new ContentScriptContextChild(extension, window);
       extensions.set(extension, context);
     }
     return context;
   },
 
   handleExtensionCapture(global, width, height, options) {
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -171,93 +171,16 @@ var Service = {
   // attached to the URI.
   extensionURIToAddonID(uri) {
     let uuid = uri.host;
     let extension = this.uuidMap.get(uuid);
     return extension ? extension.id : undefined;
   },
 };
 
-// API Levels Helpers
-
-// Find the add-on associated with this document via the
-// principal's addonId attribute. This value is computed by
-// extensionURIToAddonID, which ensures that we don't inject our
-// API into webAccessibleResources or remote web pages.
-function getAddonIdForWindow(window) {
-  return Cu.getObjectPrincipal(window).addonId;
-}
-
-const API_LEVELS = Object.freeze({
-  NO_PRIVILEGES: 0,
-  CONTENTSCRIPT_PRIVILEGES: 1,
-  FULL_PRIVILEGES: 2,
-});
-
-// Finds the API Level ("FULL_PRIVILEGES", "CONTENTSCRIPT_PRIVILEGES", "NO_PRIVILEGES")
-// with a given a window object.
-function getAPILevelForWindow(window, addonId) {
-  const {NO_PRIVILEGES, CONTENTSCRIPT_PRIVILEGES, FULL_PRIVILEGES} = API_LEVELS;
-
-  // Non WebExtension URLs and WebExtension URLs from a different extension
-  // has no access to APIs.
-  if (!addonId || getAddonIdForWindow(window) != addonId) {
-    return NO_PRIVILEGES;
-  }
-
-  if (!ExtensionManagement.isExtensionProcess) {
-    return CONTENTSCRIPT_PRIVILEGES;
-  }
-
-  let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDocShell);
-
-  // Handling of ExtensionPages running inside sub-frames.
-  if (docShell.sameTypeParent) {
-    let parentWindow = docShell.sameTypeParent.QueryInterface(Ci.nsIInterfaceRequestor)
-                               .getInterface(Ci.nsIDOMWindow);
-
-    // The option page iframe embedded in the about:addons tab should have
-    // full API level privileges. (see Bug 1256282 for rationale)
-    let parentDocument = parentWindow.document;
-    let parentIsSystemPrincipal = Services.scriptSecurityManager
-                                          .isSystemPrincipal(parentDocument.nodePrincipal);
-
-    if (parentDocument.location.href == "about:addons" && parentIsSystemPrincipal) {
-      return FULL_PRIVILEGES;
-    }
-
-    // NOTE: Special handling for devtools panels using a chrome iframe here
-    // for the devtools panel, it is needed because a content iframe breaks
-    // switching between docked and undocked mode (see bug 1075490).
-    let devtoolsBrowser = parentDocument.querySelector(
-      "browser[webextension-view-type='devtools_panel']");
-    if (devtoolsBrowser && devtoolsBrowser.contentWindow === window &&
-        parentIsSystemPrincipal) {
-      return FULL_PRIVILEGES;
-    }
-
-    // The addon iframes embedded in a addon page from with the same addonId
-    // should have the same privileges of the sameTypeParent.
-    // (see Bug 1258347 for rationale)
-    let parentSameAddonPrivileges = getAPILevelForWindow(parentWindow, addonId);
-    if (parentSameAddonPrivileges > NO_PRIVILEGES) {
-      return parentSameAddonPrivileges;
-    }
-
-    // In all the other cases, WebExtension URLs loaded into sub-frame UI
-    // will have "content script API level privileges".
-    // (see Bug 1214658 for rationale)
-    return CONTENTSCRIPT_PRIVILEGES;
-  }
-
-  // WebExtension URLs loaded into top frames UI could have full API level privileges.
-  return FULL_PRIVILEGES;
-}
-
 let cacheInvalidated = 0;
 function onCacheInvalidate() {
   cacheInvalidated++;
 }
 Services.obs.addObserver(onCacheInvalidate, "startupcache-invalidate");
 
 ExtensionManagement = {
   get cacheInvalidated() {
@@ -274,18 +197,13 @@ ExtensionManagement = {
   startupExtension: Service.startupExtension.bind(Service),
   shutdownExtension: Service.shutdownExtension.bind(Service),
 
   registerAPI: APIs.register.bind(APIs),
   unregisterAPI: APIs.unregister.bind(APIs),
 
   getURLForExtension,
 
-  // exported API Level Helpers
-  getAddonIdForWindow,
-  getAPILevelForWindow,
-  API_LEVELS,
-
   APIs,
 };
 
 XPCOMUtils.defineLazyPreferenceGetter(ExtensionManagement, "useRemoteWebExtensions",
                                       "extensions.webextensions.remote", false);
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -163,20 +163,18 @@ class ScriptMatcher {
     return true;
   }
 
   injectInto(window) {
     return this.script.injectInto(window);
   }
 }
 
-function getMessageManager(contentWindow) {
-  let docShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIDocShell)
-                              .QueryInterface(Ci.nsIInterfaceRequestor);
+function getMessageManager(window) {
+  let docShell = window.document.docShell.QueryInterface(Ci.nsIInterfaceRequestor);
   try {
     return docShell.getInterface(Ci.nsIContentFrameMessageManager);
   } catch (e) {
     // Some windows don't support this interface (hidden window).
     return null;
   }
 }
 
@@ -443,36 +441,77 @@ DocumentManager = {
   preloadScripts(uri, loadInfo) {
     for (let script of this.contentScripts) {
       if (script.matchesLoadInfo(uri, loadInfo)) {
         script.preload();
       }
     }
   },
 
+  /**
+   * Checks that all parent frames for the given withdow either have the
+   * same add-on ID, or are special chrome-privileged documents such as
+   * about:addons or developer tools panels.
+   *
+   * @param {Window} window
+   *        The window to check.
+   * @param {string} addonId
+   *        The add-on ID to check.
+   * @returns {boolean}
+   */
+  checkParentFrames(window, addonId) {
+    while (window.parent !== window) {
+      let {frameElement} = window;
+      window = window.parent;
+
+      let principal = window.document.nodePrincipal;
+
+      if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
+        // The add-on manager is a special case, since it contains extension
+        // options pages in same-type <browser> frames.
+        if (window.location.href === "about:addons") {
+          return true;
+        }
+
+        // NOTE: Special handling for devtools panels using a chrome iframe here
+        // for the devtools panel, it is needed because a content iframe breaks
+        // switching between docked and undocked mode (see bug 1075490).
+        if (frameElement &&
+            frameElement.mozMatchesSelector("browser[webextension-view-type='devtools_panel']")) {
+          return true;
+        }
+      }
+
+      if (principal.addonId !== addonId) {
+        return false;
+      }
+    }
+
+    return true;
+  },
+
   loadInto(window) {
-    let extensionId = ExtensionManagement.getAddonIdForWindow(window);
-    if (!extensionId) {
+    let {addonId} = Cu.getObjectPrincipal(window);
+    if (!addonId) {
       return;
     }
 
-    let extension = ExtensionManager.get(extensionId);
+    let extension = ExtensionManager.get(addonId);
     if (!extension) {
-      throw new Error(`No registered extension for ID ${extensionId}`);
+      throw new Error(`No registered extension for ID ${addonId}`);
     }
 
-    let apiLevel = ExtensionManagement.getAPILevelForWindow(window, extensionId);
-    const levels = ExtensionManagement.API_LEVELS;
-
-    if (apiLevel === levels.CONTENTSCRIPT_PRIVILEGES) {
-      ExtensionContent.initExtensionContext(extension.realExtension, window);
-    } else if (apiLevel === levels.FULL_PRIVILEGES) {
+    if (this.checkParentFrames(window, addonId) && ExtensionManagement.isExtensionProcess) {
+      // We're in a top-level extension frame, or a sub-frame thereof,
+      // in the extension process. Inject the full extension page API.
       ExtensionPageChild.initExtensionContext(extension.realExtension, window);
     } else {
-      throw new Error(`Unexpected window with extension ID ${extensionId}`);
+      // We're in a content sub-frame or not in the extension process.
+      // Only inject a minimal content script API.
+      ExtensionContent.initExtensionContext(extension.realExtension, window);
     }
   },
 
   // Helpers
 
   * enumerateWindows(docShell) {
     if (docShell) {
       let enum_ = docShell.getDocShellEnumerator(docShell.typeContent,
deleted file mode 100644
--- a/toolkit/components/extensions/test/xpcshell/test_getAPILevelForWindow.js
+++ /dev/null
@@ -1,70 +0,0 @@
-"use strict";
-
-const {Service} = Cu.import("resource://gre/modules/ExtensionManagement.jsm", {});
-Cu.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "uuidGen",
-                                   "@mozilla.org/uuid-generator;1",
-                                   "nsIUUIDGenerator");
-
-function createWindowWithAddonId(addonId) {
-  const uuid = uuidGen.generateUUID().number.slice(1, -1);
-
-  const url = `moz-extension://${uuid}/`;
-
-  // Set the add-on ID for this moz-extensions: URL.
-  Service.uuidMap.set(uuid, {id: addonId});
-  do_register_cleanup(() => {
-    Service.uuidMap.delete(uuid);
-  });
-
-  let baseURI = Services.io.newURI(url);
-  let principal = Services.scriptSecurityManager
-                          .createCodebasePrincipal(baseURI, {});
-  let chromeNav = Services.appShell.createWindowlessBrowser(true);
-  let interfaceRequestor = chromeNav.QueryInterface(Ci.nsIInterfaceRequestor);
-  let docShell = interfaceRequestor.getInterface(Ci.nsIDocShell);
-  docShell.createAboutBlankContentViewer(principal);
-
-  return {chromeNav, window: docShell.contentViewer.DOMDocument.defaultView};
-}
-
-add_task(function* test_eventpages() {
-  Service.init();
-
-  const {getAPILevelForWindow, getAddonIdForWindow} = ExtensionManagement;
-  const {NO_PRIVILEGES, FULL_PRIVILEGES} = ExtensionManagement.API_LEVELS;
-  const FAKE_ADDON_ID = "fakeAddonId";
-  const OTHER_ADDON_ID = "otherFakeAddonId";
-  const EMPTY_ADDON_ID = "";
-
-  let fakeAddonId = createWindowWithAddonId(FAKE_ADDON_ID);
-  equal(getAddonIdForWindow(fakeAddonId.window), FAKE_ADDON_ID,
-        "the window has the expected addonId");
-
-  let apiLevel = getAPILevelForWindow(fakeAddonId.window, FAKE_ADDON_ID);
-  equal(apiLevel, FULL_PRIVILEGES,
-        "apiLevel for the window with the right addonId should be FULL_PRIVILEGES");
-
-  apiLevel = getAPILevelForWindow(fakeAddonId.window, OTHER_ADDON_ID);
-  equal(apiLevel, NO_PRIVILEGES,
-        "apiLevel for the window with a different addonId should be NO_PRIVILEGES");
-
-  fakeAddonId.chromeNav.close();
-
-  // NOTE: check that window with an empty addon Id (which are window that are
-  // not Extensions pages) always get no WebExtensions APIs.
-  let emptyAddonId = createWindowWithAddonId(EMPTY_ADDON_ID);
-  equal(getAddonIdForWindow(emptyAddonId.window), EMPTY_ADDON_ID,
-        "the window has the expected addonId");
-
-  apiLevel = getAPILevelForWindow(emptyAddonId.window, EMPTY_ADDON_ID);
-  equal(apiLevel, NO_PRIVILEGES,
-        "apiLevel for empty addonId should be NO_PRIVILEGES");
-
-  apiLevel = getAPILevelForWindow(emptyAddonId.window, OTHER_ADDON_ID);
-  equal(apiLevel, NO_PRIVILEGES,
-        "apiLevel for an 'empty addonId' window should be always NO_PRIVILEGES");
-
-  emptyAddonId.chromeNav.close();
-});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -75,17 +75,16 @@ skip-if = "android" # Bug 1350559
 [test_ext_storage_sync.js]
 head = head.js head_sync.js
 skip-if = os == "android"
 [test_ext_storage_sync_crypto.js]
 skip-if = os == "android"
 [test_ext_themes_supported_properties.js]
 [test_ext_topSites.js]
 skip-if = os == "android"
-[test_getAPILevelForWindow.js]
 [test_ext_legacy_extension_context.js]
 [test_ext_legacy_extension_embedding.js]
 [test_locale_converter.js]
 [test_locale_data.js]
 [test_native_messaging.js]
 skip-if = os == "android"
 [test_proxy_scripts.js]
 [include:xpcshell-content.ini]
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -2669,17 +2669,18 @@ profiler_is_active()
   PS::AutoLock lock(gPSMutex);
 
   return gPS->IsActive(lock);
 }
 
 void
 profiler_set_frame_number(int aFrameNumber)
 {
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  // This function runs both on (via tests) and off the main thread.
+
   MOZ_RELEASE_ASSERT(gPS);
 
   PS::AutoLock lock(gPSMutex);
 
   gPS->SetFrameNumber(lock, aFrameNumber);
 }
 
 void
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -587,18 +587,16 @@ CycleCollectedJSContext::Initialize(JSRu
     // main thread, since the UI for this tracing data only displays data
     // relevant to the main-thread.
     mPrevGCNurseryCollectionCallback = JS::SetGCNurseryCollectionCallback(
       mJSContext, GCNurseryCollectionCallback);
   }
 
   JS_SetObjectsTenuredCallback(mJSContext, JSObjectsTenuredCb, this);
   JS::SetOutOfMemoryCallback(mJSContext, OutOfMemoryCallback, this);
-  JS::SetLargeAllocationFailureCallback(mJSContext,
-                                        LargeAllocationFailureCallback, this);
   JS_SetExternalStringSizeofCallback(mJSContext, SizeofExternalStringCallback);
   JS::SetBuildIdOp(mJSContext, GetBuildId);
   JS::SetWarningReporter(mJSContext, MozCrashWarningReporter);
 #ifdef MOZ_CRASHREPORTER
     js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback(
             CrashReporter::AnnotateOOMAllocationSize);
 #endif
 
@@ -966,24 +964,16 @@ CycleCollectedJSContext::OutOfMemoryCall
 {
   CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
 
   MOZ_ASSERT(aContext == self->Context());
 
   self->OnOutOfMemory();
 }
 
-/* static */ void
-CycleCollectedJSContext::LargeAllocationFailureCallback(void* aData)
-{
-  CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
-
-  self->OnLargeAllocationFailure();
-}
-
 /* static */ size_t
 CycleCollectedJSContext::SizeofExternalStringCallback(JSString* aStr,
                                                       MallocSizeOf aMallocSizeOf)
 {
   // We promised the JS engine we would not GC.  Enforce that:
   JS::AutoCheckCannotGC autoCannotGC;
   
   if (!XPCStringConvert::IsDOMString(aStr)) {
@@ -1750,23 +1740,21 @@ CycleCollectedJSContext::OnOutOfMemory()
   MOZ_ASSERT(mJSContext);
 
   AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting);
   CustomOutOfMemoryCallback();
   AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported);
 }
 
 void
-CycleCollectedJSContext::OnLargeAllocationFailure()
+CycleCollectedJSContext::SetLargeAllocationFailure(OOMState aNewState)
 {
   MOZ_ASSERT(mJSContext);
 
-  AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reporting);
-  CustomLargeAllocationFailureCallback();
-  AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reported);
+  AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, aNewState);
 }
 
 void
 CycleCollectedJSContext::PrepareWaitingZonesForGC()
 {
   if (mZonesWaitingForGC.Count() == 0) {
     JS::PrepareForFullGC(Context());
   } else {
--- a/xpcom/base/CycleCollectedJSContext.h
+++ b/xpcom/base/CycleCollectedJSContext.h
@@ -155,17 +155,16 @@ protected:
   void UnmarkSkippableJSHolders();
 
   virtual void
   TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback& aCb) {}
   virtual void TraceAdditionalNativeGrayRoots(JSTracer* aTracer) {}
 
   virtual void CustomGCCallback(JSGCStatus aStatus) {}
   virtual void CustomOutOfMemoryCallback() {}
-  virtual void CustomLargeAllocationFailureCallback() {}
 
   std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
   std::queue<nsCOMPtr<nsIRunnable>> mDebuggerPromiseMicroTaskQueue;
 
 private:
   void
   DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
                   nsCycleCollectionTraversalCallback& aCb) const;
@@ -213,17 +212,16 @@ private:
   static void TraceGrayJS(JSTracer* aTracer, void* aData);
   static void GCCallback(JSContext* aContext, JSGCStatus aStatus, void* aData);
   static void GCSliceCallback(JSContext* aContext, JS::GCProgress aProgress,
                               const JS::GCDescription& aDesc);
   static void GCNurseryCollectionCallback(JSContext* aContext,
                                           JS::GCNurseryProgress aProgress,
                                           JS::gcreason::Reason aReason);
   static void OutOfMemoryCallback(JSContext* aContext, void* aData);
-  static void LargeAllocationFailureCallback(void* aData);
   /**
    * Callback for reporting external string memory.
    */
   static size_t SizeofExternalStringCallback(JSString* aStr,
                                              mozilla::MallocSizeOf aMallocSizeOf);
 
   static bool ContextCallback(JSContext* aCx, unsigned aOperation,
                               void* aData);
@@ -282,21 +280,22 @@ public:
 
     // The condition has happened, but a GC cycle ended since then.
     //
     // GC is taken as a proxy for "we've been banging on the heap a good bit
     // now and haven't crashed; the OOM was probably handled correctly".
     Recovered
   };
 
+  void SetLargeAllocationFailure(OOMState aNewState);
+
 private:
   void AnnotateAndSetOutOfMemory(OOMState* aStatePtr, OOMState aNewState);
   void OnGC(JSGCStatus aStatus);
   void OnOutOfMemory();
-  void OnLargeAllocationFailure();
 
 public:
   void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer);
   void RemoveJSHolder(void* aHolder);
 #ifdef DEBUG
   bool IsJSHolder(void* aHolder);
   void AssertNoObjectsToTrace(void* aPossibleJSHolder);
 #endif
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -320,17 +320,18 @@ struct nsThreadShutdownContext
   }
   ~nsThreadShutdownContext()
   {
     MOZ_COUNT_DTOR(nsThreadShutdownContext);
   }
 
   // NB: This will be the last reference.
   NotNull<RefPtr<nsThread>> mTerminatingThread;
-  NotNull<nsThread*> mJoiningThread;
+  NotNull<nsThread*> MOZ_UNSAFE_REF("Thread manager is holding reference to joining thread")
+    mJoiningThread;
   bool mAwaitingShutdownAck;
 };
 
 // This event is responsible for notifying nsThread::Shutdown that it is time
 // to call PR_JoinThread. It implements nsICancelableRunnable so that it can
 // run on a DOM Worker thread (where all events must implement
 // nsICancelableRunnable.)
 class nsThreadShutdownAckEvent : public CancelableRunnable